servo: Merge #16752 - Report more informative CSS errors (from jdm:css-parse-error); r=SimonSapin
authorJosh Matthews <josh@joshmatthews.net>
Fri, 09 Jun 2017 14:31:48 -0700
changeset 411422 79d8f06313932e8f116100390dd153a1c8f9734b
parent 411421 f7450fa2d7aa1590ac6a4954dbfc4cd8a9557254
child 411423 b6f4ba89792e51ed67a7407113f9953de3eb4f72
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersSimonSapin
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16752 - Report more informative CSS errors (from jdm:css-parse-error); r=SimonSapin This requires https://github.com/servo/rust-cssparser/pull/143 for the final commit. There's no better way to split that work up, unfortunately, and it's extremely easy to bitrot. I would appreciate if we could expedite reviewing this work. This is the work necessary to enable https://bugzilla.mozilla.org/show_bug.cgi?id=1352669. It makes sense to merge it separately because it's so much effort to keep it up to date with the ongoing Stylo work. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [x] There are tests for these changes Source-Repo: https://github.com/servo/servo Source-Revision: 061cb5f48e5c93a5decf39e530aea4a566e97341
servo/Cargo.lock
servo/components/canvas/Cargo.toml
servo/components/canvas_traits/Cargo.toml
servo/components/script/Cargo.toml
servo/components/script/dom/canvasgradient.rs
servo/components/script/dom/canvasrenderingcontext2d.rs
servo/components/script/dom/css.rs
servo/components/script/dom/csskeyframesrule.rs
servo/components/script/dom/cssmediarule.rs
servo/components/script/dom/csssupportsrule.rs
servo/components/script/dom/element.rs
servo/components/script/dom/htmllinkelement.rs
servo/components/script/dom/htmlstyleelement.rs
servo/components/script/dom/medialist.rs
servo/components/script/dom/node.rs
servo/components/script/dom/window.rs
servo/components/script_layout_interface/Cargo.toml
servo/components/script_layout_interface/reporter.rs
servo/components/selectors/Cargo.toml
servo/components/selectors/parser.rs
servo/components/style/Cargo.toml
servo/components/style/counter_style/mod.rs
servo/components/style/custom_properties.rs
servo/components/style/error_reporting.rs
servo/components/style/font_face.rs
servo/components/style/gecko/media_queries.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/gecko/url.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/macros.rs
servo/components/style/media_queries.rs
servo/components/style/parser.rs
servo/components/style/properties/declaration_block.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/longhand/color.mako.rs
servo/components/style/properties/longhand/counters.mako.rs
servo/components/style/properties/longhand/effects.mako.rs
servo/components/style/properties/longhand/font.mako.rs
servo/components/style/properties/longhand/inherited_box.mako.rs
servo/components/style/properties/longhand/inherited_svg.mako.rs
servo/components/style/properties/longhand/inherited_table.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/list.mako.rs
servo/components/style/properties/longhand/outline.mako.rs
servo/components/style/properties/longhand/pointing.mako.rs
servo/components/style/properties/longhand/position.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/properties/longhand/table.mako.rs
servo/components/style/properties/longhand/text.mako.rs
servo/components/style/properties/longhand/ui.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/properties/shorthand/background.mako.rs
servo/components/style/properties/shorthand/border.mako.rs
servo/components/style/properties/shorthand/box.mako.rs
servo/components/style/properties/shorthand/column.mako.rs
servo/components/style/properties/shorthand/font.mako.rs
servo/components/style/properties/shorthand/inherited_svg.mako.rs
servo/components/style/properties/shorthand/inherited_text.mako.rs
servo/components/style/properties/shorthand/list.mako.rs
servo/components/style/properties/shorthand/mask.mako.rs
servo/components/style/properties/shorthand/outline.mako.rs
servo/components/style/properties/shorthand/position.mako.rs
servo/components/style/properties/shorthand/text.mako.rs
servo/components/style/selector_parser.rs
servo/components/style/servo/media_queries.rs
servo/components/style/servo/selector_parser.rs
servo/components/style/servo/url.rs
servo/components/style/stylesheets/document_rule.rs
servo/components/style/stylesheets/keyframes_rule.rs
servo/components/style/stylesheets/mod.rs
servo/components/style/stylesheets/rule_parser.rs
servo/components/style/stylesheets/stylesheet.rs
servo/components/style/stylesheets/supports_rule.rs
servo/components/style/stylesheets/viewport_rule.rs
servo/components/style/stylist.rs
servo/components/style/values/generics/grid.rs
servo/components/style/values/generics/mod.rs
servo/components/style/values/generics/rect.rs
servo/components/style/values/generics/text.rs
servo/components/style/values/mod.rs
servo/components/style/values/specified/align.rs
servo/components/style/values/specified/background.rs
servo/components/style/values/specified/basic_shape.rs
servo/components/style/values/specified/border.rs
servo/components/style/values/specified/calc.rs
servo/components/style/values/specified/color.rs
servo/components/style/values/specified/gecko.rs
servo/components/style/values/specified/grid.rs
servo/components/style/values/specified/image.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/position.rs
servo/components/style/values/specified/rect.rs
servo/components/style/values/specified/text.rs
servo/components/style/values/specified/transform.rs
servo/components/style_traits/Cargo.toml
servo/components/style_traits/lib.rs
servo/components/style_traits/values.rs
servo/components/style_traits/viewport.rs
servo/ports/geckolib/Cargo.toml
servo/ports/geckolib/glue.rs
servo/python/servo/testing_commands.py
servo/tests/unit/gfx/Cargo.toml
servo/tests/unit/style/Cargo.toml
servo/tests/unit/style/media_queries.rs
servo/tests/unit/style/parsing/border.rs
servo/tests/unit/style/parsing/image.rs
servo/tests/unit/style/parsing/length.rs
servo/tests/unit/style/parsing/mod.rs
servo/tests/unit/style/parsing/selectors.rs
servo/tests/unit/style/parsing/supports.rs
servo/tests/unit/style/parsing/value.rs
servo/tests/unit/style/properties/mod.rs
servo/tests/unit/style/properties/serialization.rs
servo/tests/unit/style/restyle_hints.rs
servo/tests/unit/style/rule_tree/bench.rs
servo/tests/unit/style/stylesheets.rs
servo/tests/unit/style/viewport.rs
servo/tests/unit/stylo/Cargo.toml
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -309,32 +309,32 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "canvas"
 version = "0.0.1"
 dependencies = [
  "azure 0.16.0 (git+https://github.com/servo/rust-azure)",
  "canvas_traits 0.0.1",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "offscreen_gl_context 0.8.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "webrender_traits 0.40.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "canvas_traits"
 version = "0.0.1"
 dependencies = [
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_traits 0.40.0 (git+https://github.com/servo/webrender)",
 ]
@@ -562,36 +562,36 @@ source = "registry+https://github.com/ru
 dependencies = [
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cssparser"
-version = "0.13.7"
+version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "procedural-masquerade 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "cssparser-macros"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "procedural-masquerade 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "dbghelp-sys"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1000,17 +1000,17 @@ dependencies = [
  "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "geckoservo"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "nsstring_vendor 0.1.0",
  "parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
  "style 0.0.1",
  "style_traits 0.0.1",
@@ -1067,17 +1067,17 @@ dependencies = [
  "xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "xml5ever 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gfx_tests"
 version = "0.0.1"
 dependencies = [
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx 0.0.1",
  "ipc-channel 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
 ]
 
 [[package]]
 name = "gfx_traits"
 version = "0.0.1"
@@ -2153,17 +2153,17 @@ dependencies = [
 
 [[package]]
 name = "precomputed-hash"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "procedural-masquerade"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "profile"
 version = "0.0.1"
 dependencies = [
  "heartbeats-simple 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2366,17 +2366,17 @@ dependencies = [
  "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bluetooth_traits 0.0.1",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas_traits 0.0.1",
  "caseless 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "cookie 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "deny_public_fields 0.0.1",
  "devtools_traits 0.0.1",
  "dom_struct 0.0.1",
  "domobject_derive 0.0.1",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
@@ -2439,17 +2439,17 @@ dependencies = [
 
 [[package]]
 name = "script_layout_interface"
 version = "0.0.1"
 dependencies = [
  "app_units 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas_traits 0.0.1",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2511,17 +2511,17 @@ dependencies = [
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "selectors"
 version = "0.19.0"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_arc 0.0.1",
  "size_of_test 0.0.1",
@@ -2893,17 +2893,17 @@ dependencies = [
  "arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "arrayvec 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.25.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2947,17 +2947,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "style_tests"
 version = "0.0.1"
 dependencies = [
  "app_units 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
@@ -2967,30 +2967,31 @@ dependencies = [
  "style_traits 0.0.1",
 ]
 
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
  "app_units 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "selectors 0.19.0",
  "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "stylo_tests"
 version = "0.0.1"
 dependencies = [
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "geckoservo 0.0.1",
  "libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
  "size_of_test 0.0.1",
  "style 0.0.1",
@@ -3560,17 +3561,17 @@ dependencies = [
 "checksum cocoa 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a5d0bcb4d345adf9b4ada6c5bb3e2b87c8150b79c46f3f26446de5f4d48de4b"
 "checksum color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a475fc4af42d83d28adf72968d9bcfaf035a1a9381642d8e85d8a04957767b0d"
 "checksum compiletest_rs 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "df47edea8bf052f23ce25a15cbf0be09c96911e3be943d1e81415bfaf0e74bf8"
 "checksum cookie 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30b3493e12a550c2f96be785088d1da8d93189e7237c8a8d0d871bc9070334c3"
 "checksum core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f51ce3b8ebe311c56de14231eb57572c15abebd2d32b3bcb99bcdb9c101f5ac3"
 "checksum core-foundation-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "41115a6aa5d3e1e5ef98148373f25971d1fad53818553f216495f9e67e90a624"
 "checksum core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ead017dcf77f503dc991f6b52de6084eeea60a94b0a652baa9bf88654a28e83f"
 "checksum core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e9719616a10f717628e074744f8c55df7b450f7a34d29c196d14f4498aad05d"
-"checksum cssparser 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef6124306e5ebc5ab11891d063aeafdd0cdc308079b708c8b566125f3680292b"
+"checksum cssparser 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a5ca71edbab09f8dc1e3d1c132717562c3b01c8598ab669183c5195bb1761"
 "checksum cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "079adec4af52bb5275eadd004292028c79eb3c5f5b4ee8086a36d4197032f6df"
 "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
 "checksum dbus 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4aee01fb76ada3e5e7ca642ea6664ebf7308a810739ca2aca44909a1191ac254"
 "checksum debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3"
 "checksum deflate 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebb02aaf4b775afc96684b8402510a338086974e38570a1f65bea8c202eb77a7"
 "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
 "checksum device 0.0.1 (git+https://github.com/servo/devices)" = "<none>"
 "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
@@ -3691,17 +3692,17 @@ dependencies = [
 "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
 "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
 "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
 "checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
 "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
 "checksum plane-split 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "556929ef77bf07a9f8584d21382bcebcd6e6f5845d311824d369e1df7cf56d54"
 "checksum png 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3cb773e9a557edb568ce9935cf783e3cdcabe06a9449d41b3e5506d88e582c82"
 "checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150"
-"checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
+"checksum procedural-masquerade 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c93cdc1fb30af9ddf3debc4afbdb0f35126cbd99daa229dd76cdd5349b41d989"
 "checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41"
 "checksum quasi 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c45c4854d6d1cf5d531db97c75880feb91c958b0720f4ec1057135fec358b3"
 "checksum quasi_codegen 0.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9e25fa23c044c1803f43ca59c98dac608976dd04ce799411edd58ece776d4"
 "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
 "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
 "checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a"
 "checksum rayon-core 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd1e76f8ee0322fbbeb0c43a07e1757fcf8ff06bb0ff92da017625882ddc04dd"
 "checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b"
--- a/servo/components/canvas/Cargo.toml
+++ b/servo/components/canvas/Cargo.toml
@@ -7,17 +7,17 @@ publish = false
 
 [lib]
 name = "canvas"
 path = "lib.rs"
 
 [dependencies]
 azure = {git = "https://github.com/servo/rust-azure"}
 canvas_traits = {path = "../canvas_traits"}
-cssparser = "0.13.7"
+cssparser = "0.14.0"
 euclid = "0.13"
 gleam = "0.4"
 ipc-channel = "0.7"
 log = "0.3.5"
 num-traits = "0.1.32"
 offscreen_gl_context = "0.8"
 servo_config = {path = "../config"}
 webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
--- a/servo/components/canvas_traits/Cargo.toml
+++ b/servo/components/canvas_traits/Cargo.toml
@@ -5,16 +5,16 @@ authors = ["The Servo Project Developers
 license = "MPL-2.0"
 publish = false
 
 [lib]
 name = "canvas_traits"
 path = "lib.rs"
 
 [dependencies]
-cssparser = "0.13.7"
+cssparser = "0.14.0"
 euclid = "0.13"
 heapsize = "0.4"
 heapsize_derive = "0.1"
 ipc-channel = "0.7"
 serde = "0.9"
 serde_derive = "0.9"
 webrender_traits = {git = "https://github.com/servo/webrender", features = ["ipc"]}
--- a/servo/components/script/Cargo.toml
+++ b/servo/components/script/Cargo.toml
@@ -30,17 +30,17 @@ audio-video-metadata = "0.1.2"
 atomic_refcell = "0.1"
 base64 = "0.5.2"
 bitflags = "0.7"
 bluetooth_traits = {path = "../bluetooth_traits"}
 byteorder = "1.0"
 canvas_traits = {path = "../canvas_traits"}
 caseless = "0.1.0"
 cookie = "0.6"
-cssparser = "0.13.7"
+cssparser = "0.14.0"
 deny_public_fields = {path = "../deny_public_fields"}
 devtools_traits = {path = "../devtools_traits"}
 dom_struct = {path = "../dom_struct"}
 domobject_derive = {path = "../domobject_derive"}
 encoding = "0.2"
 euclid = "0.13"
 fnv = "1.0"
 gleam = "0.4"
--- a/servo/components/script/dom/canvasgradient.rs
+++ b/servo/components/script/dom/canvasgradient.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use canvas_traits::{CanvasGradientStop, FillOrStrokeStyle, LinearGradientStyle, RadialGradientStyle};
-use cssparser::{Parser, RGBA};
+use cssparser::{Parser, ParserInput, RGBA};
 use cssparser::Color as CSSColor;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::CanvasGradientBinding;
 use dom::bindings::codegen::Bindings::CanvasGradientBinding::CanvasGradientMethods;
 use dom::bindings::error::{Error, ErrorResult};
 use dom::bindings::js::Root;
 use dom::bindings::num::Finite;
 use dom::bindings::reflector::{Reflector, reflect_dom_object};
@@ -48,17 +48,18 @@ impl CanvasGradient {
 
 impl CanvasGradientMethods for CanvasGradient {
     // https://html.spec.whatwg.org/multipage/#dom-canvasgradient-addcolorstop
     fn AddColorStop(&self, offset: Finite<f64>, color: DOMString) -> ErrorResult {
         if *offset < 0f64 || *offset > 1f64 {
             return Err(Error::IndexSize);
         }
 
-        let mut parser = Parser::new(&color);
+        let mut input = ParserInput::new(&color);
+        let mut parser = Parser::new(&mut input);
         let color = CSSColor::parse(&mut parser);
         let color = if parser.is_exhausted() {
             match color {
                 Ok(CSSColor::RGBA(rgba)) => rgba,
                 Ok(CSSColor::CurrentColor) => RGBA::new(0, 0, 0, 255),
                 _ => return Err(Error::Syntax)
             }
         } else {
--- a/servo/components/script/dom/canvasrenderingcontext2d.rs
+++ b/servo/components/script/dom/canvasrenderingcontext2d.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use canvas_traits::{Canvas2dMsg, CanvasCommonMsg, CanvasMsg};
 use canvas_traits::{CompositionOrBlending, FillOrStrokeStyle, FillRule};
 use canvas_traits::{LineCapStyle, LineJoinStyle, LinearGradientStyle};
 use canvas_traits::{RadialGradientStyle, RepetitionStyle, byte_swap_and_premultiply};
-use cssparser::{Parser, RGBA};
+use cssparser::{Parser, ParserInput, RGBA};
 use cssparser::Color as CSSColor;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasFillRule;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineCap;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasLineJoin;
 use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
@@ -458,17 +458,18 @@ impl CanvasRenderingContext2D {
             return None;
         }
 
         Some(Rect::new(Point2D::new(x as f32, y as f32),
                        Size2D::new(w as f32, h as f32)))
     }
 
     fn parse_color(&self, string: &str) -> Result<RGBA, ()> {
-        let mut parser = Parser::new(&string);
+        let mut input = ParserInput::new(string);
+        let mut parser = Parser::new(&mut input);
         let color = CSSColor::parse(&mut parser);
         if parser.is_exhausted() {
             match color {
                 Ok(CSSColor::RGBA(rgba)) => Ok(rgba),
                 Ok(CSSColor::CurrentColor) => {
                     // TODO: https://github.com/whatwg/html/issues/1099
                     // Reconsider how to calculate currentColor in a display:none canvas
 
@@ -1309,17 +1310,18 @@ impl Drop for CanvasRenderingContext2D {
     fn drop(&mut self) {
         if let Err(err) = self.ipc_renderer.send(CanvasMsg::Common(CanvasCommonMsg::Close)) {
             warn!("Could not close canvas: {}", err)
         }
     }
 }
 
 pub fn parse_color(string: &str) -> Result<RGBA, ()> {
-    let mut parser = Parser::new(&string);
+    let mut input = ParserInput::new(string);
+    let mut parser = Parser::new(&mut input);
     match CSSColor::parse(&mut parser) {
         Ok(CSSColor::RGBA(rgba)) => {
             if parser.is_exhausted() {
                 Ok(rgba)
             } else {
                 Err(())
             }
         },
--- a/servo/components/script/dom/css.rs
+++ b/servo/components/script/dom/css.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cssparser::{Parser, serialize_identifier};
+use cssparser::{Parser, ParserInput, serialize_identifier};
 use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
 use dom::bindings::error::Fallible;
 use dom::bindings::reflector::Reflector;
 use dom::bindings::str::DOMString;
 use dom::window::Window;
 use dom_struct::dom_struct;
 use style::context::QuirksMode;
 use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
@@ -34,17 +34,18 @@ impl CSS {
         let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Supports),
                                                    PARSING_MODE_DEFAULT,
                                                    QuirksMode::NoQuirks);
         decl.eval(&context)
     }
 
     /// https://drafts.csswg.org/css-conditional/#dom-css-supports
     pub fn Supports_(win: &Window, condition: DOMString) -> bool {
-        let mut input = Parser::new(&condition);
+        let mut input = ParserInput::new(&condition);
+        let mut input = Parser::new(&mut input);
         let cond = parse_condition_or_declaration(&mut input);
         if let Ok(cond) = cond {
             let url = win.Document().url();
             let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Supports),
                                                        PARSING_MODE_DEFAULT,
                                                        QuirksMode::NoQuirks);
             cond.eval(&context)
         } else {
--- a/servo/components/script/dom/csskeyframesrule.rs
+++ b/servo/components/script/dom/csskeyframesrule.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cssparser::Parser;
+use cssparser::{Parser, ParserInput};
 use dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding;
 use dom::bindings::codegen::Bindings::CSSKeyframesRuleBinding::CSSKeyframesRuleMethods;
 use dom::bindings::error::ErrorResult;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{MutNullableJS, Root};
 use dom::bindings::reflector::{DomObject, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::csskeyframerule::CSSKeyframeRule;
@@ -53,17 +53,18 @@ impl CSSKeyframesRule {
             CSSRuleList::new(self.global().as_window(),
                              parent_stylesheet,
                              RulesSource::Keyframes(self.keyframesrule.clone()))
         })
     }
 
     /// Given a keyframe selector, finds the index of the first corresponding rule if any
     fn find_rule(&self, selector: &str) -> Option<usize> {
-        let mut input = Parser::new(selector);
+        let mut input = ParserInput::new(selector);
+        let mut input = Parser::new(&mut input);
         if let Ok(sel) = KeyframeSelector::parse(&mut input) {
             let guard = self.cssrule.shared_lock().read();
             // This finds the *last* element matching a selector
             // because that's the rule that applies. Thus, rposition
             self.keyframesrule.read_with(&guard)
                 .keyframes.iter().rposition(|frame| {
                     frame.read_with(&guard).selector == sel
                 })
--- a/servo/components/script/dom/cssmediarule.rs
+++ b/servo/components/script/dom/cssmediarule.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cssparser::Parser;
+use cssparser::{Parser, ParserInput};
 use dom::bindings::codegen::Bindings::CSSMediaRuleBinding;
 use dom::bindings::codegen::Bindings::CSSMediaRuleBinding::CSSMediaRuleMethods;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
 use dom::bindings::js::{MutNullableJS, Root};
 use dom::bindings::reflector::{DomObject, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::cssconditionrule::CSSConditionRule;
 use dom::cssrule::SpecificCSSRule;
@@ -64,17 +64,18 @@ impl CSSMediaRule {
         let guard = self.cssconditionrule.shared_lock().read();
         let rule = self.mediarule.read_with(&guard);
         let list = rule.media_queries.read_with(&guard);
         list.to_css_string().into()
     }
 
     /// https://drafts.csswg.org/css-conditional-3/#the-cssmediarule-interface
     pub fn set_condition_text(&self, text: DOMString) {
-        let mut input = Parser::new(&text);
+        let mut input = ParserInput::new(&text);
+        let mut input = Parser::new(&mut input);
         let global = self.global();
         let win = global.as_window();
         let url = win.get_url();
         let quirks_mode = win.Document().quirks_mode();
         let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media),
                                                    PARSING_MODE_DEFAULT,
                                                    quirks_mode);
         let new_medialist = parse_media_query_list(&context, &mut input);
--- a/servo/components/script/dom/csssupportsrule.rs
+++ b/servo/components/script/dom/csssupportsrule.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cssparser::Parser;
+use cssparser::{Parser, ParserInput};
 use dom::bindings::codegen::Bindings::CSSSupportsRuleBinding;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
 use dom::bindings::js::Root;
 use dom::bindings::reflector::{DomObject, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::cssconditionrule::CSSConditionRule;
 use dom::cssrule::SpecificCSSRule;
 use dom::cssstylesheet::CSSStyleSheet;
@@ -50,17 +50,18 @@ impl CSSSupportsRule {
     pub fn get_condition_text(&self) -> DOMString {
         let guard = self.cssconditionrule.shared_lock().read();
         let rule = self.supportsrule.read_with(&guard);
         rule.condition.to_css_string().into()
     }
 
     /// https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface
     pub fn set_condition_text(&self, text: DOMString) {
-        let mut input = Parser::new(&text);
+        let mut input = ParserInput::new(&text);
+        let mut input = Parser::new(&mut input);
         let cond = SupportsCondition::parse(&mut input);
         if let Ok(cond) = cond {
             let global = self.global();
             let win = global.as_window();
             let url = win.Document().url();
             let quirks_mode = win.Document().quirks_mode();
             let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Supports),
                                                        PARSING_MODE_DEFAULT,
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -2056,33 +2056,33 @@ impl ElementMethods for Element {
     // https://dom.spec.whatwg.org/#dom-childnode-remove
     fn Remove(&self) {
         self.upcast::<Node>().remove_self();
     }
 
     // https://dom.spec.whatwg.org/#dom-element-matches
     fn Matches(&self, selectors: DOMString) -> Fallible<bool> {
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
-            Err(()) => Err(Error::Syntax),
+            Err(_) => Err(Error::Syntax),
             Ok(selectors) => {
                 let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
                 Ok(matches_selector_list(&selectors, &Root::from_ref(self), &mut ctx))
             }
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector
     fn WebkitMatchesSelector(&self, selectors: DOMString) -> Fallible<bool> {
         self.Matches(selectors)
     }
 
     // https://dom.spec.whatwg.org/#dom-element-closest
     fn Closest(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
-            Err(()) => Err(Error::Syntax),
+            Err(_) => Err(Error::Syntax),
             Ok(selectors) => {
                 let root = self.upcast::<Node>();
                 for element in root.inclusive_ancestors() {
                     if let Some(element) = Root::downcast::<Element>(element) {
                         let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
                         if matches_selector_list(&selectors, &element, &mut ctx) {
                             return Ok(Some(element));
                         }
--- a/servo/components/script/dom/htmllinkelement.rs
+++ b/servo/components/script/dom/htmllinkelement.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cssparser::Parser as CssParser;
+use cssparser::{Parser as CssParser, ParserInput};
 use dom::attr::Attr;
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListBinding::DOMTokenListMethods;
 use dom::bindings::codegen::Bindings::HTMLLinkElementBinding;
 use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{MutNullableJS, Root, RootedReference};
 use dom::bindings::str::DOMString;
@@ -273,17 +273,18 @@ impl HTMLLinkElement {
 
         let mq_attribute = element.get_attribute(&ns!(), &local_name!("media"));
         let value = mq_attribute.r().map(|a| a.value());
         let mq_str = match value {
             Some(ref value) => &***value,
             None => "",
         };
 
-        let mut css_parser = CssParser::new(&mq_str);
+        let mut input = ParserInput::new(&mq_str);
+        let mut css_parser = CssParser::new(&mut input);
         let win = document.window();
         let doc_url = document.url();
         let context = CssParserContext::new_for_cssom(&doc_url, win.css_error_reporter(), Some(CssRuleType::Media),
                                                       PARSING_MODE_DEFAULT,
                                                       document.quirks_mode());
         let media = parse_media_query_list(&context, &mut css_parser);
 
         let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
--- a/servo/components/script/dom/htmlstyleelement.rs
+++ b/servo/components/script/dom/htmlstyleelement.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cssparser::Parser as CssParser;
+use cssparser::{Parser as CssParser, ParserInput};
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::HTMLStyleElementBinding;
 use dom::bindings::codegen::Bindings::HTMLStyleElementBinding::HTMLStyleElementMethods;
 use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{MutNullableJS, Root};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::document::Document;
@@ -86,18 +86,19 @@ impl HTMLStyleElement {
         let data = node.GetTextContent().expect("Element.textContent must be a string");
         let url = win.get_url();
         let context = CssParserContext::new_for_cssom(&url,
                                                       win.css_error_reporter(),
                                                       Some(CssRuleType::Media),
                                                       PARSING_MODE_DEFAULT,
                                                       doc.quirks_mode());
         let shared_lock = node.owner_doc().style_shared_lock().clone();
+        let mut input = ParserInput::new(&mq_str);
         let mq = Arc::new(shared_lock.wrap(
-                    parse_media_query_list(&context, &mut CssParser::new(&mq_str))));
+                    parse_media_query_list(&context, &mut CssParser::new(&mut input))));
         let loader = StylesheetLoader::for_element(self.upcast());
         let sheet = Stylesheet::from_str(&data, win.get_url(), Origin::Author, mq,
                                          shared_lock, Some(&loader),
                                          win.css_error_reporter(),
                                          doc.quirks_mode(),
                                          self.line_number);
 
         let sheet = Arc::new(sheet);
--- a/servo/components/script/dom/medialist.rs
+++ b/servo/components/script/dom/medialist.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use cssparser::Parser;
+use cssparser::{Parser, ParserInput};
 use dom::bindings::codegen::Bindings::MediaListBinding;
 use dom::bindings::codegen::Bindings::MediaListBinding::MediaListMethods;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
 use dom::bindings::js::{JS, Root};
 use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
@@ -66,17 +66,18 @@ impl MediaListMethods for MediaList {
         let mut media_queries = self.media_queries.write_with(&mut guard);
         // Step 2
         if value.is_empty() {
             // Step 1
             *media_queries = StyleMediaList::empty();
             return;
         }
         // Step 3
-        let mut parser = Parser::new(&value);
+        let mut input = ParserInput::new(&value);
+        let mut parser = Parser::new(&mut input);
         let global = self.global();
         let win = global.as_window();
         let url = win.get_url();
         let quirks_mode = win.Document().quirks_mode();
         let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media),
                                                    PARSING_MODE_DEFAULT,
                                                    quirks_mode);
         *media_queries = parse_media_query_list(&context, &mut parser);
@@ -102,17 +103,18 @@ impl MediaListMethods for MediaList {
     // https://drafts.csswg.org/cssom/#dom-medialist-item
     fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
         self.Item(index)
     }
 
     // https://drafts.csswg.org/cssom/#dom-medialist-appendmedium
     fn AppendMedium(&self, medium: DOMString) {
         // Step 1
-        let mut parser = Parser::new(&medium);
+        let mut input = ParserInput::new(&medium);
+        let mut parser = Parser::new(&mut input);
         let global = self.global();
         let win = global.as_window();
         let url = win.get_url();
         let quirks_mode = win.Document().quirks_mode();
         let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media),
                                                    PARSING_MODE_DEFAULT,
                                                    quirks_mode);
         let m = MediaQuery::parse(&context, &mut parser);
@@ -130,17 +132,18 @@ impl MediaListMethods for MediaList {
         }
         // Step 4
         mq.media_queries.push(m.unwrap());
     }
 
     // https://drafts.csswg.org/cssom/#dom-medialist-deletemedium
     fn DeleteMedium(&self, medium: DOMString) {
         // Step 1
-        let mut parser = Parser::new(&medium);
+        let mut input = ParserInput::new(&medium);
+        let mut parser = Parser::new(&mut input);
         let global = self.global();
         let win = global.as_window();
         let url = win.get_url();
         let quirks_mode = win.Document().quirks_mode();
         let context = ParserContext::new_for_cssom(&url, win.css_error_reporter(), Some(CssRuleType::Media),
                                                    PARSING_MODE_DEFAULT,
                                                    quirks_mode);
         let m = MediaQuery::parse(&context, &mut parser);
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -712,17 +712,17 @@ impl Node {
         self.AppendChild(&node).map(|_| ())
     }
 
     // https://dom.spec.whatwg.org/#dom-parentnode-queryselector
     pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
         // Step 1.
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             // Step 2.
-            Err(()) => Err(Error::Syntax),
+            Err(_) => Err(Error::Syntax),
             // Step 3.
             Ok(selectors) => {
                 let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
                 Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| {
                     matches_selector_list(&selectors, element, &mut ctx)
                 }))
             }
         }
@@ -732,17 +732,17 @@ impl Node {
     /// Get an iterator over all nodes which match a set of selectors
     /// Be careful not to do anything which may manipulate the DOM tree
     /// whilst iterating, otherwise the iterator may be invalidated.
     pub fn query_selector_iter(&self, selectors: DOMString)
                                   -> Fallible<QuerySelectorIterator> {
         // Step 1.
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             // Step 2.
-            Err(()) => Err(Error::Syntax),
+            Err(_) => Err(Error::Syntax),
             // Step 3.
             Ok(selectors) => {
                 let mut descendants = self.traverse_preorder();
                 // Skip the root of the tree.
                 assert!(&*descendants.next().unwrap() == self);
                 Ok(QuerySelectorIterator::new(descendants, selectors))
             }
         }
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use base64;
 use bluetooth_traits::BluetoothRequest;
-use cssparser::Parser;
+use cssparser::{Parser, ParserInput};
 use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType};
 use dom::bindings::cell::DOMRefCell;
 use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
 use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
 use dom::bindings::codegen::Bindings::EventHandlerBinding::OnBeforeUnloadEventHandlerNonNull;
 use dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull;
 use dom::bindings::codegen::Bindings::FunctionBinding::Function;
 use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
@@ -998,17 +998,18 @@ impl WindowMethods for Window {
         match open::that(url.as_str()) {
             Ok(_) => Ok(()),
             Err(e) => Err(Error::Type(format!("Couldn't open URL: {}", e))),
         }
     }
 
     // https://drafts.csswg.org/cssom-view/#dom-window-matchmedia
     fn MatchMedia(&self, query: DOMString) -> Root<MediaQueryList> {
-        let mut parser = Parser::new(&query);
+        let mut input = ParserInput::new(&query);
+        let mut parser = Parser::new(&mut input);
         let url = self.get_url();
         let quirks_mode = self.Document().quirks_mode();
         let context = CssParserContext::new_for_cssom(&url, self.css_error_reporter(), Some(CssRuleType::Media),
                                                       PARSING_MODE_DEFAULT,
                                                       quirks_mode);
         let media_query_list = media_queries::parse_media_query_list(&context, &mut parser);
         let document = self.Document();
         let mql = MediaQueryList::new(&document, media_query_list);
--- a/servo/components/script_layout_interface/Cargo.toml
+++ b/servo/components/script_layout_interface/Cargo.toml
@@ -8,17 +8,17 @@ publish = false
 [lib]
 name = "script_layout_interface"
 path = "lib.rs"
 
 [dependencies]
 app_units = "0.4.1"
 atomic_refcell = "0.1"
 canvas_traits = {path = "../canvas_traits"}
-cssparser = "0.13.7"
+cssparser = "0.14.0"
 euclid = "0.13"
 gfx_traits = {path = "../gfx_traits"}
 heapsize = "0.4"
 heapsize_derive = "0.1"
 html5ever = "0.17"
 ipc-channel = "0.7"
 libc = "0.2"
 log = "0.3.5"
--- a/servo/components/script_layout_interface/reporter.rs
+++ b/servo/components/script_layout_interface/reporter.rs
@@ -4,46 +4,46 @@
 
 use cssparser::{Parser, SourcePosition};
 use ipc_channel::ipc::IpcSender;
 use log;
 use msg::constellation_msg::PipelineId;
 use script_traits::ConstellationControlMsg;
 use servo_url::ServoUrl;
 use std::sync::{Mutex, Arc};
-use style::error_reporting::ParseErrorReporter;
+use style::error_reporting::{ParseErrorReporter, ContextualParseError};
 
 #[derive(HeapSizeOf, Clone)]
 pub struct CSSErrorReporter {
     pub pipelineid: PipelineId,
     // Arc+Mutex combo is necessary to make this struct Sync,
     // which is necessary to fulfill the bounds required by the
     // uses of the ParseErrorReporter trait.
     #[ignore_heap_size_of = "Arc is defined in libstd"]
     pub script_chan: Arc<Mutex<IpcSender<ConstellationControlMsg>>>,
 }
 
 impl ParseErrorReporter for CSSErrorReporter {
-    fn report_error(&self,
-                    input: &mut Parser,
-                    position: SourcePosition,
-                    message: &str,
-                    url: &ServoUrl,
-                    line_number_offset: u64) {
+    fn report_error<'a>(&self,
+                        input: &mut Parser,
+                        position: SourcePosition,
+                        error: ContextualParseError<'a>,
+                        url: &ServoUrl,
+                        line_number_offset: u64) {
         let location = input.source_location(position);
         let line_offset = location.line + line_number_offset as usize;
         if log_enabled!(log::LogLevel::Info) {
             info!("Url:\t{}\n{}:{} {}",
                   url.as_str(),
                   line_offset,
                   location.column,
-                  message)
+                  error.to_string())
         }
 
         //TODO: report a real filename
         let _ = self.script_chan.lock().unwrap().send(
             ConstellationControlMsg::ReportCSSError(self.pipelineid,
                                                     "".to_owned(),
                                                     location.line,
                                                     location.column,
-                                                    message.to_owned()));
+                                                    error.to_string()));
     }
 }
--- a/servo/components/selectors/Cargo.toml
+++ b/servo/components/selectors/Cargo.toml
@@ -19,17 +19,17 @@ path = "lib.rs"
 doctest = false
 
 [features]
 gecko_like_types = []
 
 [dependencies]
 bitflags = "0.7"
 matches = "0.1"
-cssparser = "0.13.7"
+cssparser = "0.14.0"
 log = "0.3"
 fnv = "1.0"
 phf = "0.7.18"
 precomputed-hash = "0.1"
 servo_arc = { path = "../servo_arc" }
 smallvec = "0.4"
 
 [dev-dependencies]
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use attr::{AttrSelectorWithNamespace, ParsedAttrSelectorOperation, AttrSelectorOperator};
 use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint};
+use cssparser::{ParseError, BasicParseError};
 use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
 use precomputed_hash::PrecomputedHash;
 use servo_arc::{Arc, HeaderWithLength, ThinArc};
 use smallvec::SmallVec;
 use std::ascii::AsciiExt;
 use std::borrow::{Borrow, Cow};
 use std::cmp;
 use std::fmt::{self, Display, Debug, Write};
@@ -38,16 +39,40 @@ fn to_ascii_lowercase(s: &str) -> Cow<st
         let mut string = s.to_owned();
         string[first_uppercase..].make_ascii_lowercase();
         string.into()
     } else {
         s.into()
     }
 }
 
+#[derive(Clone, Debug, PartialEq)]
+pub enum SelectorParseError<'i, T> {
+    PseudoElementInComplexSelector,
+    NoQualifiedNameInAttributeSelector,
+    TooManyCompoundSelectorComponentsInNegation,
+    NegationSelectorComponentNotNamespace,
+    NegationSelectorComponentNotLocalName,
+    EmptySelector,
+    NonSimpleSelectorInNegation,
+    UnexpectedTokenInAttributeSelector,
+    PseudoElementExpectedColon,
+    PseudoElementExpectedIdent,
+    UnsupportedPseudoClass,
+    UnexpectedIdent(Cow<'i, str>),
+    ExpectedNamespace,
+    Custom(T),
+}
+
+impl<'a, T> Into<ParseError<'a, SelectorParseError<'a, T>>> for SelectorParseError<'a, T> {
+    fn into(self) -> ParseError<'a, SelectorParseError<'a, T>> {
+        ParseError::Custom(self)
+    }
+}
+
 macro_rules! with_all_bounds {
     (
         [ $( $InSelector: tt )* ]
         [ $( $CommonBounds: tt )* ]
         [ $( $FromStr: tt )* ]
     ) => {
         fn from_cow_str<T>(cow: Cow<str>) -> T where T: $($FromStr)* {
             match cow {
@@ -92,36 +117,40 @@ macro_rules! with_bounds {
     }
 }
 
 with_bounds! {
     [Clone + Eq]
     [From<String> + for<'a> From<&'a str>]
 }
 
-pub trait Parser {
+pub trait Parser<'i> {
     type Impl: SelectorImpl;
+    type Error: 'i;
 
     /// This function can return an "Err" pseudo-element in order to support CSS2.1
     /// pseudo-elements.
-    fn parse_non_ts_pseudo_class(&self, _name: Cow<str>)
-                                 -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ()> {
-        Err(())
+    fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
+                                 -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass,
+                                           ParseError<'i, SelectorParseError<'i, Self::Error>>> {
+        Err(ParseError::Custom(SelectorParseError::UnexpectedIdent(name)))
     }
 
-    fn parse_non_ts_functional_pseudo_class
-        (&self, _name: Cow<str>, _arguments: &mut CssParser)
-        -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ()>
+    fn parse_non_ts_functional_pseudo_class<'t>
+        (&self, name: Cow<'i, str>, _arguments: &mut CssParser<'i, 't>)
+         -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass,
+                   ParseError<'i, SelectorParseError<'i, Self::Error>>>
     {
-        Err(())
+        Err(ParseError::Custom(SelectorParseError::UnexpectedIdent(name)))
     }
 
-    fn parse_pseudo_element(&self, _name: Cow<str>)
-                            -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ()> {
-        Err(())
+    fn parse_pseudo_element(&self, name: Cow<'i, str>)
+                            -> Result<<Self::Impl as SelectorImpl>::PseudoElement,
+                                      ParseError<'i, SelectorParseError<'i, Self::Error>>> {
+        Err(ParseError::Custom(SelectorParseError::UnexpectedIdent(name)))
     }
 
     fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
         None
     }
 
     fn namespace_for_prefix(&self, _prefix: &<Self::Impl as SelectorImpl>::NamespacePrefix)
                             -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> {
@@ -152,18 +181,19 @@ impl<Impl: SelectorImpl> SelectorAndHash
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub struct SelectorList<Impl: SelectorImpl>(pub Vec<SelectorAndHashes<Impl>>);
 
 impl<Impl: SelectorImpl> SelectorList<Impl> {
     /// Parse a comma-separated list of Selectors.
     /// https://drafts.csswg.org/selectors/#grouping
     ///
     /// Return the Selectors or Err if there is an invalid selector.
-    pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()>
-    where P: Parser<Impl=Impl> {
+    pub fn parse<'i, 't, P, E>(parser: &P, input: &mut CssParser<'i, 't>)
+                               -> Result<Self, ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E> {
         input.parse_comma_separated(|input| parse_selector(parser, input).map(SelectorAndHashes::new))
              .map(SelectorList)
     }
 
     /// Creates a SelectorList from a Vec of selectors. Used in tests.
     pub fn from_vec(v: Vec<Selector<Impl>>) -> Self {
         SelectorList(v.into_iter().map(SelectorAndHashes::new).collect())
     }
@@ -955,21 +985,21 @@ fn complex_selector_specificity<Impl>(mu
 /// Components are large enough that we don't have much cache locality benefit
 /// from reserving stack space for fewer of them.
 type ParseVec<Impl> = SmallVec<[Component<Impl>; 32]>;
 
 /// Build up a Selector.
 /// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
 ///
 /// `Err` means invalid selector.
-fn parse_selector<P, Impl>(
+fn parse_selector<'i, 't, P, E, Impl>(
         parser: &P,
-        input: &mut CssParser)
-        -> Result<Selector<Impl>, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+        input: &mut CssParser<'i, 't>)
+        -> Result<Selector<Impl>, ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     let mut sequence = ParseVec::new();
     let mut parsed_pseudo_element;
     'outer_loop: loop {
         // Parse a sequence of simple selectors.
         parsed_pseudo_element =
             parse_compound_selector(parser, input, &mut sequence,
                                     /* inside_negation = */ false)?;
@@ -978,17 +1008,17 @@ fn parse_selector<P, Impl>(
         }
 
         // Parse a combinator.
         let combinator;
         let mut any_whitespace = false;
         loop {
             let position = input.position();
             match input.next_including_whitespace() {
-                Err(()) => break 'outer_loop,
+                Err(_e) => break 'outer_loop,
                 Ok(Token::WhiteSpace(_)) => any_whitespace = true,
                 Ok(Token::Delim('>')) => {
                     combinator = Combinator::Child;
                     break
                 }
                 Ok(Token::Delim('+')) => {
                     combinator = Combinator::NextSibling;
                     break
@@ -1021,33 +1051,35 @@ fn parse_selector<P, Impl>(
 
     let header = HeaderWithLength::new(spec, sequence.len());
     let complex = Selector(Arc::into_thin(Arc::from_header_and_iter(header, sequence.into_iter())));
     Ok(complex)
 }
 
 impl<Impl: SelectorImpl> Selector<Impl> {
     /// Parse a selector, without any pseudo-element.
-    pub fn parse<P>(parser: &P, input: &mut CssParser) -> Result<Self, ()>
-        where P: Parser<Impl=Impl>
+    pub fn parse<'i, 't, P, E>(parser: &P, input: &mut CssParser<'i, 't>)
+                               -> Result<Self, ParseError<'i, SelectorParseError<'i, E>>>
+        where P: Parser<'i, Impl=Impl, Error=E>
     {
         let selector = parse_selector(parser, input)?;
         if selector.has_pseudo_element() {
-            return Err(())
+            return Err(ParseError::Custom(SelectorParseError::PseudoElementInComplexSelector))
         }
         Ok(selector)
     }
 }
 
 /// * `Err(())`: Invalid selector, abort
 /// * `Ok(None)`: Not a type selector, could be something else. `input` was not consumed.
 /// * `Ok(Some(vec))`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
-fn parse_type_selector<P, Impl>(parser: &P, input: &mut CssParser, sequence: &mut ParseVec<Impl>)
-                       -> Result<bool, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+fn parse_type_selector<'i, 't, P, E, Impl>(parser: &P, input: &mut CssParser<'i, 't>,
+                                           sequence: &mut ParseVec<Impl>)
+                                           -> Result<bool, ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     match parse_qualified_name(parser, input, /* in_attr_selector = */ false)? {
         None => Ok(false),
         Some((namespace, local_name)) => {
             match namespace {
                 QNamePrefix::ImplicitAnyNamespace => {}
                 QNamePrefix::ImplicitDefaultNamespace(url) => {
                     sequence.push(Component::DefaultNamespace(url))
@@ -1095,21 +1127,22 @@ enum QNamePrefix<Impl: SelectorImpl> {
     ExplicitNoNamespace,  // `|foo`
     ExplicitAnyNamespace,  // `*|foo`
     ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl),  // `prefix|foo`
 }
 
 /// * `Err(())`: Invalid selector, abort
 /// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed.
 /// * `Ok(Some((namespace, local_name)))`: `None` for the local name means a `*` universal selector
-fn parse_qualified_name<'i, 't, P, Impl>
+fn parse_qualified_name<'i, 't, P, E, Impl>
                        (parser: &P, input: &mut CssParser<'i, 't>,
                         in_attr_selector: bool)
-                        -> Result<Option<(QNamePrefix<Impl>, Option<Cow<'i, str>>)>, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+                        -> Result<Option<(QNamePrefix<Impl>, Option<Cow<'i, str>>)>,
+                                  ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     let default_namespace = |local_name| {
         let namespace = match parser.default_namespace() {
             Some(url) => QNamePrefix::ImplicitDefaultNamespace(url),
             None => QNamePrefix::ImplicitAnyNamespace,
         };
         Ok(Some((namespace, local_name)))
     };
@@ -1117,29 +1150,30 @@ fn parse_qualified_name<'i, 't, P, Impl>
     let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| {
         match input.next_including_whitespace() {
             Ok(Token::Delim('*')) if !in_attr_selector => {
                 Ok(Some((namespace, None)))
             },
             Ok(Token::Ident(local_name)) => {
                 Ok(Some((namespace, Some(local_name))))
             },
-            _ => Err(()),
+            Ok(t) => Err(ParseError::Basic(BasicParseError::UnexpectedToken(t))),
+            Err(e) => Err(ParseError::Basic(e)),
         }
     };
 
     let position = input.position();
     match input.next_including_whitespace() {
         Ok(Token::Ident(value)) => {
             let position = input.position();
             match input.next_including_whitespace() {
                 Ok(Token::Delim('|')) => {
                     let prefix = from_cow_str(value);
                     let result = parser.namespace_for_prefix(&prefix);
-                    let url = result.ok_or(())?;
+                    let url = result.ok_or(ParseError::Custom(SelectorParseError::ExpectedNamespace))?;
                     explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url))
                 },
                 _ => {
                     input.reset(position);
                     if in_attr_selector {
                         Ok(Some((QNamePrefix::ImplicitNoNamespace, Some(value))))
                     } else {
                         default_namespace(Some(value))
@@ -1148,20 +1182,23 @@ fn parse_qualified_name<'i, 't, P, Impl>
             }
         },
         Ok(Token::Delim('*')) => {
             let position = input.position();
             match input.next_including_whitespace() {
                 Ok(Token::Delim('|')) => {
                     explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace)
                 }
-                _ => {
+                result => {
                     input.reset(position);
                     if in_attr_selector {
-                        Err(())
+                        match result {
+                            Ok(t) => Err(ParseError::Basic(BasicParseError::UnexpectedToken(t))),
+                             Err(e) => Err(ParseError::Basic(e)),
+                        }
                     } else {
                         default_namespace(None)
                     }
                 },
             }
         },
         Ok(Token::Delim('|')) => {
             explicit_namespace(input, QNamePrefix::ExplicitNoNamespace)
@@ -1169,24 +1206,25 @@ fn parse_qualified_name<'i, 't, P, Impl>
         _ => {
             input.reset(position);
             Ok(None)
         }
     }
 }
 
 
-fn parse_attribute_selector<P, Impl>(parser: &P, input: &mut CssParser)
-                                     -> Result<Component<Impl>, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+fn parse_attribute_selector<'i, 't, P, E, Impl>(parser: &P, input: &mut CssParser<'i, 't>)
+                                                -> Result<Component<Impl>,
+                                                          ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     let namespace;
     let local_name;
     match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {
-        None => return Err(()),
+        None => return Err(ParseError::Custom(SelectorParseError::NoQualifiedNameInAttributeSelector)),
         Some((_, None)) => unreachable!(),
         Some((ns, Some(ln))) => {
             local_name = ln;
             namespace = match ns {
                 QNamePrefix::ImplicitNoNamespace |
                 QNamePrefix::ExplicitNoNamespace => {
                     None
                 }
@@ -1204,17 +1242,17 @@ fn parse_attribute_selector<P, Impl>(par
         }
     }
 
     let operator;
     let value;
     let never_matches;
     match input.next() {
         // [foo]
-        Err(()) => {
+        Err(_) => {
             let local_name_lower = from_cow_str(to_ascii_lowercase(&local_name));
             let local_name = from_cow_str(local_name);
             if let Some(namespace) = namespace {
                 return Ok(Component::AttributeOther(Box::new(AttrSelectorWithNamespace {
                     namespace: namespace,
                     local_name: local_name,
                     local_name_lower: local_name_lower,
                     operation: ParsedAttrSelectorOperation::Exists,
@@ -1259,17 +1297,17 @@ fn parse_attribute_selector<P, Impl>(par
             operator = AttrSelectorOperator::Substring;
         }
         // [foo$=bar]
         Ok(Token::SuffixMatch) => {
             value = input.expect_ident_or_string()?;
             never_matches = value.is_empty();
             operator = AttrSelectorOperator::Suffix;
         }
-        _ => return Err(())
+        _ => return Err(SelectorParseError::UnexpectedTokenInAttributeSelector.into())
     }
 
     let mut case_sensitivity = parse_attribute_flags(input)?;
 
     let value = from_cow_str(value);
     let local_name_lower;
     {
         let local_name_lower_cow = to_ascii_lowercase(&local_name);
@@ -1305,41 +1343,44 @@ fn parse_attribute_selector<P, Impl>(par
             value: value,
             case_sensitivity: case_sensitivity,
             never_matches: never_matches,
         })
     }
 }
 
 
-fn parse_attribute_flags(input: &mut CssParser) -> Result<ParsedCaseSensitivity, ()> {
+fn parse_attribute_flags<'i, 't, E>(input: &mut CssParser<'i, 't>)
+                                    -> Result<ParsedCaseSensitivity,
+                                              ParseError<'i, SelectorParseError<'i, E>>> {
     match input.next() {
-        Err(()) => Ok(ParsedCaseSensitivity::CaseSensitive),
+        Err(_) => Ok(ParsedCaseSensitivity::CaseSensitive),
         Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => {
             Ok(ParsedCaseSensitivity::AsciiCaseInsensitive)
         }
-        _ => Err(())
+        Ok(t) => Err(ParseError::Basic(BasicParseError::UnexpectedToken(t)))
     }
 }
 
 
 /// Level 3: Parse **one** simple_selector.  (Though we might insert a second
 /// implied "<defaultns>|*" type selector.)
-fn parse_negation<P, Impl>(parser: &P,
-                           input: &mut CssParser)
-                           -> Result<Component<Impl>, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+fn parse_negation<'i, 't, P, E, Impl>(parser: &P,
+                                      input: &mut CssParser<'i, 't>)
+                                      -> Result<Component<Impl>,
+                                                ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     let mut v = ParseVec::new();
     parse_compound_selector(parser, input, &mut v, /* inside_negation = */ true)?;
 
     if single_simple_selector(&v) {
         Ok(Component::Negation(v.into_vec().into_boxed_slice()))
     } else {
-        Err(())
+        Err(ParseError::Custom(SelectorParseError::NonSimpleSelectorInNegation))
     }
 }
 
 // A single type selector can be represented as two components
 fn single_simple_selector<Impl: SelectorImpl>(v: &[Component<Impl>]) -> bool {
     v.len() == 1 || (
         v.len() == 2 &&
         match v[1] {
@@ -1360,23 +1401,23 @@ fn single_simple_selector<Impl: Selector
 
 /// simple_selector_sequence
 /// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
 /// | [ HASH | class | attrib | pseudo | negation ]+
 ///
 /// `Err(())` means invalid selector.
 ///
 /// The boolean represent whether a pseudo-element has been parsed.
-fn parse_compound_selector<P, Impl>(
+fn parse_compound_selector<'i, 't, P, E, Impl>(
     parser: &P,
-    input: &mut CssParser,
+    input: &mut CssParser<'i, 't>,
     mut sequence: &mut ParseVec<Impl>,
     inside_negation: bool)
-    -> Result<bool, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+    -> Result<bool, ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     // Consume any leading whitespace.
     loop {
         let position = input.position();
         if !matches!(input.next_including_whitespace(), Ok(Token::WhiteSpace(_))) {
             input.reset(position);
             break
         }
@@ -1407,31 +1448,31 @@ fn parse_compound_selector<P, Impl>(
             }
             Some(SimpleSelectorParseResult::PseudoElement(p)) => {
                 // Try to parse state to its right.
                 let mut state_selectors = ParseVec::new();
 
                 loop {
                     match input.next_including_whitespace() {
                         Ok(Token::Colon) => {},
-                        Ok(Token::WhiteSpace(_)) | Err(()) => break,
-                        _ => return Err(()),
+                        Ok(Token::WhiteSpace(_)) | Err(_) => break,
+                        _ => return Err(SelectorParseError::PseudoElementExpectedColon.into()),
                     }
 
                     // TODO(emilio): Functional pseudo-classes too?
                     // We don't need it for now.
                     let name = match input.next_including_whitespace() {
                         Ok(Token::Ident(name)) => name,
-                        _ => return Err(()),
+                        _ => return Err(SelectorParseError::PseudoElementExpectedIdent.into()),
                     };
 
                     let pseudo_class =
                         P::parse_non_ts_pseudo_class(parser, name)?;
                     if !p.supports_pseudo_class(&pseudo_class) {
-                        return Err(());
+                        return Err(SelectorParseError::UnsupportedPseudoClass.into());
                     }
                     state_selectors.push(Component::NonTSPseudoClass(pseudo_class));
                 }
 
                 if !sequence.is_empty() {
                     sequence.push(Component::Combinator(Combinator::PseudoElement));
                 }
 
@@ -1443,79 +1484,83 @@ fn parse_compound_selector<P, Impl>(
                 pseudo = true;
                 empty = false;
                 break
             }
         }
     }
     if empty {
         // An empty selector is invalid.
-        Err(())
+        Err(ParseError::Custom(SelectorParseError::EmptySelector))
     } else {
         Ok(pseudo)
     }
 }
 
-fn parse_functional_pseudo_class<P, Impl>(parser: &P,
-                                          input: &mut CssParser,
-                                          name: Cow<str>,
-                                          inside_negation: bool)
-                                          -> Result<Component<Impl>, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+fn parse_functional_pseudo_class<'i, 't, P, E, Impl>(parser: &P,
+                                                     input: &mut CssParser<'i, 't>,
+                                                     name: Cow<'i, str>,
+                                                     inside_negation: bool)
+                                                     -> Result<Component<Impl>,
+                                                               ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     match_ignore_ascii_case! { &name,
         "nth-child" => return parse_nth_pseudo_class(input, Component::NthChild),
         "nth-of-type" => return parse_nth_pseudo_class(input, Component::NthOfType),
         "nth-last-child" => return parse_nth_pseudo_class(input, Component::NthLastChild),
         "nth-last-of-type" => return parse_nth_pseudo_class(input, Component::NthLastOfType),
         "not" => {
             if inside_negation {
-                return Err(())
+                return Err(ParseError::Custom(SelectorParseError::UnexpectedIdent("not".into())));
             }
             return parse_negation(parser, input)
         },
         _ => {}
     }
     P::parse_non_ts_functional_pseudo_class(parser, name, input)
         .map(Component::NonTSPseudoClass)
 }
 
 
-fn parse_nth_pseudo_class<Impl, F>(input: &mut CssParser, selector: F)
-                                   -> Result<Component<Impl>, ()>
+fn parse_nth_pseudo_class<'i, 't, Impl, F, E>(input: &mut CssParser<'i, 't>, selector: F)
+                                              -> Result<Component<Impl>,
+                                                        ParseError<'i, SelectorParseError<'i, E>>>
 where Impl: SelectorImpl, F: FnOnce(i32, i32) -> Component<Impl> {
     let (a, b) = parse_nth(input)?;
     Ok(selector(a, b))
 }
 
 
 /// Parse a simple selector other than a type selector.
 ///
 /// * `Err(())`: Invalid selector, abort
 /// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed.
 /// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element
-fn parse_one_simple_selector<P, Impl>(parser: &P,
-                                      input: &mut CssParser,
-                                      inside_negation: bool)
-                                      -> Result<Option<SimpleSelectorParseResult<Impl>>, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+fn parse_one_simple_selector<'i, 't, P, E, Impl>(parser: &P,
+                                                 input: &mut CssParser<'i, 't>,
+                                                 inside_negation: bool)
+                                                 -> Result<Option<SimpleSelectorParseResult<Impl>>,
+                                                           ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     let start_position = input.position();
     match input.next_including_whitespace() {
         Ok(Token::IDHash(id)) => {
             let id = Component::ID(from_cow_str(id));
             Ok(Some(SimpleSelectorParseResult::SimpleSelector(id)))
         }
         Ok(Token::Delim('.')) => {
             match input.next_including_whitespace() {
                 Ok(Token::Ident(class)) => {
                     let class = Component::Class(from_cow_str(class));
                     Ok(Some(SimpleSelectorParseResult::SimpleSelector(class)))
                 }
-                _ => Err(()),
+                Ok(t) => Err(ParseError::Basic(BasicParseError::UnexpectedToken(t))),
+                Err(e) => Err(ParseError::Basic(e)),
             }
         }
         Ok(Token::SquareBracketBlock) => {
             let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?;
             Ok(Some(SimpleSelectorParseResult::SimpleSelector(attr)))
         }
         Ok(Token::Colon) => {
             match input.next_including_whitespace() {
@@ -1540,31 +1585,35 @@ fn parse_one_simple_selector<P, Impl>(pa
                     Ok(Some(SimpleSelectorParseResult::SimpleSelector(pseudo)))
                 }
                 Ok(Token::Colon) => {
                     match input.next_including_whitespace() {
                         Ok(Token::Ident(name)) => {
                             let pseudo = P::parse_pseudo_element(parser, name)?;
                             Ok(Some(SimpleSelectorParseResult::PseudoElement(pseudo)))
                         }
-                        _ => Err(())
+                        Ok(t) => Err(ParseError::Basic(BasicParseError::UnexpectedToken(t))),
+                        Err(e) => Err(ParseError::Basic(e)),
                     }
                 }
-                _ => Err(())
+                Ok(t) => Err(ParseError::Basic(BasicParseError::UnexpectedToken(t))),
+                Err(e) => Err(ParseError::Basic(e)),
             }
         }
         _ => {
             input.reset(start_position);
             Ok(None)
         }
     }
 }
 
-fn parse_simple_pseudo_class<P, Impl>(parser: &P, name: Cow<str>) -> Result<Component<Impl>, ()>
-    where P: Parser<Impl=Impl>, Impl: SelectorImpl
+fn parse_simple_pseudo_class<'i, P, E, Impl>(parser: &P, name: Cow<'i, str>)
+                                             -> Result<Component<Impl>,
+                                                       ParseError<'i, SelectorParseError<'i, E>>>
+    where P: Parser<'i, Impl=Impl, Error=E>, Impl: SelectorImpl
 {
     (match_ignore_ascii_case! { &name,
         "first-child" => Ok(Component::FirstChild),
         "last-child"  => Ok(Component::LastChild),
         "only-child"  => Ok(Component::OnlyChild),
         "root" => Ok(Component::Root),
         "empty" => Ok(Component::Empty),
         "first-of-type" => Ok(Component::FirstOfType),
@@ -1575,17 +1624,17 @@ fn parse_simple_pseudo_class<P, Impl>(pa
         P::parse_non_ts_pseudo_class(parser, name)
             .map(Component::NonTSPseudoClass)
     })
 }
 
 // NB: pub module in order to access the DummyParser
 #[cfg(test)]
 pub mod tests {
-    use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
+    use cssparser::{Parser as CssParser, ToCss, serialize_identifier, ParserInput};
     use parser;
     use std::borrow::Cow;
     use std::collections::HashMap;
     use std::fmt;
     use super::*;
 
     #[derive(PartialEq, Clone, Debug, Eq)]
     pub enum PseudoClass {
@@ -1686,86 +1735,95 @@ pub mod tests {
     }
 
     impl PrecomputedHash for DummyAtom {
         fn precomputed_hash(&self) -> u32 {
             return 0
         }
     }
 
-    impl Parser for DummyParser {
+    impl<'i> Parser<'i> for DummyParser {
         type Impl = DummySelectorImpl;
+        type Error = ();
 
-        fn parse_non_ts_pseudo_class(&self, name: Cow<str>)
-                                     -> Result<PseudoClass, ()> {
+        fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
+                                     -> Result<PseudoClass,
+                                               ParseError<'i, SelectorParseError<'i, ()>>> {
             match_ignore_ascii_case! { &name,
                 "hover" => Ok(PseudoClass::Hover),
                 "active" => Ok(PseudoClass::Active),
-                _ => Err(())
+                _ => Err(SelectorParseError::Custom(()).into())
             }
         }
 
-        fn parse_non_ts_functional_pseudo_class(&self, name: Cow<str>,
-                                                parser: &mut CssParser)
-                                                -> Result<PseudoClass, ()> {
+        fn parse_non_ts_functional_pseudo_class<'t>(&self, name: Cow<'i, str>,
+                                                    parser: &mut CssParser<'i, 't>)
+                                                    -> Result<PseudoClass,
+                                                              ParseError<'i, SelectorParseError<'i, ()>>> {
             match_ignore_ascii_case! { &name,
                 "lang" => Ok(PseudoClass::Lang(try!(parser.expect_ident_or_string()).into_owned())),
-                _ => Err(())
+                _ => Err(SelectorParseError::Custom(()).into())
             }
         }
 
-        fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
+        fn parse_pseudo_element(&self, name: Cow<'i, str>)
+                                -> Result<PseudoElement,
+                                          ParseError<'i, SelectorParseError<'i, ()>>> {
             match_ignore_ascii_case! { &name,
                 "before" => Ok(PseudoElement::Before),
                 "after" => Ok(PseudoElement::After),
-                _ => Err(())
+                _ => Err(SelectorParseError::Custom(()).into())
             }
         }
 
         fn default_namespace(&self) -> Option<DummyAtom> {
             self.default_ns.clone()
         }
 
         fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option<DummyAtom> {
             self.ns_prefixes.get(prefix).cloned()
         }
     }
 
-    fn parse(input: &str) -> Result<SelectorList<DummySelectorImpl>, ()> {
+    fn parse<'i>(input: &'i str) -> Result<SelectorList<DummySelectorImpl>,
+                                           ParseError<'i, SelectorParseError<'i, ()>>> {
         parse_ns(input, &DummyParser::default())
     }
 
-    fn parse_ns(input: &str, parser: &DummyParser)
-                -> Result<SelectorList<DummySelectorImpl>, ()> {
-        let result = SelectorList::parse(parser, &mut CssParser::new(input));
+    fn parse_ns<'i>(input: &'i str, parser: &DummyParser)
+                    -> Result<SelectorList<DummySelectorImpl>,
+                              ParseError<'i, SelectorParseError<'i, ()>>> {
+        let mut parser_input = ParserInput::new(input);
+        let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input));
         if let Ok(ref selectors) = result {
             assert_eq!(selectors.0.len(), 1);
             assert_eq!(selectors.0[0].selector.to_css_string(), input);
         }
         result
     }
 
     fn specificity(a: u32, b: u32, c: u32) -> u32 {
         a << 20 | b << 10 | c
     }
 
     #[test]
     fn test_empty() {
-        let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(":empty"));
+        let mut input = ParserInput::new(":empty");
+        let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(&mut input));
         assert!(list.is_ok());
     }
 
     const MATHML: &'static str = "http://www.w3.org/1998/Math/MathML";
     const SVG: &'static str = "http://www.w3.org/2000/svg";
 
     #[test]
     fn test_parsing() {
-        assert_eq!(parse(""), Err(())) ;
-        assert_eq!(parse(":lang(4)"), Err(())) ;
-        assert_eq!(parse(":lang(en US)"), Err(())) ;
+        assert!(parse("").is_err()) ;
+        assert!(parse(":lang(4)").is_err()) ;
+        assert!(parse(":lang(en US)").is_err()) ;
         assert_eq!(parse("EeÉ"), Ok(SelectorList::from_vec(vec!(
             Selector::from_vec(vec!(
                 Component::LocalName(LocalName {
                     name: DummyAtom::from("EeÉ"),
                     lower_name: DummyAtom::from("eeÉ") })
             ), specificity(0, 0, 1))
         ))));
         assert_eq!(parse("|e"), Ok(SelectorList::from_vec(vec!(
@@ -1841,17 +1899,17 @@ pub mod tests {
         assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList::from_vec(vec!(
             Selector::from_vec(vec!(
                 Component::AttributeInNoNamespaceExists {
                     local_name: DummyAtom::from("Foo"),
                     local_name_lower: DummyAtom::from("foo"),
                 }
             ), specificity(0, 1, 0))
         ))));
-        assert_eq!(parse_ns("svg|circle", &parser), Err(()));
+        assert!(parse_ns("svg|circle", &parser).is_err());
         parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into()));
         assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList::from_vec(vec!(
             Selector::from_vec(vec!(
                 Component::Namespace(DummyAtom("svg".into()), SVG.into()),
                 Component::LocalName(LocalName {
                     name: DummyAtom::from("circle"),
                     lower_name: DummyAtom::from("circle"),
                 })
@@ -1955,24 +2013,24 @@ pub mod tests {
         ))));
         assert_eq!(parse("::before:hover:hover"), Ok(SelectorList::from_vec(vec!(
             Selector::from_vec(vec!(
                 Component::PseudoElement(PseudoElement::Before),
                 Component::NonTSPseudoClass(PseudoClass::Hover),
                 Component::NonTSPseudoClass(PseudoClass::Hover),
             ), specificity(0, 2, 1) | HAS_PSEUDO_BIT)
         ))));
-        assert_eq!(parse("::before:hover:active"), Err(()));
-        assert_eq!(parse("::before:hover .foo"), Err(()));
-        assert_eq!(parse("::before .foo"), Err(()));
-        assert_eq!(parse("::before ~ bar"), Err(()));
-        assert_eq!(parse("::before:active"), Err(()));
+        assert!(parse("::before:hover:active").is_err());
+        assert!(parse("::before:hover .foo").is_err());
+        assert!(parse("::before .foo").is_err());
+        assert!(parse("::before ~ bar").is_err());
+        assert!(parse("::before:active").is_err());
 
         // https://github.com/servo/servo/issues/15335
-        assert_eq!(parse(":: before"), Err(()));
+        assert!(parse(":: before").is_err());
         assert_eq!(parse("div ::after"), Ok(SelectorList::from_vec(vec!(
             Selector::from_vec(vec!(
                  Component::LocalName(LocalName {
                     name: DummyAtom::from("div"),
                     lower_name: DummyAtom::from("div") }),
                 Component::Combinator(Combinator::Descendant),
                 Component::Combinator(Combinator::PseudoElement),
                 Component::PseudoElement(PseudoElement::After),
@@ -1981,18 +2039,18 @@ pub mod tests {
         assert_eq!(parse("#d1 > .ok"), Ok(SelectorList::from_vec(vec!(
             Selector::from_vec(vec!(
                 Component::ID(DummyAtom::from("d1")),
                 Component::Combinator(Combinator::Child),
                 Component::Class(DummyAtom::from("ok")),
             ), (1 << 20) + (1 << 10) + (0 << 0))
         ))));
         parser.default_ns = None;
-        assert_eq!(parse(":not(#provel.old)"), Err(()));
-        assert_eq!(parse(":not(#provel > old)"), Err(()));
+        assert!(parse(":not(#provel.old)").is_err());
+        assert!(parse(":not(#provel > old)").is_err());
         assert!(parse("table[rules]:not([rules = \"none\"]):not([rules = \"\"])").is_ok());
         assert_eq!(parse(":not(#provel)"), Ok(SelectorList::from_vec(vec!(
             Selector::from_vec(vec!(Component::Negation(vec!(
                     Component::ID(DummyAtom::from("provel")),
                 ).into_boxed_slice()
             )), specificity(1, 0, 0))
         ))));
         assert_eq!(parse_ns(":not(svg|circle)", &parser), Ok(SelectorList::from_vec(vec!(
--- a/servo/components/style/Cargo.toml
+++ b/servo/components/style/Cargo.toml
@@ -33,17 +33,17 @@ gecko_debug = ["nsstring_vendor/gecko_de
 app_units = "0.4.1"
 arrayvec = "0.3.20"
 arraydeque = "0.2.3"
 atomic_refcell = "0.1"
 bitflags = "0.7"
 bit-vec = "0.4.3"
 byteorder = "1.0"
 cfg-if = "0.1.0"
-cssparser = "0.13.7"
+cssparser = "0.14.0"
 encoding = {version = "0.2", optional = true}
 euclid = "0.13"
 fnv = "1.0"
 heapsize = {version = "0.4", optional = true}
 heapsize_derive = {version = "0.1", optional = true}
 itoa = "0.3"
 html5ever = {version = "0.17", optional = true}
 lazy_static = "0.2"
--- a/servo/components/style/counter_style/mod.rs
+++ b/servo/components/style/counter_style/mod.rs
@@ -3,30 +3,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! The [`@counter-style`][counter-style] at-rule.
 //!
 //! [counter-style]: https://drafts.csswg.org/css-counter-styles/
 
 use Atom;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser};
-use cssparser::{Parser, Token, serialize_identifier};
+use cssparser::{Parser, Token, serialize_identifier, BasicParseError};
+use error_reporting::ContextualParseError;
 #[cfg(feature = "gecko")] use gecko::rules::CounterStyleDescriptors;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSCounterDesc;
 use parser::{ParserContext, log_css_error, Parse};
+use selectors::parser::SelectorParseError;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
 use std::ops::Range;
-use style_traits::{ToCss, OneOrMoreCommaSeparated};
+use style_traits::{ToCss, OneOrMoreCommaSeparated, ParseError, StyleParseError};
 use values::CustomIdent;
 
 /// Parse the prelude of an @counter-style rule
-pub fn parse_counter_style_name(input: &mut Parser) -> Result<CustomIdent, ()> {
+pub fn parse_counter_style_name<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CustomIdent, ParseError<'i>> {
     macro_rules! predefined {
         ($($name: expr,)+) => {
             {
                 ascii_case_insensitive_phf_map! {
                     // FIXME: use static atoms https://github.com/rust-lang/rust/issues/33156
                     predefined -> &'static str = {
                         $(
                             $name => $name,
@@ -43,83 +45,80 @@ pub fn parse_counter_style_name(input: &
                 }
             }
         }
     }
     include!("predefined.rs")
 }
 
 /// Parse the body (inside `{}`) of an @counter-style rule
-pub fn parse_counter_style_body(name: CustomIdent, context: &ParserContext, input: &mut Parser)
-                            -> Result<CounterStyleRuleData, ()> {
+pub fn parse_counter_style_body<'i, 't>(name: CustomIdent, context: &ParserContext, input: &mut Parser<'i, 't>)
+                                        -> Result<CounterStyleRuleData, ParseError<'i>> {
     let start = input.position();
     let mut rule = CounterStyleRuleData::empty(name);
     {
         let parser = CounterStyleRuleParser {
             context: context,
             rule: &mut rule,
         };
         let mut iter = DeclarationListParser::new(input, parser);
         while let Some(declaration) = iter.next() {
-            if let Err(range) = declaration {
-                let pos = range.start;
-                let message = format!("Unsupported @counter-style descriptor declaration: '{}'",
-                                      iter.input.slice(range));
-                log_css_error(iter.input, pos, &*message, context);
+            if let Err(err) = declaration {
+                let pos = err.span.start;
+                let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(
+                    iter.input.slice(err.span), err.error);
+                log_css_error(iter.input, pos, error, context);
             }
         }
     }
     let error = match *rule.system() {
         ref system @ System::Cyclic |
         ref system @ System::Fixed { .. } |
         ref system @ System::Symbolic |
         ref system @ System::Alphabetic |
         ref system @ System::Numeric
         if rule.symbols.is_none() => {
             let system = system.to_css_string();
-            Some(format!("Invalid @counter-style rule: 'system: {}' without 'symbols'", system))
+            Some(ContextualParseError::InvalidCounterStyleWithoutSymbols(system))
         }
         ref system @ System::Alphabetic |
         ref system @ System::Numeric
         if rule.symbols().unwrap().0.len() < 2 => {
             let system = system.to_css_string();
-            Some(format!("Invalid @counter-style rule: 'system: {}' less than two 'symbols'",
-                         system))
+            Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols(system))
         }
         System::Additive if rule.additive_symbols.is_none() => {
-            let s = "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'";
-            Some(s.to_owned())
+            Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols)
         }
         System::Extends(_) if rule.symbols.is_some() => {
-            let s = "Invalid @counter-style rule: 'system: extends …' with 'symbols'";
-            Some(s.to_owned())
+            Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols)
         }
         System::Extends(_) if rule.additive_symbols.is_some() => {
-            let s = "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'";
-            Some(s.to_owned())
+            Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols)
         }
         _ => None
     };
-    if let Some(message) = error {
-        log_css_error(input, start, &message, context);
-        Err(())
+    if let Some(error) = error {
+        log_css_error(input, start, error, context);
+        Err(StyleParseError::UnspecifiedError.into())
     } else {
         Ok(rule)
     }
 }
 
 struct CounterStyleRuleParser<'a, 'b: 'a> {
     context: &'a ParserContext<'b>,
     rule: &'a mut CounterStyleRuleData,
 }
 
 /// Default methods reject all at rules.
-impl<'a, 'b> AtRuleParser for CounterStyleRuleParser<'a, 'b> {
+impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> {
     type Prelude = ();
     type AtRule = ();
+    type Error = SelectorParseError<'i, StyleParseError<'i>>;
 }
 
 macro_rules! accessor {
     (#[$doc: meta] $name: tt $ident: ident: $ty: ty = !) => {
         #[$doc]
         pub fn $ident(&self) -> Option<&$ty> {
             self.$ident.as_ref()
         }
@@ -176,32 +175,34 @@ macro_rules! counter_style_descriptors {
                 $(
                     if let Some(value) = self.$ident {
                         descriptors[nsCSSCounterDesc::$gecko_ident as usize].set_from(value)
                     }
                 )*
             }
         }
 
-       impl<'a, 'b> DeclarationParser for CounterStyleRuleParser<'a, 'b> {
+        impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> {
             type Declaration = ();
+            type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
-            fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> {
-                match_ignore_ascii_case! { name,
+            fn parse_value<'t>(&mut self, name: Cow<'i, str>, input: &mut Parser<'i, 't>)
+                               -> Result<(), ParseError<'i>> {
+                match_ignore_ascii_case! { &*name,
                     $(
                         $name => {
                             // DeclarationParser also calls parse_entirely
                             // so we’d normally not need to,
                             // but in this case we do because we set the value as a side effect
                             // rather than returning it.
                             let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
                             self.rule.$ident = Some(value)
                         }
                     )*
-                    _ => return Err(())
+                    _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 Ok(())
             }
         }
 
         impl ToCssWithGuard for CounterStyleRuleData {
             fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
             where W: fmt::Write {
@@ -288,33 +289,34 @@ pub enum System {
         /// '<integer>?'
         first_symbol_value: Option<i32>
     },
     /// 'extends <counter-style-name>'
     Extends(CustomIdent),
 }
 
 impl Parse for System {
-    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        match_ignore_ascii_case! { &input.expect_ident()?,
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+        let ident = input.expect_ident()?;
+        (match_ignore_ascii_case! { &ident,
             "cyclic" => Ok(System::Cyclic),
             "numeric" => Ok(System::Numeric),
             "alphabetic" => Ok(System::Alphabetic),
             "symbolic" => Ok(System::Symbolic),
             "additive" => Ok(System::Additive),
             "fixed" => {
                 let first_symbol_value = input.try(|i| i.expect_integer()).ok();
                 Ok(System::Fixed { first_symbol_value: first_symbol_value })
             }
             "extends" => {
                 let other = parse_counter_style_name(input)?;
                 Ok(System::Extends(other))
             }
             _ => Err(())
-        }
+        }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
     }
 }
 
 impl ToCss for System {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
             System::Cyclic => dest.write_str("cyclic"),
             System::Numeric => dest.write_str("numeric"),
@@ -344,21 +346,22 @@ pub enum Symbol {
     /// <ident>
     Ident(String),
     // Not implemented:
     // /// <image>
     // Image(Image),
 }
 
 impl Parse for Symbol {
-    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         match input.next() {
             Ok(Token::QuotedString(s)) => Ok(Symbol::String(s.into_owned())),
             Ok(Token::Ident(s)) => Ok(Symbol::Ident(s.into_owned())),
-            _ => Err(())
+            Ok(t) => Err(BasicParseError::UnexpectedToken(t).into()),
+            Err(e) => Err(e.into()),
         }
     }
 }
 
 impl ToCss for Symbol {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
             Symbol::String(ref s) => s.to_css(dest),
@@ -378,17 +381,17 @@ impl Symbol {
     }
 }
 
 /// https://drafts.csswg.org/css-counter-styles/#counter-style-negative
 #[derive(Debug, Clone)]
 pub struct Negative(pub Symbol, pub Option<Symbol>);
 
 impl Parse for Negative {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         Ok(Negative(
             Symbol::parse(context, input)?,
             input.try(|input| Symbol::parse(context, input)).ok(),
         ))
     }
 }
 
 impl ToCss for Negative {
@@ -404,39 +407,40 @@ impl ToCss for Negative {
 
 /// https://drafts.csswg.org/css-counter-styles/#counter-style-range
 ///
 /// Empty Vec represents 'auto'
 #[derive(Debug, Clone)]
 pub struct Ranges(pub Vec<Range<Option<i32>>>);
 
 impl Parse for Ranges {
-    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("auto")).is_ok() {
             Ok(Ranges(Vec::new()))
         } else {
             input.parse_comma_separated(|input| {
                 let opt_start = parse_bound(input)?;
                 let opt_end = parse_bound(input)?;
                 if let (Some(start), Some(end)) = (opt_start, opt_end) {
                     if start > end {
-                        return Err(())
+                        return Err(StyleParseError::UnspecifiedError.into())
                     }
                 }
                 Ok(opt_start..opt_end)
             }).map(Ranges)
         }
     }
 }
 
-fn parse_bound(input: &mut Parser) -> Result<Option<i32>, ()> {
+fn parse_bound<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Option<i32>, ParseError<'i>> {
     match input.next() {
         Ok(Token::Number(ref v)) if v.int_value.is_some() => Ok(Some(v.int_value.unwrap())),
         Ok(Token::Ident(ref ident)) if ident.eq_ignore_ascii_case("infinite") => Ok(None),
-        _ => Err(())
+        Ok(t) => Err(BasicParseError::UnexpectedToken(t).into()),
+        Err(e) => Err(e.into()),
     }
 }
 
 impl ToCss for Ranges {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         let mut iter = self.0.iter();
         if let Some(first) = iter.next() {
             range_to_css(first, dest)?;
@@ -466,63 +470,63 @@ fn bound_to_css<W>(range: Option<i32>, d
     }
 }
 
 /// https://drafts.csswg.org/css-counter-styles/#counter-style-pad
 #[derive(Debug, Clone)]
 pub struct Pad(pub u32, pub Symbol);
 
 impl Parse for Pad {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let pad_with = input.try(|input| Symbol::parse(context, input));
         let min_length = input.expect_integer()?;
         if min_length < 0 {
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
-        let pad_with = pad_with.or_else(|()| Symbol::parse(context, input))?;
+        let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?;
         Ok(Pad(min_length as u32, pad_with))
     }
 }
 
 impl ToCss for Pad {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         write!(dest, "{} ", self.0)?;
         self.1.to_css(dest)
     }
 }
 
 /// https://drafts.csswg.org/css-counter-styles/#counter-style-fallback
 #[derive(Debug, Clone)]
 pub struct Fallback(pub CustomIdent);
 
 impl Parse for Fallback {
-    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         parse_counter_style_name(input).map(Fallback)
     }
 }
 
 impl ToCss for Fallback {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         self.0.to_css(dest)
     }
 }
 
 /// https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Symbols(pub Vec<Symbol>);
 
 impl Parse for Symbols {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let mut symbols = Vec::new();
         loop {
             if let Ok(s) = input.try(|input| Symbol::parse(context, input)) {
                 symbols.push(s)
             } else {
                 if symbols.is_empty() {
-                    return Err(())
+                    return Err(StyleParseError::UnspecifiedError.into())
                 } else {
                     return Ok(Symbols(symbols))
                 }
             }
         }
     }
 }
 
@@ -539,21 +543,21 @@ impl ToCss for Symbols {
     }
 }
 
 /// https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols
 #[derive(Debug, Clone)]
 pub struct AdditiveSymbols(pub Vec<AdditiveTuple>);
 
 impl Parse for AdditiveSymbols {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let tuples = Vec::<AdditiveTuple>::parse(context, input)?;
         // FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220
         if tuples.windows(2).any(|window| window[0].weight <= window[1].weight) {
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
         Ok(AdditiveSymbols(tuples))
     }
 }
 
 impl ToCss for AdditiveSymbols {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         self.0.to_css(dest)
@@ -567,23 +571,23 @@ pub struct AdditiveTuple {
     pub weight: u32,
     /// <symbol>
     pub symbol: Symbol,
 }
 
 impl OneOrMoreCommaSeparated for AdditiveTuple {}
 
 impl Parse for AdditiveTuple {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let symbol = input.try(|input| Symbol::parse(context, input));
         let weight = input.expect_integer()?;
         if weight < 0 {
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
-        let symbol = symbol.or_else(|()| Symbol::parse(context, input))?;
+        let symbol = symbol.or_else(|_| Symbol::parse(context, input))?;
         Ok(AdditiveTuple {
             weight: weight as u32,
             symbol: symbol,
         })
     }
 }
 
 impl ToCss for AdditiveTuple {
@@ -606,37 +610,38 @@ pub enum SpeakAs {
     Words,
     // /// spell-out, not supported, see bug 1024178
     // SpellOut,
     /// <counter-style-name>
     Other(CustomIdent),
 }
 
 impl Parse for SpeakAs {
-    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let mut is_spell_out = false;
-        let result = input.try(|input| {
-            match_ignore_ascii_case! { &input.expect_ident()?,
+        let result: Result<_, ParseError> = input.try(|input| {
+            let ident = input.expect_ident()?;
+            (match_ignore_ascii_case! { &ident,
                 "auto" => Ok(SpeakAs::Auto),
                 "bullets" => Ok(SpeakAs::Bullets),
                 "numbers" => Ok(SpeakAs::Numbers),
                 "words" => Ok(SpeakAs::Words),
                 "spell-out" => {
                     is_spell_out = true;
                     Err(())
                 }
                 _ => Err(())
-            }
+            }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
         });
         if is_spell_out {
             // spell-out is not supported, but don’t parse it as a <counter-style-name>.
             // See bug 1024178.
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
-        result.or_else(|()| {
+        result.or_else(|_| {
             Ok(SpeakAs::Other(parse_counter_style_name(input)?))
         })
     }
 }
 
 impl ToCss for SpeakAs {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
--- a/servo/components/style/custom_properties.rs
+++ b/servo/components/style/custom_properties.rs
@@ -2,24 +2,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Support for [custom properties for cascading variables][custom].
 //!
 //! [custom]: https://drafts.csswg.org/css-variables/
 
 use Atom;
-use cssparser::{Delimiter, Parser, SourcePosition, Token, TokenSerializationType};
+use cssparser::{Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType};
 use parser::ParserContext;
 use properties::{CSSWideKeyword, DeclaredValue};
+use selectors::parser::SelectorParseError;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::collections::{HashMap, HashSet};
 use std::fmt;
-use style_traits::{HasViewportPercentage, ToCss};
+use style_traits::{HasViewportPercentage, ToCss, StyleParseError, ParseError};
 use stylearc::Arc;
 
 /// A custom property name is just an `Atom`.
 ///
 /// Note that this does not include the `--` prefix
 pub type Name = Atom;
 
 /// Parse a custom property name.
@@ -126,44 +127,45 @@ impl ComputedValue {
 
     fn push_variable(&mut self, variable: &ComputedValue) {
         self.push(&variable.css, variable.first_token_type, variable.last_token_type)
     }
 }
 
 impl SpecifiedValue {
     /// Parse a custom property SpecifiedValue.
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Box<Self>, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<Box<Self>, ParseError<'i>> {
         let mut references = Some(HashSet::new());
         let (first, css, last) = try!(parse_self_contained_declaration_value(input, &mut references));
         Ok(Box::new(SpecifiedValue {
             css: css.into_owned(),
             first_token_type: first,
             last_token_type: last,
             references: references.unwrap(),
         }))
     }
 }
 
 /// Parse the value of a non-custom property that contains `var()` references.
 pub fn parse_non_custom_with_var<'i, 't>
                                 (input: &mut Parser<'i, 't>)
-                                -> Result<(TokenSerializationType, Cow<'i, str>), ()> {
+                                -> Result<(TokenSerializationType, Cow<'i, str>), ParseError<'i>> {
     let (first_token_type, css, _) = try!(parse_self_contained_declaration_value(input, &mut None));
     Ok((first_token_type, css))
 }
 
 fn parse_self_contained_declaration_value<'i, 't>
                                          (input: &mut Parser<'i, 't>,
                                           references: &mut Option<HashSet<Name>>)
                                           -> Result<(
                                               TokenSerializationType,
                                               Cow<'i, str>,
                                               TokenSerializationType
-                                          ), ()> {
+                                          ), ParseError<'i>> {
     let start_position = input.position();
     let mut missing_closing_characters = String::new();
     let (first, last) = try!(
         parse_declaration_value(input, references, &mut missing_closing_characters));
     let mut css: Cow<str> = input.slice_from(start_position).into();
     if !missing_closing_characters.is_empty() {
         // Unescaped backslash at EOF in a quoted string is ignored.
         if css.ends_with("\\") && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'') {
@@ -174,37 +176,39 @@ fn parse_self_contained_declaration_valu
     Ok((first, css, last))
 }
 
 /// https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value
 fn parse_declaration_value<'i, 't>
                           (input: &mut Parser<'i, 't>,
                            references: &mut Option<HashSet<Name>>,
                            missing_closing_characters: &mut String)
-                          -> Result<(TokenSerializationType, TokenSerializationType), ()> {
+                          -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
     input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
         // Need at least one token
         let start_position = input.position();
         try!(input.next_including_whitespace());
         input.reset(start_position);
 
         parse_declaration_value_block(input, references, missing_closing_characters)
     })
 }
 
 /// Like parse_declaration_value, but accept `!` and `;` since they are only
 /// invalid at the top level
-fn parse_declaration_value_block(input: &mut Parser,
+fn parse_declaration_value_block<'i, 't>
+                                (input: &mut Parser<'i, 't>,
                                  references: &mut Option<HashSet<Name>>,
                                  missing_closing_characters: &mut String)
-                                 -> Result<(TokenSerializationType, TokenSerializationType), ()> {
+                                 -> Result<(TokenSerializationType, TokenSerializationType),
+                                           ParseError<'i>> {
     let mut token_start = input.position();
     let mut token = match input.next_including_whitespace_and_comments() {
         Ok(token) => token,
-        Err(()) => return Ok((TokenSerializationType::nothing(), TokenSerializationType::nothing()))
+        Err(_) => return Ok((TokenSerializationType::nothing(), TokenSerializationType::nothing()))
     };
     let first_token_type = token.serialization_type();
     loop {
         macro_rules! nested {
             () => {
                 try!(input.parse_nested_block(|input| {
                     parse_declaration_value_block(input, references, missing_closing_characters)
                 }))
@@ -221,23 +225,26 @@ fn parse_declaration_value_block(input: 
             Token::Comment(_) => {
                 let token_slice = input.slice_from(token_start);
                 if !token_slice.ends_with("*/") {
                     missing_closing_characters.push_str(
                         if token_slice.ends_with('*') { "/" } else { "*/" })
                 }
                 token.serialization_type()
             }
-            Token::BadUrl |
-            Token::BadString |
-            Token::CloseParenthesis |
-            Token::CloseSquareBracket |
-            Token::CloseCurlyBracket => {
-                return Err(())
-            }
+            Token::BadUrl =>
+                return Err(StyleParseError::BadUrlInDeclarationValueBlock.into()),
+            Token::BadString =>
+                return Err(StyleParseError::BadStringInDeclarationValueBlock.into()),
+            Token::CloseParenthesis =>
+                return Err(StyleParseError::UnbalancedCloseParenthesisInDeclarationValueBlock.into()),
+            Token::CloseSquareBracket =>
+                return Err(StyleParseError::UnbalancedCloseSquareBracketInDeclarationValueBlock.into()),
+            Token::CloseCurlyBracket =>
+                return Err(StyleParseError::UnbalancedCloseCurlyBracketInDeclarationValueBlock.into()),
             Token::Function(ref name) => {
                 if name.eq_ignore_ascii_case("var") {
                     let position = input.position();
                     try!(input.parse_nested_block(|input| {
                         parse_var_function(input, references)
                     }));
                     input.reset(position);
                 }
@@ -298,19 +305,22 @@ fn parse_declaration_value_block(input: 
             Err(..) => return Ok((first_token_type, last_token_type)),
         };
     }
 }
 
 // If the var function is valid, return Ok((custom_property_name, fallback))
 fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>,
                               references: &mut Option<HashSet<Name>>)
-                              -> Result<(), ()> {
+                              -> Result<(), ParseError<'i>> {
     let name = try!(input.expect_ident());
-    let name = try!(parse_name(&name));
+    let name: Result<_, ParseError> =
+        parse_name(&name)
+        .map_err(|()| SelectorParseError::UnexpectedIdent(name.clone()).into());
+    let name = try!(name);
     if input.try(|input| input.expect_comma()).is_ok() {
         // Exclude `!` and `;` at the top level
         // https://drafts.csswg.org/css-syntax/#typedef-declaration-value
         try!(input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
             // At least one non-comment token.
             try!(input.next_including_whitespace());
             // Skip until the end.
             while let Ok(_) = input.next_including_whitespace_and_comments() {}
@@ -469,17 +479,18 @@ fn substitute_one(name: &Name,
         return Ok(computed_value.last_token_type)
     }
 
     if invalid.contains(name) {
         return Err(());
     }
     let computed_value = if specified_value.references.map(|set| set.is_empty()) == Some(false) {
         let mut partial_computed_value = ComputedValue::empty();
-        let mut input = Parser::new(&specified_value.css);
+        let mut input = ParserInput::new(&specified_value.css);
+        let mut input = Parser::new(&mut input);
         let mut position = (input.position(), specified_value.first_token_type);
         let result = substitute_block(
             &mut input, &mut position, &mut partial_computed_value,
             &mut |name, partial_computed_value| {
                 if let Some(other_specified_value) = specified_values_map.get(name) {
                     substitute_one(name, other_specified_value, specified_values_map, inherited,
                                    Some(partial_computed_value), computed_values_map, invalid)
                 } else {
@@ -520,31 +531,31 @@ fn substitute_one(name: &Name,
 /// The `substitute_one` callback is called for each `var()` function in `input`.
 /// If the variable has its initial value,
 /// the callback should return `Err(())` and leave `partial_computed_value` unchanged.
 /// Otherwise, it should push the value of the variable (with its own `var()` functions replaced)
 /// to `partial_computed_value` and return `Ok(last_token_type of what was pushed)`
 ///
 /// Return `Err(())` if `input` is invalid at computed-value time.
 /// or `Ok(last_token_type that was pushed to partial_computed_value)` otherwise.
-fn substitute_block<F>(input: &mut Parser,
-                       position: &mut (SourcePosition, TokenSerializationType),
-                       partial_computed_value: &mut ComputedValue,
-                       substitute_one: &mut F)
-                       -> Result<TokenSerializationType, ()>
+fn substitute_block<'i, 't, F>(input: &mut Parser<'i, 't>,
+                               position: &mut (SourcePosition, TokenSerializationType),
+                               partial_computed_value: &mut ComputedValue,
+                               substitute_one: &mut F)
+                               -> Result<TokenSerializationType, ParseError<'i>>
                        where F: FnMut(&Name, &mut ComputedValue) -> Result<TokenSerializationType, ()> {
     let mut last_token_type = TokenSerializationType::nothing();
     let mut set_position_at_next_iteration = false;
     loop {
         let before_this_token = input.position();
         let next = input.next_including_whitespace_and_comments();
         if set_position_at_next_iteration {
             *position = (before_this_token, match next {
                 Ok(ref token) => token.serialization_type(),
-                Err(()) => TokenSerializationType::nothing(),
+                Err(_) => TokenSerializationType::nothing(),
             });
             set_position_at_next_iteration = false;
         }
         let token = match next {
             Ok(token) => token,
             Err(..) => break,
         };
         match token {
@@ -600,21 +611,22 @@ fn substitute_block<F>(input: &mut Parse
     // <p style="background: var(--color) var(--image) top left; --image: url('a.png"></p>
     // </div>
     // ```
     Ok(last_token_type)
 }
 
 /// Replace `var()` functions for a non-custom property.
 /// Return `Err(())` for invalid at computed time.
-pub fn substitute(input: &str, first_token_type: TokenSerializationType,
-                  computed_values_map: &Option<Arc<HashMap<Name, ComputedValue>>>)
-                  -> Result<String, ()> {
+pub fn substitute<'i>(input: &'i str, first_token_type: TokenSerializationType,
+                      computed_values_map: &Option<Arc<HashMap<Name, ComputedValue>>>)
+                      -> Result<String, ParseError<'i>> {
     let mut substituted = ComputedValue::empty();
-    let mut input = Parser::new(input);
+    let mut input = ParserInput::new(input);
+    let mut input = Parser::new(&mut input);
     let mut position = (input.position(), first_token_type);
     let last_token_type = try!(substitute_block(
         &mut input, &mut position, &mut substituted, &mut |name, substituted| {
             if let Some(value) = computed_values_map.as_ref().and_then(|map| map.get(name)) {
                 substituted.push_variable(value);
                 Ok(value.last_token_type)
             } else {
                 Err(())
--- a/servo/components/style/error_reporting.rs
+++ b/servo/components/style/error_reporting.rs
@@ -1,62 +1,195 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Types used to report parsing errors.
 
 #![deny(missing_docs)]
 
-use cssparser::{Parser, SourcePosition};
+use cssparser::{Parser, SourcePosition, BasicParseError, Token, NumericValue, PercentageValue};
+use cssparser::ParseError as CssParseError;
 use log;
+use style_traits::ParseError;
 use stylesheets::UrlExtraData;
 
+/// Errors that can be encountered while parsing CSS.
+pub enum ContextualParseError<'a> {
+    /// A property declaration was not recognized.
+    UnsupportedPropertyDeclaration(&'a str, ParseError<'a>),
+    /// A font face descriptor was not recognized.
+    UnsupportedFontFaceDescriptor(&'a str, ParseError<'a>),
+    /// A keyframe rule was not valid.
+    InvalidKeyframeRule(&'a str, ParseError<'a>),
+    /// A keyframe property declaration was not recognized.
+    UnsupportedKeyframePropertyDeclaration(&'a str, ParseError<'a>),
+    /// A rule was invalid for some reason.
+    InvalidRule(&'a str, ParseError<'a>),
+    /// A rule was not recognized.
+    UnsupportedRule(&'a str, ParseError<'a>),
+    /// A viewport descriptor declaration was not recognized.
+    UnsupportedViewportDescriptorDeclaration(&'a str, ParseError<'a>),
+    /// A counter style descriptor declaration was not recognized.
+    UnsupportedCounterStyleDescriptorDeclaration(&'a str, ParseError<'a>),
+    /// A counter style rule had no symbols.
+    InvalidCounterStyleWithoutSymbols(String),
+    /// A counter style rule had less than two symbols.
+    InvalidCounterStyleNotEnoughSymbols(String),
+    /// A counter style rule did not have additive-symbols.
+    InvalidCounterStyleWithoutAdditiveSymbols,
+    /// A counter style rule had extends with symbols.
+    InvalidCounterStyleExtendsWithSymbols,
+    /// A counter style rule had extends with additive-symbols.
+    InvalidCounterStyleExtendsWithAdditiveSymbols
+}
+
+impl<'a> ContextualParseError<'a> {
+    /// Turn a parse error into a string representation.
+    pub fn to_string(&self) -> String {
+        fn token_to_str(t: &Token) -> String {
+            match *t {
+                Token::Ident(ref i) => format!("identifier {}", i),
+                Token::AtKeyword(ref kw) => format!("keyword @{}", kw),
+                Token::Hash(ref h) => format!("hash #{}", h),
+                Token::IDHash(ref h) => format!("id selector #{}", h),
+                Token::QuotedString(ref s) => format!("quoted string \"{}\"", s),
+                Token::UnquotedUrl(ref u) => format!("url {}", u),
+                Token::Delim(ref d) => format!("delimiter {}", d),
+                Token::Number(NumericValue { int_value: Some(i), .. }) => format!("number {}", i),
+                Token::Number(ref n) => format!("number {}", n.value),
+                Token::Percentage(PercentageValue { int_value: Some(i), .. }) => format!("percentage {}", i),
+                Token::Percentage(ref p) => format!("percentage {}", p.unit_value),
+                Token::Dimension(_, ref d) => format!("dimension {}", d),
+                Token::WhiteSpace(_) => format!("whitespace"),
+                Token::Comment(_) => format!("comment"),
+                Token::Colon => format!("colon (:)"),
+                Token::Semicolon => format!("semicolon (;)"),
+                Token::Comma => format!("comma (,)"),
+                Token::IncludeMatch => format!("include match (~=)"),
+                Token::DashMatch => format!("dash match (|=)"),
+                Token::PrefixMatch => format!("prefix match (^=)"),
+                Token::SuffixMatch => format!("suffix match ($=)"),
+                Token::SubstringMatch => format!("substring match (*=)"),
+                Token::Column => format!("column (||)"),
+                Token::CDO => format!("CDO (<!--)"),
+                Token::CDC => format!("CDC (-->)"),
+                Token::Function(ref f) => format!("function {}", f),
+                Token::ParenthesisBlock => format!("parenthesis ("),
+                Token::SquareBracketBlock => format!("square bracket ["),
+                Token::CurlyBracketBlock => format!("curly bracket {{"),
+                Token::BadUrl => format!("bad url parse error"),
+                Token::BadString => format!("bad string parse error"),
+                Token::CloseParenthesis => format!("unmatched close parenthesis"),
+                Token::CloseSquareBracket => format!("unmatched close square bracket"),
+                Token::CloseCurlyBracket => format!("unmatched close curly bracket"),
+            }
+        }
+
+        fn parse_error_to_str(err: &ParseError) -> String {
+            match *err {
+                CssParseError::Basic(BasicParseError::UnexpectedToken(ref t)) =>
+                    format!("found unexpected {}", token_to_str(t)),
+                CssParseError::Basic(BasicParseError::ExpectedToken(ref t)) =>
+                    format!("expected {}", token_to_str(t)),
+                CssParseError::Basic(BasicParseError::EndOfInput) =>
+                    format!("unexpected end of input"),
+                CssParseError::Basic(BasicParseError::AtRuleInvalid) =>
+                    format!("@ rule invalid"),
+                CssParseError::Basic(BasicParseError::QualifiedRuleInvalid) =>
+                    format!("qualified rule invalid"),
+                CssParseError::Custom(ref err) =>
+                    format!("{:?}", err)
+            }
+        }
+
+        match *self {
+            ContextualParseError::UnsupportedPropertyDeclaration(decl, ref err) =>
+                format!("Unsupported property declaration: '{}', {}", decl,
+                        parse_error_to_str(err)),
+            ContextualParseError::UnsupportedFontFaceDescriptor(decl, ref err) =>
+                format!("Unsupported @font-face descriptor declaration: '{}', {}", decl,
+                        parse_error_to_str(err)),
+            ContextualParseError::InvalidKeyframeRule(rule, ref err) =>
+                format!("Invalid keyframe rule: '{}', {}", rule,
+                        parse_error_to_str(err)),
+            ContextualParseError::UnsupportedKeyframePropertyDeclaration(decl, ref err) =>
+                format!("Unsupported keyframe property declaration: '{}', {}", decl,
+                        parse_error_to_str(err)),
+            ContextualParseError::InvalidRule(rule, ref err) =>
+                format!("Invalid rule: '{}', {}", rule, parse_error_to_str(err)),
+            ContextualParseError::UnsupportedRule(rule, ref err) =>
+                format!("Unsupported rule: '{}', {}", rule, parse_error_to_str(err)),
+            ContextualParseError::UnsupportedViewportDescriptorDeclaration(decl, ref err) =>
+                format!("Unsupported @viewport descriptor declaration: '{}', {}", decl,
+                        parse_error_to_str(err)),
+            ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(decl, ref err) =>
+                format!("Unsupported @counter-style descriptor declaration: '{}', {}", decl,
+                        parse_error_to_str(err)),
+            ContextualParseError::InvalidCounterStyleWithoutSymbols(ref system) =>
+                format!("Invalid @counter-style rule: 'system: {}' without 'symbols'", system),
+            ContextualParseError::InvalidCounterStyleNotEnoughSymbols(ref system) =>
+                format!("Invalid @counter-style rule: 'system: {}' less than two 'symbols'", system),
+            ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols =>
+                "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'".into(),
+            ContextualParseError::InvalidCounterStyleExtendsWithSymbols =>
+                "Invalid @counter-style rule: 'system: extends …' with 'symbols'".into(),
+            ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols =>
+                "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'".into(),
+        }
+    }
+}
+
 /// A generic trait for an error reporter.
-pub trait ParseErrorReporter : Sync + Send {
+pub trait ParseErrorReporter : Sync {
     /// Called when the style engine detects an error.
     ///
     /// Returns the current input being parsed, the source position it was
     /// reported from, and a message.
-    fn report_error(&self,
-                    input: &mut Parser,
-                    position: SourcePosition,
-                    message: &str,
-                    url: &UrlExtraData,
-                    line_number_offset: u64);
+    fn report_error<'a>(&self,
+                        input: &mut Parser,
+                        position: SourcePosition,
+                        error: ContextualParseError<'a>,
+                        url: &UrlExtraData,
+                        line_number_offset: u64);
 }
 
 /// An error reporter that uses [the `log` crate](https://github.com/rust-lang-nursery/log)
 /// at `info` level.
 ///
 /// This logging is silent by default, and can be enabled with a `RUST_LOG=style=info`
 /// environment variable.
 /// (See [`env_logger`](https://rust-lang-nursery.github.io/log/env_logger/).)
 pub struct RustLogReporter;
 
 impl ParseErrorReporter for RustLogReporter {
-    fn report_error(&self,
-                    input: &mut Parser,
-                    position: SourcePosition,
-                    message: &str,
-                    url: &UrlExtraData,
-                    line_number_offset: u64) {
+    fn report_error<'a>(&self,
+                        input: &mut Parser,
+                        position: SourcePosition,
+                        error: ContextualParseError<'a>,
+                        url: &UrlExtraData,
+                        line_number_offset: u64) {
         if log_enabled!(log::LogLevel::Info) {
             let location = input.source_location(position);
             let line_offset = location.line + line_number_offset as usize;
-            info!("Url:\t{}\n{}:{} {}", url.as_str(), line_offset, location.column, message)
+            info!("Url:\t{}\n{}:{} {}", url.as_str(), line_offset, location.column, error.to_string())
         }
     }
 }
 
 /// Error reporter which silently forgets errors
 pub struct NullReporter;
 
 impl ParseErrorReporter for NullReporter {
-    fn report_error(&self,
+    fn report_error<'a>(&self,
             _: &mut Parser,
             _: SourcePosition,
-            _: &str,
+            _: ContextualParseError<'a>,
             _: &UrlExtraData,
             _: u64) {
         // do nothing
     }
 }
+
+/// Create an instance of the default error reporter.
+pub fn create_error_reporter() -> RustLogReporter {
+    RustLogReporter
+}
--- a/servo/components/style/font_face.rs
+++ b/servo/components/style/font_face.rs
@@ -8,22 +8,25 @@
 
 #![deny(missing_docs)]
 
 #[cfg(feature = "gecko")]
 use computed_values::{font_feature_settings, font_stretch, font_style, font_weight};
 use computed_values::font_family::FamilyName;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
 use cssparser::SourceLocation;
+use error_reporting::ContextualParseError;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::CSSFontFaceDescriptors;
 #[cfg(feature = "gecko")] use cssparser::UnicodeRange;
 use parser::{ParserContext, log_css_error, Parse};
+use selectors::parser::SelectorParseError;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use std::borrow::Cow;
 use std::fmt;
-use style_traits::{ToCss, OneOrMoreCommaSeparated};
+use style_traits::{ToCss, OneOrMoreCommaSeparated, ParseError, StyleParseError};
 use values::specified::url::SpecifiedUrl;
 
 /// A source for a font-face rule.
 #[derive(Clone, Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 pub enum Source {
     /// A `url()` source.
     Url(UrlSource),
@@ -92,21 +95,21 @@ pub fn parse_font_face_block(context: &P
     rule.source_location = location;
     {
         let parser = FontFaceRuleParser {
             context: context,
             rule: &mut rule,
         };
         let mut iter = DeclarationListParser::new(input, parser);
         while let Some(declaration) = iter.next() {
-            if let Err(range) = declaration {
-                let pos = range.start;
-                let message = format!("Unsupported @font-face descriptor declaration: '{}'",
-                                      iter.input.slice(range));
-                log_css_error(iter.input, pos, &*message, context);
+            if let Err(err) = declaration {
+                let pos = err.span.start;
+                let error = ContextualParseError::UnsupportedFontFaceDescriptor(
+                    iter.input.slice(err.span), err.error);
+                log_css_error(iter.input, pos, error, context);
             }
         }
     }
     rule
 }
 
 /// A @font-face rule that is known to have font-family and src declarations.
 #[cfg(feature = "servo")]
@@ -149,23 +152,25 @@ impl Iterator for EffectiveSources {
 }
 
 struct FontFaceRuleParser<'a, 'b: 'a> {
     context: &'a ParserContext<'b>,
     rule: &'a mut FontFaceRuleData,
 }
 
 /// Default methods reject all at rules.
-impl<'a, 'b> AtRuleParser for FontFaceRuleParser<'a, 'b> {
+impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
     type Prelude = ();
     type AtRule = ();
+    type Error = SelectorParseError<'i, StyleParseError<'i>>;
 }
 
 impl Parse for Source {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Source, ()> {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                     -> Result<Source, ParseError<'i>> {
         if input.try(|input| input.expect_function_matching("local")).is_ok() {
             return input.parse_nested_block(|input| {
                 FamilyName::parse(context, input)
             }).map(Source::Local)
         }
 
         let url = SpecifiedUrl::parse(context, input)?;
 
@@ -242,32 +247,34 @@ macro_rules! font_face_descriptors_commo
                         ToCss::to_css(value, dest)?;
                         dest.write_str(";\n")?;
                     }
                 )*
                 dest.write_str("}")
             }
         }
 
-       impl<'a, 'b> DeclarationParser for FontFaceRuleParser<'a, 'b> {
-            type Declaration = ();
+       impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
+           type Declaration = ();
+           type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
-            fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> {
-                match_ignore_ascii_case! { name,
+           fn parse_value<'t>(&mut self, name: Cow<'i, str>, input: &mut Parser<'i, 't>)
+                              -> Result<(), ParseError<'i>> {
+                match_ignore_ascii_case! { &*name,
                     $(
                         $name => {
                             // DeclarationParser also calls parse_entirely
                             // so we’d normally not need to,
                             // but in this case we do because we set the value as a side effect
                             // rather than returning it.
                             let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
                             self.rule.$ident = Some(value)
                         }
                     )*
-                    _ => return Err(())
+                    _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 Ok(())
             }
         }
     }
 }
 
 macro_rules! font_face_descriptors {
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -1,34 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Gecko's media-query device and expression representation.
 
 use app_units::Au;
 use context::QuirksMode;
-use cssparser::{CssStringWriter, Parser, RGBA, Token};
+use cssparser::{CssStringWriter, Parser, RGBA, Token, BasicParseError};
 use euclid::Size2D;
 use font_metrics::get_metrics_provider_for_product;
 use gecko::values::convert_nscolor_to_rgba;
 use gecko_bindings::bindings;
 use gecko_bindings::structs::{nsCSSKeyword, nsCSSProps_KTableEntry, nsCSSValue, nsCSSUnit, nsStringBuffer};
 use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
+use selectors::parser::SelectorParseError;
 use std::fmt::{self, Write};
 use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
 use str::starts_with_ignore_ascii_case;
 use string_cache::Atom;
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError, StyleParseError};
 use style_traits::viewport::ViewportConstraints;
 use stylearc::Arc;
 use values::{CSSFloat, specified};
 use values::computed::{self, ToComputedValue};
 
 /// The `Device` in Gecko wraps a pres context, has a default values computed,
 /// and contains all the viewport rule state.
 pub struct Device {
@@ -223,34 +224,35 @@ impl Resolution {
     fn to_dpi(&self) -> CSSFloat {
         match *self {
             Resolution::Dpi(f) => f,
             Resolution::Dppx(f) => f * 96.0,
             Resolution::Dpcm(f) => f * 2.54,
         }
     }
 
-    fn parse(input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let (value, unit) = match try!(input.next()) {
             Token::Dimension(value, unit) => {
-                (value.value, unit)
+                (value, unit)
             },
-            _ => return Err(()),
+            t => return Err(BasicParseError::UnexpectedToken(t).into()),
         };
 
-        if value <= 0. {
-            return Err(())
+        let inner_value = value.value;
+        if inner_value <= 0. {
+            return Err(StyleParseError::UnspecifiedError.into())
         }
 
-        Ok(match_ignore_ascii_case! { &unit,
-            "dpi" => Resolution::Dpi(value),
-            "dppx" => Resolution::Dppx(value),
-            "dpcm" => Resolution::Dpcm(value),
-            _ => return Err(())
-        })
+        (match_ignore_ascii_case! { &unit,
+            "dpi" => Ok(Resolution::Dpi(inner_value)),
+            "dppx" => Ok(Resolution::Dppx(inner_value)),
+            "dpcm" => Ok(Resolution::Dpcm(inner_value)),
+            _ => Err(())
+        }).map_err(|()| StyleParseError::UnexpectedDimension(unit).into())
     }
 }
 
 impl ToCss for Resolution {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
         where W: fmt::Write,
     {
         match *self {
@@ -454,101 +456,108 @@ impl Expression {
         }
     }
 
     /// Parse a media expression of the form:
     ///
     /// ```
     /// (media-feature: media-value)
     /// ```
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<Self, ParseError<'i>> {
         try!(input.expect_parenthesis_block());
         input.parse_nested_block(|input| {
             let ident = try!(input.expect_ident());
 
             let mut flags = 0;
-            let mut feature_name = &*ident;
+            let result = {
+                let mut feature_name = &*ident;
 
-            // TODO(emilio): this is under a pref in Gecko.
-            if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
-                feature_name = &feature_name[8..];
-                flags |= nsMediaFeature_RequirementFlags::eHasWebkitPrefix as u8;
-            }
+                // TODO(emilio): this is under a pref in Gecko.
+                if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
+                    feature_name = &feature_name[8..];
+                    flags |= nsMediaFeature_RequirementFlags::eHasWebkitPrefix as u8;
+                }
 
-            let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
-                feature_name = &feature_name[4..];
-                nsMediaExpression_Range::eMin
-            } else if starts_with_ignore_ascii_case(feature_name, "max-") {
-                feature_name = &feature_name[4..];
-                nsMediaExpression_Range::eMax
-            } else {
-                nsMediaExpression_Range::eEqual
+                let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
+                    feature_name = &feature_name[4..];
+                    nsMediaExpression_Range::eMin
+                } else if starts_with_ignore_ascii_case(feature_name, "max-") {
+                    feature_name = &feature_name[4..];
+                    nsMediaExpression_Range::eMax
+                } else {
+                    nsMediaExpression_Range::eEqual
+                };
+
+                let atom = Atom::from(feature_name);
+                match find_feature(|f| atom.as_ptr() == unsafe { *f.mName }) {
+                    Some(f) => Ok((f, range)),
+                    None => Err(()),
+                }
             };
 
-            let atom = Atom::from(feature_name);
-            let feature =
-                match find_feature(|f| atom.as_ptr() == unsafe { *f.mName }) {
-                    Some(f) => f,
-                    None => return Err(()),
-                };
+            let (feature, range) = match result {
+                Ok((feature, range)) => (feature, range),
+                Err(()) => return Err(SelectorParseError::UnexpectedIdent(ident).into()),
+            };
 
             if (feature.mReqFlags & !flags) != 0 {
-                return Err(());
+                return Err(SelectorParseError::UnexpectedIdent(ident).into());
             }
 
             if range != nsMediaExpression_Range::eEqual &&
                 feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
-                return Err(());
+                return Err(SelectorParseError::UnexpectedIdent(ident).into());
             }
 
             // If there's no colon, this is a media query of the form
             // '(<feature>)', that is, there's no value specified.
             //
             // Gecko doesn't allow ranged expressions without a value, so just
             // reject them here too.
             if input.try(|i| i.expect_colon()).is_err() {
                 if range != nsMediaExpression_Range::eEqual {
-                    return Err(())
+                    return Err(StyleParseError::RangedExpressionWithNoValue.into())
                 }
                 return Ok(Expression::new(feature, None, range));
             }
 
             let value = match feature.mValueType {
                 nsMediaFeature_ValueType::eLength => {
                     MediaExpressionValue::Length(
                         specified::Length::parse_non_negative(context, input)?)
                 },
                 nsMediaFeature_ValueType::eInteger => {
                     let i = input.expect_integer()?;
                     if i < 0 {
-                        return Err(())
+                        return Err(StyleParseError::UnspecifiedError.into())
                     }
                     MediaExpressionValue::Integer(i as u32)
                 }
                 nsMediaFeature_ValueType::eBoolInteger => {
                     let i = input.expect_integer()?;
                     if i < 0 || i > 1 {
-                        return Err(())
+                        return Err(StyleParseError::UnspecifiedError.into())
                     }
                     MediaExpressionValue::BoolInteger(i == 1)
                 }
                 nsMediaFeature_ValueType::eFloat => {
                     MediaExpressionValue::Float(input.expect_number()?)
                 }
                 nsMediaFeature_ValueType::eIntRatio => {
                     let a = input.expect_integer()?;
                     if a <= 0 {
-                        return Err(())
+                        return Err(StyleParseError::UnspecifiedError.into())
                     }
 
                     input.expect_delim('/')?;
 
                     let b = input.expect_integer()?;
                     if b <= 0 {
-                        return Err(())
+                        return Err(StyleParseError::UnspecifiedError.into())
                     }
                     MediaExpressionValue::IntRatio(a as u32, b as u32)
                 }
                 nsMediaFeature_ValueType::eResolution => {
                     MediaExpressionValue::Resolution(Resolution::parse(input)?)
                 }
                 nsMediaFeature_ValueType::eEnumerated => {
                     let keyword = input.expect_ident()?;
@@ -561,17 +570,17 @@ impl Expression {
                         *feature.mData.mKeywordTable.as_ref()
                     };
 
                     let value =
                         match unsafe { find_in_table(first_table_entry, |kw, _| kw == keyword) } {
                             Some((_kw, value)) => {
                                 value
                             }
-                            None => return Err(()),
+                            None => return Err(StyleParseError::UnspecifiedError.into()),
                         };
 
                     MediaExpressionValue::Enumerated(value)
                 }
                 nsMediaFeature_ValueType::eIdent => {
                     MediaExpressionValue::Ident(input.expect_ident()?.into_owned())
                 }
             };
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -3,21 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Gecko-specific bits for selector-parsing.
 
 use cssparser::{Parser, ToCss};
 use element_state::ElementState;
 use gecko_bindings::structs::CSSPseudoClassType;
 use selector_parser::{SelectorParser, PseudoElementCascadeType};
-use selectors::parser::{Selector, SelectorMethods};
+use selectors::parser::{Selector, SelectorMethods, SelectorParseError};
 use selectors::visitor::SelectorVisitor;
 use std::borrow::Cow;
 use std::fmt;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
+use style_traits::{ParseError, StyleParseError};
 
 pub use gecko::pseudo_element::{PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT};
 pub use gecko::snapshot::SnapshotMap;
 
 bitflags! {
     flags NonTSPseudoClassFlag: u8 {
         // See NonTSPseudoClass::is_internal()
         const PSEUDO_CLASS_INTERNAL = 0x01,
@@ -233,42 +234,45 @@ impl ::selectors::SelectorImpl for Selec
     type NamespaceUrl = Namespace;
     type BorrowedNamespaceUrl = WeakNamespace;
     type BorrowedLocalName = WeakAtom;
 
     type PseudoElement = PseudoElement;
     type NonTSPseudoClass = NonTSPseudoClass;
 }
 
-impl<'a> ::selectors::Parser for SelectorParser<'a> {
+impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
     type Impl = SelectorImpl;
+    type Error = StyleParseError<'i>;
 
-    fn parse_non_ts_pseudo_class(&self, name: Cow<str>) -> Result<NonTSPseudoClass, ()> {
+    fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
+                                 -> Result<NonTSPseudoClass, ParseError<'i>> {
         macro_rules! pseudo_class_parse {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*],
              keyword: [$(($k_css:expr, $k_name:ident, $k_gecko_type:tt, $k_state:tt, $k_flags:tt),)*]) => {
                 match_ignore_ascii_case! { &name,
                     $($css => NonTSPseudoClass::$name,)*
-                    _ => return Err(())
+                    _ => return Err(::selectors::parser::SelectorParseError::UnexpectedIdent(
+                        name.clone()).into())
                 }
             }
         }
         let pseudo_class = apply_non_ts_list!(pseudo_class_parse);
         if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
             Ok(pseudo_class)
         } else {
-            Err(())
+            Err(SelectorParseError::UnexpectedIdent(name).into())
         }
     }
 
-    fn parse_non_ts_functional_pseudo_class(&self,
-                                            name: Cow<str>,
-                                            parser: &mut Parser)
-                                            -> Result<NonTSPseudoClass, ()> {
+    fn parse_non_ts_functional_pseudo_class<'t>(&self,
+                                                name: Cow<'i, str>,
+                                                parser: &mut Parser<'i, 't>)
+                                                -> Result<NonTSPseudoClass, ParseError<'i>> {
         macro_rules! pseudo_class_string_parse {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*],
              keyword: [$(($k_css:expr, $k_name:ident, $k_gecko_type:tt, $k_state:tt, $k_flags:tt),)*]) => {
                 match_ignore_ascii_case! { &name,
                     $($s_css => {
                         let name = parser.expect_ident_or_string()?;
                         // convert to null terminated utf16 string
@@ -284,35 +288,35 @@ impl<'a> ::selectors::Parser for Selecto
                         NonTSPseudoClass::$k_name(utf16.into_boxed_slice())
                     }, )*
                     "-moz-any" => {
                         let selectors = parser.parse_comma_separated(|input| {
                             Selector::parse(self, input)
                         })?;
                         // Selectors inside `:-moz-any` may not include combinators.
                         if selectors.iter().flat_map(|x| x.iter_raw()).any(|s| s.is_combinator()) {
-                            return Err(())
+                            return Err(SelectorParseError::UnexpectedIdent("-moz-any".into()).into())
                         }
                         NonTSPseudoClass::MozAny(selectors.into_boxed_slice())
                     }
-                    _ => return Err(())
+                    _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
             }
         }
         let pseudo_class = apply_non_ts_list!(pseudo_class_string_parse);
         if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
             Ok(pseudo_class)
         } else {
-            Err(())
+            Err(SelectorParseError::UnexpectedIdent(name).into())
         }
     }
 
-    fn parse_pseudo_element(&self, name: Cow<str>) -> Result<PseudoElement, ()> {
+    fn parse_pseudo_element(&self, name: Cow<'i, str>) -> Result<PseudoElement, ParseError<'i>> {
         PseudoElement::from_slice(&name, self.in_user_agent_stylesheet())
-            .ok_or(())
+            .ok_or(SelectorParseError::UnexpectedIdent(name.clone()).into())
     }
 
     fn default_namespace(&self) -> Option<Namespace> {
         self.namespaces.default.clone().as_ref().map(|&(ref ns, _)| ns.clone())
     }
 
     fn namespace_for_prefix(&self, prefix: &Atom) -> Option<Namespace> {
         self.namespaces.prefixes.get(prefix).map(|&(ref ns, _)| ns.clone())
--- a/servo/components/style/gecko/url.rs
+++ b/servo/components/style/gecko/url.rs
@@ -6,17 +6,17 @@
 
 use cssparser::CssStringWriter;
 use gecko_bindings::structs::{ServoBundledURI, URLExtraData};
 use gecko_bindings::structs::root::mozilla::css::ImageValue;
 use gecko_bindings::sugar::refptr::RefPtr;
 use parser::ParserContext;
 use std::borrow::Cow;
 use std::fmt::{self, Write};
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError};
 use stylearc::Arc;
 
 /// A specified url() value for gecko. Gecko does not eagerly resolve SpecifiedUrls.
 #[derive(Clone, Debug, PartialEq)]
 pub struct SpecifiedUrl {
     /// The URL in unresolved string form.
     ///
     /// Refcounted since cloning this should be cheap and data: uris can be
@@ -33,17 +33,17 @@ pub struct SpecifiedUrl {
 
 impl SpecifiedUrl {
     /// Try to parse a URL from a string value that is a valid CSS token for a
     /// URL.
     ///
     /// Returns `Err` in the case that extra_data is incomplete.
     pub fn parse_from_string<'a>(url: Cow<'a, str>,
                                  context: &ParserContext)
-                                 -> Result<Self, ()> {
+                                 -> Result<Self, ParseError<'a>> {
         Ok(SpecifiedUrl {
             serialization: Arc::new(url.into_owned()),
             extra_data: context.url_data.clone(),
             image_value: None,
         })
     }
 
     /// Returns true if the URL is definitely invalid. We don't eagerly resolve
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -16,17 +16,17 @@
 
 use app_units::Au;
 use atomic_refcell::AtomicRefCell;
 use context::{QuirksMode, SharedStyleContext, UpdateAnimationsTasks};
 use data::ElementData;
 use dom::{self, DescendantsBit, LayoutIterator, NodeInfo, TElement, TNode, UnsafeNode};
 use dom::{OpaqueNode, PresentationalHintsSynthesizer};
 use element_state::ElementState;
-use error_reporting::RustLogReporter;
+use error_reporting::create_error_reporter;
 use font_metrics::{FontMetrics, FontMetricsProvider, FontMetricsQueryResult};
 use gecko::data::PerDocumentStyleData;
 use gecko::global_style_data::GLOBAL_STYLE_DATA;
 use gecko::selector_parser::{SelectorImpl, NonTSPseudoClass, PseudoElement};
 use gecko::snapshot_helpers;
 use gecko_bindings::bindings;
 use gecko_bindings::bindings::{Gecko_DropStyleChildrenIterator, Gecko_MaybeCreateStyleChildrenIterator};
 use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetLastChild, Gecko_GetNextStyleChild};
@@ -411,17 +411,17 @@ impl<'le> fmt::Debug for GeckoElement<'l
     }
 }
 
 impl<'le> GeckoElement<'le> {
     /// Parse the style attribute of an element.
     pub fn parse_style_attribute(value: &str,
                                  url_data: &UrlExtraData,
                                  quirks_mode: QuirksMode) -> PropertyDeclarationBlock {
-        parse_style_attribute(value, url_data, &RustLogReporter, quirks_mode)
+        parse_style_attribute(value, url_data, &create_error_reporter(), quirks_mode)
     }
 
     fn flags(&self) -> u32 {
         self.raw_node()._base._base_1.mFlags
     }
 
     fn raw_node(&self) -> &RawGeckoNode {
         &(self.0)._base._base._base
--- a/servo/components/style/macros.rs
+++ b/servo/components/style/macros.rs
@@ -13,21 +13,24 @@ macro_rules! define_numbered_css_keyword
         #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
         pub enum $name {
             $( $variant = $value ),+
         }
 
         impl $crate::parser::Parse for $name {
             #[allow(missing_docs)]
-            fn parse(_context: &$crate::parser::ParserContext, input: &mut ::cssparser::Parser) -> Result<$name, ()> {
-                match_ignore_ascii_case! { &try!(input.expect_ident()),
+            fn parse<'i, 't>(_context: &$crate::parser::ParserContext,
+                             input: &mut ::cssparser::Parser<'i, 't>)
+                             -> Result<$name, ::style_traits::ParseError<'i>> {
+                let ident = try!(input.expect_ident());
+                (match_ignore_ascii_case! { &ident,
                     $( $css => Ok($name::$variant), )+
                     _ => Err(())
-                }
+                }).map_err(|()| ::selectors::parser::SelectorParseError::UnexpectedIdent(ident).into())
             }
         }
 
         impl ::style_traits::values::ToCss for $name {
             fn to_css<W>(&self, dest: &mut W) -> ::std::fmt::Result
                 where W: ::std::fmt::Write,
             {
                 match *self {
@@ -44,19 +47,19 @@ macro_rules! define_numbered_css_keyword
 ///
 /// NOTE: We should either move `Parse` trait to `style_traits`
 /// or `define_css_keyword_enum` macro to this crate, but that
 /// may involve significant cleanup in both the crates.
 macro_rules! add_impls_for_keyword_enum {
     ($name:ident) => {
         impl $crate::parser::Parse for $name {
             #[inline]
-            fn parse(_context: &$crate::parser::ParserContext,
-                     input: &mut ::cssparser::Parser)
-                     -> Result<Self, ()> {
+            fn parse<'i, 't>(_context: &$crate::parser::ParserContext,
+                             input: &mut ::cssparser::Parser<'i, 't>)
+                             -> Result<Self, ::style_traits::ParseError<'i>> {
                 $name::parse(input)
             }
         }
 
         impl $crate::values::computed::ComputedValueAsSpecified for $name {}
         no_viewport_percentage!($name);
     };
 }
@@ -84,19 +87,19 @@ macro_rules! define_keyword_type {
 
         impl fmt::Debug for $name {
             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                 write!(f, $css)
             }
         }
 
         impl $crate::parser::Parse for $name {
-            fn parse(_context: &$crate::parser::ParserContext,
-                     input: &mut ::cssparser::Parser)
-                     -> Result<$name, ()> {
-                input.expect_ident_matching($css).map(|_| $name)
+            fn parse<'i, 't>(_context: &$crate::parser::ParserContext,
+                             input: &mut ::cssparser::Parser<'i, 't>)
+                             -> Result<$name, ::style_traits::ParseError<'i>> {
+                input.expect_ident_matching($css).map(|_| $name).map_err(|e| e.into())
             }
         }
 
         impl $crate::values::computed::ComputedValueAsSpecified for $name {}
         no_viewport_percentage!($name);
     };
 }
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -3,22 +3,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! [Media queries][mq].
 //!
 //! [mq]: https://drafts.csswg.org/mediaqueries/
 
 use Atom;
 use context::QuirksMode;
-use cssparser::{Delimiter, Parser, Token};
+use cssparser::{Delimiter, Parser, Token, ParserInput};
 use parser::ParserContext;
+use selectors::parser::SelectorParseError;
 use serialize_comma_separated_list;
 use std::ascii::AsciiExt;
 use std::fmt;
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError, StyleParseError};
 
 #[cfg(feature = "servo")]
 pub use servo::media_queries::{Device, Expression};
 #[cfg(feature = "gecko")]
 pub use gecko::media_queries::{Device, Expression};
 
 /// A type that encapsulates a media query list.
 #[derive(Debug, Clone)]
@@ -204,33 +205,38 @@ impl MediaType {
             _ => return None
         })
     }
 }
 impl MediaQuery {
     /// Parse a media query given css input.
     ///
     /// Returns an error if any of the expressions is unknown.
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<MediaQuery, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<MediaQuery, ParseError<'i>> {
         let mut expressions = vec![];
 
         let qualifier = if input.try(|input| input.expect_ident_matching("only")).is_ok() {
             Some(Qualifier::Only)
         } else if input.try(|input| input.expect_ident_matching("not")).is_ok() {
             Some(Qualifier::Not)
         } else {
             None
         };
 
         let media_type = match input.try(|input| input.expect_ident()) {
-            Ok(ident) => try!(MediaQueryType::parse(&*ident)),
-            Err(()) => {
+            Ok(ident) => {
+                let result: Result<_, ParseError> = MediaQueryType::parse(&*ident)
+                    .map_err(|()| SelectorParseError::UnexpectedIdent(ident).into());
+                try!(result)
+            }
+            Err(_) => {
                 // Media type is only optional if qualifier is not specified.
                 if qualifier.is_some() {
-                    return Err(())
+                    return Err(StyleParseError::UnspecifiedError.into())
                 }
 
                 // Without a media type, require at least one expression.
                 expressions.push(try!(Expression::parse(context, input)));
 
                 MediaQueryType::All
             }
         };
@@ -265,17 +271,17 @@ pub fn parse_media_query_list(context: &
             Err(..) => {
                 media_queries.push(MediaQuery::never_matching());
             },
         }
 
         match input.next() {
             Ok(Token::Comma) => {},
             Ok(_) => unreachable!(),
-            Err(()) => break,
+            Err(_) => break,
         }
     }
 
     MediaList {
         media_queries: media_queries,
     }
 }
 
@@ -306,17 +312,18 @@ impl MediaList {
         self.media_queries.is_empty()
     }
 
     /// Append a new media query item to the media list.
     /// https://drafts.csswg.org/cssom/#dom-medialist-appendmedium
     ///
     /// Returns true if added, false if fail to parse the medium string.
     pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool {
-        let mut parser = Parser::new(new_medium);
+        let mut input = ParserInput::new(new_medium);
+        let mut parser = Parser::new(&mut input);
         let new_query = match MediaQuery::parse(&context, &mut parser) {
             Ok(query) => query,
             Err(_) => { return false; }
         };
         // This algorithm doesn't actually matches the current spec,
         // but it matches the behavior of Gecko and Edge.
         // See https://github.com/w3c/csswg-drafts/issues/697
         self.media_queries.retain(|query| query != &new_query);
@@ -324,17 +331,18 @@ impl MediaList {
         true
     }
 
     /// Delete a media query from the media list.
     /// https://drafts.csswg.org/cssom/#dom-medialist-deletemedium
     ///
     /// Returns true if found and deleted, false otherwise.
     pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool {
-        let mut parser = Parser::new(old_medium);
+        let mut input = ParserInput::new(old_medium);
+        let mut parser = Parser::new(&mut input);
         let old_query = match MediaQuery::parse(context, &mut parser) {
             Ok(query) => query,
             Err(_) => { return false; }
         };
         let old_len = self.media_queries.len();
         self.media_queries.retain(|query| query != &old_query);
         old_len != self.media_queries.len()
     }
--- a/servo/components/style/parser.rs
+++ b/servo/components/style/parser.rs
@@ -1,18 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! The context within which CSS code is parsed.
 
 use context::QuirksMode;
 use cssparser::{Parser, SourcePosition, UnicodeRange};
-use error_reporting::ParseErrorReporter;
-use style_traits::OneOrMoreCommaSeparated;
+use error_reporting::{ParseErrorReporter, ContextualParseError};
+use style_traits::{OneOrMoreCommaSeparated, ParseError};
 use stylesheets::{CssRuleType, Origin, UrlExtraData, Namespaces};
 
 bitflags! {
     /// The mode to use when parsing values.
     pub flags ParsingMode: u8 {
         /// In CSS, lengths must have units, except for zero values, where the unit can be omitted.
         /// https://www.w3.org/TR/css3-values/#lengths
         const PARSING_MODE_DEFAULT = 0x00,
@@ -159,57 +159,60 @@ impl<'a> ParserContext<'a> {
     pub fn rule_type(&self) -> CssRuleType {
         self.rule_type.expect("Rule type expected, but none was found.")
     }
 }
 
 /// Defaults to a no-op.
 /// Set a `RUST_LOG=style::errors` environment variable
 /// to log CSS parse errors to stderr.
-pub fn log_css_error(input: &mut Parser,
-                     position: SourcePosition,
-                     message: &str,
-                     parsercontext: &ParserContext) {
+pub fn log_css_error<'a>(input: &mut Parser,
+                         position: SourcePosition,
+                         error: ContextualParseError<'a>,
+                         parsercontext: &ParserContext) {
     let url_data = parsercontext.url_data;
     let line_number_offset = parsercontext.line_number_offset;
     parsercontext.error_reporter.report_error(input, position,
-                                              message, url_data,
+                                              error, url_data,
                                               line_number_offset);
 }
 
 // XXXManishearth Replace all specified value parse impls with impls of this
 // trait. This will make it easy to write more generic values in the future.
 /// A trait to abstract parsing of a specified value given a `ParserContext` and
 /// CSS input.
 pub trait Parse : Sized {
     /// Parse a value of this type.
     ///
     /// Returns an error on failure.
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()>;
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                     -> Result<Self, ParseError<'i>>;
 }
 
 impl<T> Parse for Vec<T> where T: Parse + OneOrMoreCommaSeparated {
-    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                     -> Result<Self, ParseError<'i>> {
         input.parse_comma_separated(|input| T::parse(context, input))
     }
 }
 
 /// Parse a non-empty space-separated or comma-separated list of objects parsed by parse_one
-pub fn parse_space_or_comma_separated<F, T>(input: &mut Parser, mut parse_one: F)
-        -> Result<Vec<T>, ()>
-        where F: FnMut(&mut Parser) -> Result<T, ()> {
+pub fn parse_space_or_comma_separated<'i, 't, F, T>(input: &mut Parser<'i, 't>, mut parse_one: F)
+        -> Result<Vec<T>, ParseError<'i>>
+        where F: for<'ii, 'tt> FnMut(&mut Parser<'ii, 'tt>) -> Result<T, ParseError<'ii>> {
     let first = parse_one(input)?;
     let mut vec = vec![first];
     loop {
         let _ = input.try(|i| i.expect_comma());
         if let Ok(val) = input.try(|i| parse_one(i)) {
             vec.push(val)
         } else {
             break
         }
     }
     Ok(vec)
 }
 impl Parse for UnicodeRange {
-    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
-        UnicodeRange::parse(input)
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                     -> Result<Self, ParseError<'i>> {
+        UnicodeRange::parse(input).map_err(|e| e.into())
     }
 }
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -2,25 +2,26 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A property declaration block.
 
 #![deny(missing_docs)]
 
 use context::QuirksMode;
-use cssparser::{DeclarationListParser, parse_important};
+use cssparser::{DeclarationListParser, parse_important, ParserInput};
 use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter};
-use error_reporting::ParseErrorReporter;
+use error_reporting::{ParseErrorReporter, ContextualParseError};
 use parser::{PARSING_MODE_DEFAULT, ParsingMode, ParserContext, log_css_error};
 use properties::animated_properties::AnimationValue;
+use selectors::parser::SelectorParseError;
 use shared_lock::Locked;
 use std::fmt;
 use std::slice::Iter;
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError, StyleParseError};
 use stylesheets::{CssRuleType, Origin, UrlExtraData};
 use stylesheets::{MallocSizeOf, MallocSizeOfFn};
 use super::*;
 use values::computed::Context;
 #[cfg(feature = "gecko")] use properties::animated_properties::AnimationValueMap;
 
 /// The animation rules.
 ///
@@ -870,17 +871,18 @@ pub fn parse_style_attribute(input: &str
                              quirks_mode: QuirksMode)
                              -> PropertyDeclarationBlock {
     let context = ParserContext::new(Origin::Author,
                                      url_data,
                                      error_reporter,
                                      Some(CssRuleType::Style),
                                      PARSING_MODE_DEFAULT,
                                      quirks_mode);
-    parse_property_declaration_list(&context, &mut Parser::new(input))
+    let mut input = ParserInput::new(input);
+    parse_property_declaration_list(&context, &mut Parser::new(&mut input))
 }
 
 /// Parse a given property declaration. Can result in multiple
 /// `PropertyDeclaration`s when expanding a shorthand, for example.
 ///
 /// This does not attempt to parse !important at all.
 pub fn parse_one_declaration_into(declarations: &mut SourcePropertyDeclaration,
                                   id: PropertyId,
@@ -891,47 +893,51 @@ pub fn parse_one_declaration_into(declar
                                   quirks_mode: QuirksMode)
                                   -> Result<(), ()> {
     let context = ParserContext::new(Origin::Author,
                                      url_data,
                                      error_reporter,
                                      Some(CssRuleType::Style),
                                      parsing_mode,
                                      quirks_mode);
-    Parser::new(input).parse_entirely(|parser| {
+    let mut input = ParserInput::new(input);
+    Parser::new(&mut input).parse_entirely(|parser| {
         PropertyDeclaration::parse_into(declarations, id, &context, parser)
-            .map_err(|_| ())
-    })
+            .map_err(|e| e.into())
+    }).map_err(|_| ())
 }
 
 /// A struct to parse property declarations.
 struct PropertyDeclarationParser<'a, 'b: 'a> {
     context: &'a ParserContext<'b>,
     declarations: &'a mut SourcePropertyDeclaration,
 }
 
 
 /// Default methods reject all at rules.
-impl<'a, 'b> AtRuleParser for PropertyDeclarationParser<'a, 'b> {
+impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'b> {
     type Prelude = ();
     type AtRule = Importance;
+    type Error = SelectorParseError<'i, StyleParseError<'i>>;
 }
 
-impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
+impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyDeclarationParser<'a, 'b> {
     type Declaration = Importance;
+    type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
-    fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<Importance, ()> {
-        let id = try!(PropertyId::parse(name.into()));
+    fn parse_value<'t>(&mut self, name: Cow<'i, str>, input: &mut Parser<'i, 't>)
+                       -> Result<Importance, ParseError<'i>> {
+        let id = try!(PropertyId::parse(name));
         input.parse_until_before(Delimiter::Bang, |input| {
             PropertyDeclaration::parse_into(self.declarations, id, self.context, input)
-                .map_err(|_| ())
+                .map_err(|e| e.into())
         })?;
         let importance = match input.try(parse_important) {
             Ok(()) => Importance::Important,
-            Err(()) => Importance::Normal,
+            Err(_) => Importance::Normal,
         };
         // In case there is still unparsed text in the declaration, we should roll back.
         input.expect_exhausted()?;
         Ok(importance)
     }
 }
 
 
@@ -947,19 +953,19 @@ pub fn parse_property_declaration_list(c
         declarations: &mut declarations,
     };
     let mut iter = DeclarationListParser::new(input, parser);
     while let Some(declaration) = iter.next() {
         match declaration {
             Ok(importance) => {
                 block.extend(iter.parser.declarations.drain(), importance);
             }
-            Err(range) => {
+            Err(err) => {
                 iter.parser.declarations.clear();
-                let pos = range.start;
-                let message = format!("Unsupported property declaration: '{}'",
-                                      iter.input.slice(range));
-                log_css_error(iter.input, pos, &*message, &context);
+                let pos = err.span.start;
+                let error = ContextualParseError::UnsupportedPropertyDeclaration(
+                    iter.input.slice(err.span), err.error);
+                log_css_error(iter.input, pos, error, &context);
             }
         }
     }
     block
 }
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -28,19 +28,19 @@
             % endif
         }
         #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} }
         % if initial_specified_value:
         #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { ${initial_specified_value} }
         % endif
         #[allow(unused_variables)]
         #[inline]
-        pub fn parse(context: &ParserContext,
-                     input: &mut Parser)
-                     -> Result<SpecifiedValue, ()> {
+        pub fn parse<'i, 't>(context: &ParserContext,
+                             input: &mut Parser<'i, 't>)
+                             -> Result<SpecifiedValue, ParseError<'i>> {
             % if allow_quirks:
             specified::${type}::${parse_method}_quirky(context, input, AllowQuirks::Yes)
             % elif needs_context:
             specified::${type}::${parse_method}(context, input)
             % else:
             specified::${type}::${parse_method}(input)
             % endif
         }
@@ -82,22 +82,26 @@
             use smallvec::SmallVec;
             use std::fmt;
             #[allow(unused_imports)]
             use style_traits::HasViewportPercentage;
             use style_traits::ToCss;
 
             pub mod single_value {
                 #[allow(unused_imports)]
-                use cssparser::Parser;
+                use cssparser::{Parser, BasicParseError};
                 #[allow(unused_imports)]
                 use parser::{Parse, ParserContext};
                 #[allow(unused_imports)]
                 use properties::ShorthandId;
                 #[allow(unused_imports)]
+                use selectors::parser::SelectorParseError;
+                #[allow(unused_imports)]
+                use style_traits::{ParseError, StyleParseError};
+                #[allow(unused_imports)]
                 use values::computed::{Context, ToComputedValue};
                 #[allow(unused_imports)]
                 use values::{computed, specified};
                 #[allow(unused_imports)]
                 use values::{Auto, Either, None_, Normal};
                 ${caller.body()}
             }
 
@@ -202,17 +206,18 @@
                     computed_value::T(SmallVec::new())
                 % else:
                     let mut v = SmallVec::new();
                     v.push(single_value::get_initial_value());
                     computed_value::T(v)
                 % endif
             }
 
-            pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+            pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                 -> Result<SpecifiedValue, ParseError<'i>> {
                 #[allow(unused_imports)]
                 use parser::parse_space_or_comma_separated;
 
                 <%
                     parse_func = "Parser::parse_comma_separated"
                     if space_separated_allowed:
                         parse_func = "parse_space_or_comma_separated"
                 %>
@@ -261,17 +266,17 @@
         property = data.declare_longhand(*args, **kwargs)
         if property is None:
             return ""
     %>
     /// ${property.spec}
     pub mod ${property.ident} {
         % if not property.derived_from:
             #[allow(unused_imports)]
-            use cssparser::Parser;
+            use cssparser::{Parser, BasicParseError, Token};
             #[allow(unused_imports)]
             use parser::{Parse, ParserContext};
             #[allow(unused_imports)]
             use properties::{UnparsedValue, ShorthandId};
         % endif
         #[allow(unused_imports)]
         use values::{Auto, Either, None_, Normal};
         #[allow(unused_imports)]
@@ -282,18 +287,22 @@
         use properties::longhands;
         #[allow(unused_imports)]
         use properties::{DeclaredValue, LonghandId, LonghandIdSet};
         #[allow(unused_imports)]
         use properties::{CSSWideKeyword, ComputedValues, PropertyDeclaration};
         #[allow(unused_imports)]
         use properties::style_structs;
         #[allow(unused_imports)]
+        use selectors::parser::SelectorParseError;
+        #[allow(unused_imports)]
         use stylearc::Arc;
         #[allow(unused_imports)]
+        use style_traits::{ParseError, StyleParseError};
+        #[allow(unused_imports)]
         use values::computed::{Context, ToComputedValue};
         #[allow(unused_imports)]
         use values::{computed, generics, specified};
         #[allow(unused_imports)]
         use Atom;
         ${caller.body()}
         #[allow(unused_variables)]
         pub fn cascade_property(declaration: &PropertyDeclaration,
@@ -418,34 +427,34 @@
                                             cacheable,
                                             error_reporter);
                 % endif
             % else:
                 // Do not allow stylesheets to set derived properties.
             % endif
         }
         % if not property.derived_from:
-            pub fn parse_specified(context: &ParserContext, input: &mut Parser)
+            pub fn parse_specified<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                 % if property.boxed:
-                                   -> Result<Box<SpecifiedValue>, ()> {
+                                   -> Result<Box<SpecifiedValue>, ParseError<'i>> {
                     parse(context, input).map(|result| Box::new(result))
                 % else:
-                                   -> Result<SpecifiedValue, ()> {
+                                   -> Result<SpecifiedValue, ParseError<'i>> {
                     % if property.allow_quirks:
                         parse_quirky(context, input, specified::AllowQuirks::Yes)
                     % else:
                         parse(context, input)
                     % endif
                 % endif
             }
-            pub fn parse_declared(context: &ParserContext, input: &mut Parser)
-                                  -> Result<PropertyDeclaration, ()> {
+            pub fn parse_declared<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                          -> Result<PropertyDeclaration, ParseError<'i>> {
                 match input.try(|i| CSSWideKeyword::parse(context, i)) {
                     Ok(keyword) => Ok(PropertyDeclaration::CSSWideKeyword(LonghandId::${property.camel_case}, keyword)),
-                    Err(()) => {
+                    Err(_) => {
                         input.look_for_var_functions();
                         let start = input.position();
                         let specified = parse_specified(context, input);
                         if specified.is_err() {
                             while let Ok(_) = input.next() {}  // Look for var() after the error.
                         }
                         let var = input.seen_var_functions();
                         if specified.is_err() && var {
@@ -482,25 +491,25 @@
         use std::fmt;
         use style_traits::ToCss;
         no_viewport_percentage!(SpecifiedValue);
 
         pub mod computed_value {
             use cssparser::Parser;
             use parser::{Parse, ParserContext};
 
-            use style_traits::ToCss;
+            use style_traits::{ToCss, ParseError};
             define_css_keyword_enum! { T:
                 % for value in keyword.values_for(product):
                     "${value}" => ${to_rust_ident(value)},
                 % endfor
             }
 
             impl Parse for T {
-                fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+                fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
                     T::parse(input)
                 }
             }
 
             ${gecko_keyword_conversion(keyword, keyword.values_for(product), type="T", cast_to="i32")}
         }
 
         #[derive(Debug, Clone, PartialEq, Eq, Copy)]
@@ -513,17 +522,17 @@
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
                 match *self {
                     SpecifiedValue::Keyword(k) => k.to_css(dest),
                     SpecifiedValue::System(_) => Ok(())
                 }
             }
         }
 
-        pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        pub fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<SpecifiedValue, ParseError<'i>> {
             Ok(SpecifiedValue::Keyword(computed_value::T::parse(input)?))
         }
 
         impl ToComputedValue for SpecifiedValue {
             type ComputedValue = computed_value::T;
             fn to_computed_value(&self, _cx: &Context) -> Self::ComputedValue {
                 match *self {
                     SpecifiedValue::Keyword(v) => v,
@@ -711,24 +720,24 @@
         pub fn get_initial_value() -> computed_value::T {
             computed_value::T::${to_rust_ident(values.split()[0])}
         }
         #[inline]
         pub fn get_initial_specified_value() -> SpecifiedValue {
             SpecifiedValue::${to_rust_ident(values.split()[0])}
         }
         #[inline]
-        pub fn parse(_context: &ParserContext, input: &mut Parser)
-                     -> Result<SpecifiedValue, ()> {
+        pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                             -> Result<SpecifiedValue, ParseError<'i>> {
             SpecifiedValue::parse(input)
         }
         impl Parse for SpecifiedValue {
             #[inline]
-            fn parse(_context: &ParserContext, input: &mut Parser)
-                         -> Result<SpecifiedValue, ()> {
+            fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                             -> Result<SpecifiedValue, ParseError<'i>> {
                 SpecifiedValue::parse(input)
             }
         }
 
         % if needs_conversion:
             <%
                 conversion_values = keyword.values_for(product)
                 if extra_specified:
@@ -759,19 +768,21 @@
 %>
     % if shorthand:
     /// ${shorthand.spec}
     pub mod ${shorthand.ident} {
         use cssparser::Parser;
         use parser::ParserContext;
         use properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed};
         use properties::{ShorthandId, LonghandId, UnparsedValue, longhands};
+        #[allow(unused_imports)]
+        use selectors::parser::SelectorParseError;
         use std::fmt;
         use stylearc::Arc;
-        use style_traits::ToCss;
+        use style_traits::{ToCss, ParseError, StyleParseError};
 
         pub struct Longhands {
             % for sub_property in shorthand.sub_properties:
                 pub ${sub_property.ident}:
                     % if sub_property.boxed:
                         Box<
                     % endif
                     longhands::${sub_property.ident}::SpecifiedValue
@@ -833,18 +844,18 @@
                     }),
                     _ => Err(())
                 }
             }
         }
 
         /// Parse the given shorthand and fill the result into the
         /// `declarations` vector.
-        pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
-                     context: &ParserContext, input: &mut Parser) -> Result<(), ()> {
+        pub fn parse_into<'i, 't>(declarations: &mut SourcePropertyDeclaration,
+                     context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
             input.look_for_var_functions();
             let start = input.position();
             let value = input.parse_entirely(|input| parse_value(context, input));
             if value.is_err() {
                 while let Ok(_) = input.next() {}  // Look for var() after the error.
             }
             let var = input.seen_var_functions();
             if let Ok(value) = value {
@@ -867,17 +878,17 @@
                 % for sub_property in shorthand.sub_properties:
                     declarations.push(PropertyDeclaration::WithVariables(
                         LonghandId::${sub_property.camel_case},
                         unparsed.clone()
                     ));
                 % endfor
                 Ok(())
             } else {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             }
         }
 
         ${caller.body()}
     }
     % endif
 </%def>
 
@@ -885,17 +896,18 @@
                                  needs_context=True, allow_quirks=False, **kwargs)">
     <% sub_properties=' '.join(sub_property_pattern % side for side in ['top', 'right', 'bottom', 'left']) %>
     <%call expr="self.shorthand(name, sub_properties=sub_properties, **kwargs)">
         #[allow(unused_imports)]
         use parser::Parse;
         use values::generics::rect::Rect;
         use values::specified;
 
-        pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+        pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                   -> Result<Longhands, ParseError<'i>> {
             let rect = Rect::parse_with(context, input, |_c, i| {
             % if allow_quirks:
                 ${parser_function}_quirky(_c, i, specified::AllowQuirks::Yes)
             % elif needs_context:
                 ${parser_function}(_c, i)
             % else:
                 ${parser_function}(i)
             % endif
@@ -1109,26 +1121,27 @@
         }
         % endif
 
         #[inline]
         pub fn get_initial_value() -> computed_value::T {
             use values::computed::${length_type};
             ${length_type}::${initial_value}
         }
-        fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
             % if logical:
             let ret = ${length_type}::parse(context, input);
             % else:
             let ret = ${length_type}::parse_quirky(context, input, AllowQuirks::Yes);
             % endif
             // Keyword values don't make sense in the block direction; don't parse them
             % if "block" in name:
                 if let Ok(${length_type}::ExtremumLength(..)) = ret {
-                    return Err(())
+                    return Err(StyleParseError::UnspecifiedError.into())
                 }
             % endif
             ret.map(SpecifiedValue)
         }
 
         impl ToComputedValue for SpecifiedValue {
             type ComputedValue = computed_value::T;
             #[inline]
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -22,22 +22,23 @@ use properties::longhands::font_stretch:
 use properties::longhands::text_shadow::computed_value::T as TextShadowList;
 use properties::longhands::box_shadow::computed_value::T as BoxShadowList;
 use properties::longhands::transform::computed_value::ComputedMatrix;
 use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use properties::longhands::transform::computed_value::T as TransformList;
 use properties::longhands::vertical_align::computed_value::T as VerticalAlign;
 use properties::longhands::visibility::computed_value::T as Visibility;
 #[cfg(feature = "gecko")] use properties::{PropertyDeclarationId, LonghandId};
+use selectors::parser::SelectorParseError;
 #[cfg(feature = "servo")] use servo_atoms::Atom;
 use smallvec::SmallVec;
 use std::cmp;
 #[cfg(feature = "gecko")] use std::collections::HashMap;
 use std::fmt;
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError};
 use super::ComputedValues;
 use values::CSSFloat;
 use values::{Auto, Either};
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
 use values::computed::{BorderCornerRadius, ClipRect};
 use values::computed::{CalcLengthOrPercentage, Color, Context, ComputedValueAsSpecified};
 use values::computed::{LengthOrPercentage, MaxLength, MozLength, Shadow, ToComputedValue};
 use values::generics::{SVGPaint, SVGPaintKind};
@@ -94,36 +95,36 @@ impl TransitionProperty {
                     return true;
                 }
             % endif
         % endfor
         false
     }
 
     /// Parse a transition-property value.
-    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let ident = try!(input.expect_ident());
-        match_ignore_ascii_case! { &ident,
+        (match_ignore_ascii_case! { &ident,
             "all" => Ok(TransitionProperty::All),
             % for prop in data.longhands:
                 % if prop.animatable:
                     "${prop.name}" => Ok(TransitionProperty::${prop.camel_case}),
                 % endif
             % endfor
             % for prop in data.shorthands_except_all():
                 "${prop.name}" => Ok(TransitionProperty::${prop.camel_case}),
             % endfor
             "none" => Err(()),
             _ => {
                 match CSSWideKeyword::from_ident(&ident) {
                     Some(_) => Err(()),
                     None => Ok(TransitionProperty::Unsupported((&*ident).into()))
                 }
             }
-        }
+        }).map_err(|()| SelectorParseError::UnexpectedIdent(ident.into()).into())
     }
 
     /// Get a transition property from a property declaration.
     pub fn from_declaration(declaration: &PropertyDeclaration) -> Option<Self> {
         use properties::LonghandId;
         match *declaration {
             % for prop in data.longhands:
                 % if prop.animatable:
@@ -471,17 +472,17 @@ impl AnimationValue {
                 % endif
             % endfor
         }
     }
 
     /// Construct an AnimationValue from a property declaration
     pub fn from_declaration(decl: &PropertyDeclaration, context: &mut Context,
                             initial: &ComputedValues) -> Option<Self> {
-        use error_reporting::RustLogReporter;
+        use error_reporting::create_error_reporter;
         use properties::LonghandId;
         use properties::DeclaredValue;
 
         match *decl {
             % for prop in data.longhands:
             % if prop.animatable:
             PropertyDeclaration::${prop.camel_case}(ref val) => {
             % if prop.ident in SYSTEM_FONT_LONGHANDS and product == "gecko":
@@ -534,17 +535,17 @@ impl AnimationValue {
                     % if not prop.animatable:
                     LonghandId::${prop.camel_case} => None,
                     % endif
                     % endfor
                 }
             },
             PropertyDeclaration::WithVariables(id, ref variables) => {
                 let custom_props = context.style().custom_properties();
-                let reporter = RustLogReporter;
+                let reporter = create_error_reporter();
                 match id {
                     % for prop in data.longhands:
                     % if prop.animatable:
                     LonghandId::${prop.camel_case} => {
                         let mut result = None;
                         let quirks_mode = context.quirks_mode;
                         ::properties::substitute_variables_${prop.ident}_slow(
                             &variables.css,
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -123,27 +123,30 @@
             match (computed.0, computed.1) {
                 (RepeatKeyword::Repeat, RepeatKeyword::NoRepeat) => SpecifiedValue::RepeatX,
                 (RepeatKeyword::NoRepeat, RepeatKeyword::Repeat) => SpecifiedValue::RepeatY,
                 (horizontal, vertical) => SpecifiedValue::Other(horizontal, Some(vertical)),
             }
         }
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let ident = input.expect_ident()?;
-        match_ignore_ascii_case! { &ident,
+        (match_ignore_ascii_case! { &ident,
             "repeat-x" => Ok(SpecifiedValue::RepeatX),
             "repeat-y" => Ok(SpecifiedValue::RepeatY),
-            _ => {
-                let horizontal = try!(RepeatKeyword::from_ident(&ident));
-                let vertical = input.try(RepeatKeyword::parse).ok();
-                Ok(SpecifiedValue::Other(horizontal, vertical))
-            }
-        }
+            _ => Err(()),
+        }).or_else(|()| {
+            let horizontal: Result<_, ParseError> = RepeatKeyword::from_ident(&ident)
+                .map_err(|()| SelectorParseError::UnexpectedIdent(ident).into());
+            let horizontal = try!(horizontal);
+            let vertical = input.try(RepeatKeyword::parse).ok();
+            Ok(SpecifiedValue::Other(horizontal, vertical))
+        })
     }
 </%helpers:vector_longhand>
 
 ${helpers.single_keyword("background-attachment",
                          "scroll fixed" + (" local" if product == "gecko" else ""),
                          vector=True,
                          gecko_constant_prefix="NS_STYLE_IMAGELAYER_ATTACHMENT",
                          spec="https://drafts.csswg.org/css-backgrounds/#the-background-attachment",
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -156,31 +156,31 @@
                     computed_value::T(None) => {
                         SpecifiedValue::None
                     }
                 }
             }
         }
 
         #[inline]
-        pub fn parse(context: &ParserContext, input: &mut Parser)
-                     -> Result<SpecifiedValue, ()> {
+        pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                             -> Result<SpecifiedValue, ParseError<'i>> {
             if input.try(|input| input.expect_ident_matching("none")).is_ok() {
                 return Ok(SpecifiedValue::None)
             }
 
             let mut result = Vec::new();
             while let Ok(value) = input.try(|i| RGBAColor::parse(context, i)) {
                 result.push(value);
             }
 
             if !result.is_empty() {
                 Ok(SpecifiedValue::Colors(result))
             } else {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             }
         }
     </%helpers:longhand>
 % endfor
 
 ${helpers.single_keyword("box-decoration-break", "slice clone",
                          gecko_enum_prefix="StyleBoxDecorationBreak",
                          gecko_inexhaustive=True,
@@ -267,17 +267,18 @@
             computed_value::T(self.0, self.1.unwrap_or(self.0))
         }
         #[inline]
         fn from_computed_value(computed: &computed_value::T) -> Self {
             SpecifiedValue(computed.0, Some(computed.1))
         }
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let first = try!(RepeatKeyword::parse(input));
         let second = input.try(RepeatKeyword::parse).ok();
 
         Ok(SpecifiedValue(first, second))
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("border-image-width", "BorderImageWidth",
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -108,26 +108,27 @@
 
     /// The initial display value.
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T::${to_rust_ident(values[0])}
     }
 
     /// Parse a display value.
-    pub fn parse(_context: &ParserContext, input: &mut Parser)
-                 -> Result<SpecifiedValue, ()> {
-        match_ignore_ascii_case! { &try!(input.expect_ident()),
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
+        let ident = try!(input.expect_ident());
+        (match_ignore_ascii_case! { &ident,
             % for value in values:
                 "${value}" => {
                     Ok(computed_value::T::${to_rust_ident(value)})
                 },
             % endfor
             _ => Err(())
-        }
+        }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
     % if product == "servo":
         fn cascade_property_custom(_declaration: &PropertyDeclaration,
                                    _inherited_style: &ComputedValues,
                                    context: &mut computed::Context,
@@ -287,26 +288,28 @@
                     SpecifiedValue::${to_rust_ident(keyword)} => dest.write_str("${keyword}"),
                 % endfor
                 SpecifiedValue::LengthOrPercentage(ref value) => value.to_css(dest),
             }
         }
     }
     /// baseline | sub | super | top | text-top | middle | bottom | text-bottom
     /// | <percentage> | <length>
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         input.try(|i| specified::LengthOrPercentage::parse_quirky(context, i, AllowQuirks::Yes))
         .map(SpecifiedValue::LengthOrPercentage)
         .or_else(|_| {
-            match_ignore_ascii_case! { &try!(input.expect_ident()),
+            let ident = try!(input.expect_ident());
+            (match_ignore_ascii_case! { &ident,
                 % for keyword in vertical_align_keywords:
                     "${keyword}" => Ok(SpecifiedValue::${to_rust_ident(keyword)}),
                 % endfor
                 _ => Err(())
-            }
+            }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
         })
     }
 
     /// The computed value for `vertical-align`.
     pub mod computed_value {
         use std::fmt;
         use style_traits::ToCss;
         use values::computed;
@@ -495,27 +498,29 @@
                 name.to_css(dest)
             } else {
                 dest.write_str("none")
             }
         }
     }
 
     impl Parse for SpecifiedValue {
-        fn parse(context: &ParserContext, input: &mut ::cssparser::Parser) -> Result<Self, ()> {
+        fn parse<'i, 't>(context: &ParserContext, input: &mut ::cssparser::Parser<'i, 't>)
+                         -> Result<Self, ParseError<'i>> {
             if let Ok(name) = input.try(|input| KeyframesName::parse(context, input)) {
                 Ok(SpecifiedValue(Some(name)))
             } else {
-                input.expect_ident_matching("none").map(|()| SpecifiedValue(None))
+                input.expect_ident_matching("none").map(|_| SpecifiedValue(None)).map_err(|e| e.into())
             }
         }
     }
     no_viewport_percentage!(SpecifiedValue);
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         SpecifiedValue::parse(context, input)
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 </%helpers:vector_longhand>
 
 ${helpers.predefined_type("animation-duration",
                           "Time",
@@ -555,24 +560,25 @@
     #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
     #[derive(Debug, Clone, PartialEq, ToCss)]
     pub enum SpecifiedValue {
         Number(f32),
         Infinite,
     }
 
     impl Parse for SpecifiedValue {
-        fn parse(_context: &ParserContext, input: &mut ::cssparser::Parser) -> Result<Self, ()> {
+        fn parse<'i, 't>(_context: &ParserContext, input: &mut ::cssparser::Parser<'i, 't>)
+                         -> Result<Self, ParseError<'i>> {
             if input.try(|input| input.expect_ident_matching("infinite")).is_ok() {
                 return Ok(SpecifiedValue::Infinite)
             }
 
             let number = try!(input.expect_number());
             if number < 0.0 {
-                return Err(());
+                return Err(StyleParseError::UnspecifiedError.into());
             }
 
             Ok(SpecifiedValue::Number(number))
         }
     }
 
     no_viewport_percentage!(SpecifiedValue);
 
@@ -582,17 +588,18 @@
     }
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         SpecifiedValue::Number(1.0)
     }
 
     #[inline]
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         SpecifiedValue::parse(context, input)
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 </%helpers:vector_longhand>
 
 <% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %>
 ${helpers.single_keyword("animation-direction",
@@ -943,50 +950,50 @@
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(None)
     }
 
     // Allow unitless zero angle for rotate() and skew() to align with gecko
-    fn parse_internal(context: &ParserContext, input: &mut Parser, prefixed: bool)
-        -> Result<SpecifiedValue,()> {
+    fn parse_internal<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>, prefixed: bool)
+        -> Result<SpecifiedValue,ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue(Vec::new()))
         }
 
         let mut result = Vec::new();
         loop {
             let name = match input.try(|i| i.expect_function()) {
                 Ok(name) => name,
                 Err(_) => break,
             };
-            match_ignore_ascii_case! {
+            let valid_fn = match_ignore_ascii_case! {
                 &name,
                 "matrix" => {
                     try!(input.parse_nested_block(|input| {
                         // Standard matrix parsing.
                         if !prefixed {
                             let values = try!(input.parse_comma_separated(|input| {
                                 specified::parse_number(context, input)
                             }));
                             if values.len() != 6 {
-                                return Err(())
+                                return Err(StyleParseError::UnspecifiedError.into())
                             }
 
                             result.push(SpecifiedOperation::Matrix {
                                 a: values[0],
                                 b: values[1],
                                 c: values[2],
                                 d: values[3],
                                 e: values[4],
                                 f: values[5],
                             });
-                            return Ok(());
+                            return Ok(true);
                         }
 
                         // Non-standard prefixed matrix parsing.
                         //
                         // -moz-transform accepts LengthOrPercentageOrNumber in the
                         //  nondiagonal homogeneous components. transform accepts only number.
                         let mut values = Vec::with_capacity(4);
                         let mut lengths = Vec::with_capacity(2);
@@ -1009,35 +1016,35 @@
                         result.push(SpecifiedOperation::PrefixedMatrix {
                             a: values[0],
                             b: values[1],
                             c: values[2],
                             d: values[3],
                             e: lengths[0].clone(),
                             f: lengths[1].clone(),
                         });
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "matrix3d" => {
                     try!(input.parse_nested_block(|input| {
                         // Standard matrix3d parsing.
                         if !prefixed {
                             let values = try!(input.parse_comma_separated(|i| specified::parse_number(context, i)));
                             if values.len() != 16 {
-                                return Err(())
+                                return Err(StyleParseError::UnspecifiedError.into())
                             }
 
                             result.push(SpecifiedOperation::Matrix3D {
                                 m11: values[ 0], m12: values[ 1], m13: values[ 2], m14: values[ 3],
                                 m21: values[ 4], m22: values[ 5], m23: values[ 6], m24: values[ 7],
                                 m31: values[ 8], m32: values[ 9], m33: values[10], m34: values[11],
                                 m41: values[12], m42: values[13], m43: values[14], m44: values[15]
                             });
-                            return Ok(());
+                            return Ok(true);
                         }
 
                         // Non-standard prefixed matrix3d parsing.
                         //
                         // -moz-transform accepts LengthOrPercentageOrNumber in the
                         //  nondiagonal homogeneous components. transform accepts only number.
                         let mut values = Vec::with_capacity(13);
                         let mut lops = Vec::with_capacity(2);
@@ -1064,203 +1071,208 @@
 
                         result.push(SpecifiedOperation::PrefixedMatrix3D {
                             m11: values[ 0], m12: values[ 1], m13: values[ 2], m14: values[ 3],
                             m21: values[ 4], m22: values[ 5], m23: values[ 6], m24: values[ 7],
                             m31: values[ 8], m32: values[ 9], m33: values[10], m34: values[11],
                             m41: lops[0].clone(), m42: lops[1].clone(), m43: length_or_number.unwrap(),
                             m44: values[12]
                         });
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "translate" => {
                     try!(input.parse_nested_block(|input| {
                         let sx = try!(specified::LengthOrPercentage::parse(context, input));
                         if input.try(|input| input.expect_comma()).is_ok() {
                             let sy = try!(specified::LengthOrPercentage::parse(context, input));
                             result.push(SpecifiedOperation::Translate(sx, Some(sy)));
                         } else {
                             result.push(SpecifiedOperation::Translate(sx, None));
                         }
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "translatex" => {
                     try!(input.parse_nested_block(|input| {
                         let tx = try!(specified::LengthOrPercentage::parse(context, input));
                         result.push(SpecifiedOperation::TranslateX(tx));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "translatey" => {
                     try!(input.parse_nested_block(|input| {
                         let ty = try!(specified::LengthOrPercentage::parse(context, input));
                         result.push(SpecifiedOperation::TranslateY(ty));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "translatez" => {
                     try!(input.parse_nested_block(|input| {
                         let tz = try!(specified::Length::parse(context, input));
                         result.push(SpecifiedOperation::TranslateZ(tz));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "translate3d" => {
                     try!(input.parse_nested_block(|input| {
                         let tx = try!(specified::LengthOrPercentage::parse(context, input));
                         try!(input.expect_comma());
                         let ty = try!(specified::LengthOrPercentage::parse(context, input));
                         try!(input.expect_comma());
                         let tz = try!(specified::Length::parse(context, input));
                         result.push(SpecifiedOperation::Translate3D(tx, ty, tz));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "scale" => {
                     try!(input.parse_nested_block(|input| {
                         let sx = try!(specified::parse_number(context, input));
                         if input.try(|input| input.expect_comma()).is_ok() {
                             let sy = try!(specified::parse_number(context, input));
                             result.push(SpecifiedOperation::Scale(sx, Some(sy)));
                         } else {
                             result.push(SpecifiedOperation::Scale(sx, None));
                         }
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "scalex" => {
                     try!(input.parse_nested_block(|input| {
                         let sx = try!(specified::parse_number(context, input));
                         result.push(SpecifiedOperation::ScaleX(sx));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "scaley" => {
                     try!(input.parse_nested_block(|input| {
                         let sy = try!(specified::parse_number(context, input));
                         result.push(SpecifiedOperation::ScaleY(sy));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "scalez" => {
                     try!(input.parse_nested_block(|input| {
                         let sz = try!(specified::parse_number(context, input));
                         result.push(SpecifiedOperation::ScaleZ(sz));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "scale3d" => {
                     try!(input.parse_nested_block(|input| {
                         let sx = try!(specified::parse_number(context, input));
                         try!(input.expect_comma());
                         let sy = try!(specified::parse_number(context, input));
                         try!(input.expect_comma());
                         let sz = try!(specified::parse_number(context, input));
                         result.push(SpecifiedOperation::Scale3D(sx, sy, sz));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "rotate" => {
                     try!(input.parse_nested_block(|input| {
                         let theta = try!(specified::Angle::parse_with_unitless(context, input));
                         result.push(SpecifiedOperation::Rotate(theta));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "rotatex" => {
                     try!(input.parse_nested_block(|input| {
                         let theta = try!(specified::Angle::parse_with_unitless(context, input));
                         result.push(SpecifiedOperation::RotateX(theta));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "rotatey" => {
                     try!(input.parse_nested_block(|input| {
                         let theta = try!(specified::Angle::parse_with_unitless(context, input));
                         result.push(SpecifiedOperation::RotateY(theta));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "rotatez" => {
                     try!(input.parse_nested_block(|input| {
                         let theta = try!(specified::Angle::parse_with_unitless(context, input));
                         result.push(SpecifiedOperation::RotateZ(theta));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "rotate3d" => {
                     try!(input.parse_nested_block(|input| {
                         let ax = try!(specified::parse_number(context, input));
                         try!(input.expect_comma());
                         let ay = try!(specified::parse_number(context, input));
                         try!(input.expect_comma());
                         let az = try!(specified::parse_number(context, input));
                         try!(input.expect_comma());
                         let theta = try!(specified::Angle::parse_with_unitless(context, input));
                         // TODO(gw): Check the axis can be normalized!!
                         result.push(SpecifiedOperation::Rotate3D(ax, ay, az, theta));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "skew" => {
                     try!(input.parse_nested_block(|input| {
                         let theta_x = try!(specified::Angle::parse_with_unitless(context, input));
                         if input.try(|input| input.expect_comma()).is_ok() {
                             let theta_y = try!(specified::Angle::parse_with_unitless(context, input));
                             result.push(SpecifiedOperation::Skew(theta_x, Some(theta_y)));
                         } else {
                             result.push(SpecifiedOperation::Skew(theta_x, None));
                         }
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "skewx" => {
                     try!(input.parse_nested_block(|input| {
                         let theta_x = try!(specified::Angle::parse_with_unitless(context, input));
                         result.push(SpecifiedOperation::SkewX(theta_x));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "skewy" => {
                     try!(input.parse_nested_block(|input| {
                         let theta_y = try!(specified::Angle::parse_with_unitless(context, input));
                         result.push(SpecifiedOperation::SkewY(theta_y));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
                 "perspective" => {
                     try!(input.parse_nested_block(|input| {
                         let d = try!(specified::Length::parse_non_negative(context, input));
                         result.push(SpecifiedOperation::Perspective(d));
-                        Ok(())
+                        Ok(true)
                     }))
                 },
-                _ => return Err(())
+                _ => false
+            };
+            if !valid_fn {
+                return Err(StyleParseError::UnexpectedFunction(name).into());
             }
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue(result))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     /// Parses `transform` property.
     #[inline]
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         parse_internal(context, input, false)
     }
 
     /// Parses `-moz-transform` property. This prefixed property also accepts LengthOrPercentage
     /// in the nondiagonal homogeneous components of matrix and matrix3d.
     #[inline]
-    pub fn parse_prefixed(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse_prefixed<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                  -> Result<SpecifiedValue,ParseError<'i>> {
         parse_internal(context, input, true)
     }
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
@@ -1766,44 +1778,46 @@
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T::empty()
     }
 
     /// none | strict | content | [ size || layout || style || paint ]
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut result = SpecifiedValue::empty();
 
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(result)
         }
         if input.try(|input| input.expect_ident_matching("strict")).is_ok() {
             result.insert(STRICT | STRICT_BITS);
             return Ok(result)
         }
 
         while let Ok(name) = input.try(|input| input.expect_ident()) {
             let flag = match_ignore_ascii_case! { &name,
-                "layout" => LAYOUT,
-                "style" => STYLE,
-                "paint" => PAINT,
-                _ => return Err(())
+                "layout" => Some(LAYOUT),
+                "style" => Some(STYLE),
+                "paint" => Some(PAINT),
+                _ => None
             };
-            if result.contains(flag) {
-                return Err(())
-            }
+            let flag = match flag {
+                Some(flag) if !result.contains(flag) => flag,
+                _ => return Err(SelectorParseError::UnexpectedIdent(name).into())
+            };
             result.insert(flag);
         }
 
         if !result.is_empty() {
             Ok(result)
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 </%helpers:longhand>
 
 // Non-standard
 ${helpers.single_keyword("-moz-appearance",
                          """none button button-arrow-down button-arrow-next button-arrow-previous button-arrow-up
                             button-bevel button-focus caret checkbox checkbox-container checkbox-label checkmenuitem
@@ -1895,28 +1909,33 @@
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T::Auto
     }
 
     /// auto | <animateable-feature>#
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("auto")).is_ok() {
             Ok(computed_value::T::Auto)
         } else {
             input.parse_comma_separated(|i| {
                 let ident = i.expect_ident()?;
-                match_ignore_ascii_case! { &ident,
+                let bad_keyword = match_ignore_ascii_case! { &ident,
                     "will-change" | "none" | "all" | "auto" |
-                    "initial" | "inherit" | "unset" | "default" => return Err(()),
-                    _ => {},
+                    "initial" | "inherit" | "unset" | "default" => true,
+                    _ => false,
+                };
+                if bad_keyword {
+                    Err(SelectorParseError::UnexpectedIdent(ident.into()).into())
+                } else {
+                    Ok(Atom::from(ident))
                 }
-                Ok((Atom::from(ident)))
             }).map(SpecifiedValue::AnimateableFeatures)
         }
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("shape-outside", "basic_shape::FloatAreaShape",
                           "generics::basic_shape::ShapeSource::None",
                           products="gecko", boxed="True",
@@ -1971,19 +1990,20 @@
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         TOUCH_ACTION_AUTO
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let ident = input.expect_ident()?;
-        match_ignore_ascii_case! { &ident,
+        (match_ignore_ascii_case! { &ident,
             "auto" => Ok(TOUCH_ACTION_AUTO),
             "none" => Ok(TOUCH_ACTION_NONE),
             "manipulation" => Ok(TOUCH_ACTION_MANIPULATION),
             "pan-x" => {
                 if input.try(|i| i.expect_ident_matching("pan-y")).is_ok() {
                     Ok(TOUCH_ACTION_PAN_X | TOUCH_ACTION_PAN_Y)
                 } else {
                     Ok(TOUCH_ACTION_PAN_X)
@@ -1992,14 +2012,14 @@
             "pan-y" => {
                 if input.try(|i| i.expect_ident_matching("pan-x")).is_ok() {
                     Ok(TOUCH_ACTION_PAN_X | TOUCH_ACTION_PAN_Y)
                 } else {
                     Ok(TOUCH_ACTION_PAN_Y)
                 }
             },
             _ => Err(()),
-        }
+        }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
     }
 
     #[cfg(feature = "gecko")]
     impl_bitflags_conversions!(SpecifiedValue);
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/color.mako.rs
+++ b/servo/components/style/properties/longhand/color.mako.rs
@@ -38,17 +38,18 @@
     pub mod computed_value {
         use cssparser;
         pub type T = cssparser::RGBA;
     }
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         RGBA::new(0, 0, 0, 255) // black
     }
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         Color::parse_quirky(context, input, AllowQuirks::Yes).map(SpecifiedValue)
     }
 
     // FIXME(#15973): Add servo support for system colors
     % if product == "gecko":
         <%
             # These are actually parsed. See nsCSSProps::kColorKTable
             system_colors = """activeborder activecaption appworkspace background buttonface
@@ -113,28 +114,28 @@
 
             #[inline]
             fn from_computed_value(_: &Self::ComputedValue) -> Self {
                 unreachable!()
             }
         }
 
         impl SystemColor {
-            pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+            pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
                 #[cfg(feature = "gecko")]
                 use std::ascii::AsciiExt;
                 static PARSE_ARRAY: &'static [(&'static str, SystemColor); ${len(system_colors)}] = &[
                     % for color in system_colors:
                         ("${color}", LookAndFeel_ColorID::eColorID_${to_rust_ident(color)}),
                     % endfor
                 ];
 
                 let ident = input.expect_ident()?;
                 for &(name, color) in PARSE_ARRAY.iter() {
                     if name.eq_ignore_ascii_case(&ident) {
                         return Ok(color)
                     }
                 }
-                Err(())
+                Err(SelectorParseError::UnexpectedIdent(ident).into())
             }
         }
     % endif
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/counters.mako.rs
+++ b/servo/components/style/properties/longhand/counters.mako.rs
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %>
 
 <%helpers:longhand name="content" boxed="True" animation_value_type="none"
                    spec="https://drafts.csswg.org/css-content/#propdef-content">
-    use cssparser::Token;
     use values::computed::ComputedValueAsSpecified;
     #[cfg(feature = "gecko")]
     use values::generics::CounterStyleOrNone;
     #[cfg(feature = "gecko")]
     use values::specified::url::SpecifiedUrl;
     #[cfg(feature = "gecko")]
     use values::specified::Attr;
 
@@ -152,18 +151,18 @@
             input.expect_comma()?;
             CounterStyleOrNone::parse(context, input)
         }).unwrap_or(CounterStyleOrNone::decimal())
     }
 
     // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote |
     // no-close-quote ]+
     // TODO: <uri>, attr(<identifier>)
-    pub fn parse(context: &ParserContext, input: &mut Parser)
-                 -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             return Ok(SpecifiedValue::Normal)
         }
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue::None)
         }
         % if product == "gecko":
             if input.try(|input| input.expect_ident_matching("-moz-alt-content")).is_ok() {
@@ -180,66 +179,71 @@
                     continue;
                 }
             % endif
             match input.next() {
                 Ok(Token::QuotedString(value)) => {
                     content.push(ContentItem::String(value.into_owned()))
                 }
                 Ok(Token::Function(name)) => {
-                    content.push(try!(match_ignore_ascii_case! { &name,
-                        "counter" => input.parse_nested_block(|input| {
+                    let result = match_ignore_ascii_case! { &name,
+                        "counter" => Some(input.parse_nested_block(|input| {
                             let name = try!(input.expect_ident()).into_owned();
                             let style = parse_counter_style(context, input);
                             Ok(ContentItem::Counter(name, style))
-                        }),
-                        "counters" => input.parse_nested_block(|input| {
+                        })),
+                        "counters" => Some(input.parse_nested_block(|input| {
                             let name = try!(input.expect_ident()).into_owned();
                             try!(input.expect_comma());
                             let separator = try!(input.expect_string()).into_owned();
                             let style = parse_counter_style(context, input);
                             Ok(ContentItem::Counters(name, separator, style))
-                        }),
+                        })),
                         % if product == "gecko":
-                            "attr" => input.parse_nested_block(|input| {
+                            "attr" => Some(input.parse_nested_block(|input| {
                                 Ok(ContentItem::Attr(Attr::parse_function(context, input)?))
-                            }),
+                            })),
                         % endif
-                        _ => return Err(())
-                    }));
+                        _ => None
+                    };
+                    match result {
+                        Some(result) => content.push(try!(result)),
+                        None => return Err(StyleParseError::UnexpectedFunction(name).into())
+                    }
                 }
                 Ok(Token::Ident(ident)) => {
-                    match_ignore_ascii_case! { &ident,
-                        "open-quote" => content.push(ContentItem::OpenQuote),
-                        "close-quote" => content.push(ContentItem::CloseQuote),
-                        "no-open-quote" => content.push(ContentItem::NoOpenQuote),
-                        "no-close-quote" => content.push(ContentItem::NoCloseQuote),
+                    let valid = match_ignore_ascii_case! { &ident,
+                        "open-quote" => { content.push(ContentItem::OpenQuote); true },
+                        "close-quote" => { content.push(ContentItem::CloseQuote); true },
+                        "no-open-quote" => { content.push(ContentItem::NoOpenQuote); true },
+                        "no-close-quote" => { content.push(ContentItem::NoCloseQuote); true },
 
-                        _ => return Err(())
+                        _ => false,
+                    };
+                    if !valid {
+                        return Err(SelectorParseError::UnexpectedIdent(ident).into())
                     }
                 }
                 Err(_) => break,
-                _ => return Err(())
+                Ok(t) => return Err(BasicParseError::UnexpectedToken(t).into())
             }
         }
         if content.is_empty() {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
         Ok(SpecifiedValue::Items(content))
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="counter-increment" animation_value_type="none"
                    spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment">
     use std::fmt;
     use style_traits::ToCss;
     use values::CustomIdent;
 
-    use cssparser::Token;
-
     #[derive(Debug, Clone, PartialEq)]
     pub struct SpecifiedValue(pub Vec<(CustomIdent, specified::Integer)>);
 
     pub mod computed_value {
         use std::fmt;
         use style_traits::ToCss;
         use values::CustomIdent;
 
@@ -310,46 +314,49 @@
                 dest.write_str(" ")?;
                 value.to_css(dest)?;
             }
 
             Ok(())
         }
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         parse_common(context, 1, input)
     }
 
-    pub fn parse_common(context: &ParserContext, default_value: i32, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse_common<'i, 't>(context: &ParserContext, default_value: i32, input: &mut Parser<'i, 't>)
+                                -> Result<SpecifiedValue, ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue(Vec::new()))
         }
 
         let mut counters = Vec::new();
         loop {
             let counter_name = match input.next() {
                 Ok(Token::Ident(ident)) => CustomIdent::from_ident(ident, &["none"])?,
-                Ok(_) => return Err(()),
+                Ok(t) => return Err(BasicParseError::UnexpectedToken(t).into()),
                 Err(_) => break,
             };
             let counter_delta = input.try(|input| specified::parse_integer(context, input))
                                      .unwrap_or(specified::Integer::new(default_value));
             counters.push((counter_name, counter_delta))
         }
 
         if !counters.is_empty() {
             Ok(SpecifiedValue(counters))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="counter-reset" animation_value_type="none"
                    spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset">
     pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value};
     use super::counter_increment::parse_common;
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         parse_common(context, 0, input)
     }
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/effects.mako.rs
+++ b/servo/components/style/properties/longhand/effects.mako.rs
@@ -22,17 +22,18 @@
     pub type SpecifiedValue = specified::Shadow;
 
     pub mod computed_value {
         use values::computed::Shadow;
 
         pub type T = Shadow;
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<specified::Shadow, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<specified::Shadow, ParseError<'i>> {
         specified::Shadow::parse(context, input, false)
     }
 </%helpers:vector_longhand>
 
 ${helpers.predefined_type("clip",
                           "ClipRectOrAuto",
                           "computed::ClipRectOrAuto::auto()",
                           animation_value_type="ComputedValue",
@@ -258,17 +259,18 @@
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T::new(Vec::new())
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut filters = Vec::new();
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue(filters))
         }
         loop {
             % if product == "gecko":
                 if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) {
                     filters.push(SpecifiedFilter::Url(url));
@@ -285,33 +287,34 @@
                         "invert" => parse_factor(input).map(SpecifiedFilter::Invert),
                         "opacity" => parse_factor(input).map(SpecifiedFilter::Opacity),
                         "saturate" => parse_factor(input).map(SpecifiedFilter::Saturate),
                         "sepia" => parse_factor(input).map(SpecifiedFilter::Sepia),
                         % if product == "gecko":
                         "drop-shadow" => specified::Shadow::parse(context, input, true)
                                              .map(SpecifiedFilter::DropShadow),
                         % endif
-                        _ => Err(())
+                        _ => Err(StyleParseError::UnexpectedFunction(function_name.clone()).into())
                     }
                 })));
             } else if filters.is_empty() {
-                return Err(())
+                return Err(StyleParseError::UnspecifiedError.into())
             } else {
                 return Ok(SpecifiedValue(filters))
             }
         }
     }
 
-    fn parse_factor(input: &mut Parser) -> Result<::values::CSSFloat, ()> {
+    fn parse_factor<'i, 't>(input: &mut Parser<'i, 't>) -> Result<::values::CSSFloat, ParseError<'i>> {
         use cssparser::Token;
         match input.next() {
             Ok(Token::Number(value)) if value.value.is_sign_positive() => Ok(value.value),
             Ok(Token::Percentage(value)) if value.unit_value.is_sign_positive() => Ok(value.unit_value),
-            _ => Err(())
+            Ok(t) => Err(BasicParseError::UnexpectedToken(t).into()),
+            Err(e) => Err(e.into())
         }
     }
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             computed_value::T{ filters: self.0.iter().map(|value| {
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -87,17 +87,17 @@ macro_rules! impl_gecko_keyword_from_tra
     use style_traits::ToCss;
 
     no_viewport_percentage!(SpecifiedValue);
 
     pub mod computed_value {
         use cssparser::{CssStringWriter, Parser, serialize_identifier};
         use std::fmt::{self, Write};
         use Atom;
-        use style_traits::ToCss;
+        use style_traits::{ToCss, ParseError};
         pub use self::FontFamily as SingleComputedValue;
 
         #[derive(Debug, PartialEq, Eq, Clone, Hash)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
         pub enum FontFamily {
             FamilyName(FamilyName),
             Generic(Atom),
         }
@@ -149,17 +149,17 @@ macro_rules! impl_gecko_keyword_from_tra
                 // quoted by default.
                 FontFamily::FamilyName(FamilyName {
                     name: input,
                     quoted: true,
                 })
             }
 
             /// Parse a font-family value
-            pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+            pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
                 if let Ok(value) = input.try(|input| input.expect_string()) {
                     return Ok(FontFamily::FamilyName(FamilyName {
                         name: Atom::from(&*value),
                         quoted: true,
                     }))
                 }
                 let first_ident = try!(input.expect_ident());
 
@@ -289,17 +289,18 @@ macro_rules! impl_gecko_keyword_from_tra
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(vec![FontFamily::Generic(atom!("serif"))])
     }
 
     /// <family-name>#
     /// <family-name> = <string> | [ <ident>+ ]
     /// TODO: <generic-family>
-    pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         SpecifiedValue::parse(input)
     }
 
     #[derive(Debug, Clone, PartialEq, Eq, Hash)]
     pub enum SpecifiedValue {
         Values(Vec<FontFamily>),
         System(SystemFont),
     }
@@ -343,17 +344,17 @@ macro_rules! impl_gecko_keyword_from_tra
         pub fn get_system(&self) -> Option<SystemFont> {
             if let SpecifiedValue::System(s) = *self {
                 Some(s)
             } else {
                 None
             }
         }
 
-        pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+        pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
             input.parse_comma_separated(|input| FontFamily::parse(input)).map(SpecifiedValue::Values)
         }
     }
 
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
@@ -369,21 +370,21 @@ macro_rules! impl_gecko_keyword_from_tra
                 _ => Ok(())
             }
         }
     }
 
     /// `FamilyName::parse` is based on `FontFamily::parse` and not the other way around
     /// because we want the former to exclude generic family keywords.
     impl Parse for FamilyName {
-        fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
             match FontFamily::parse(input) {
                 Ok(FontFamily::FamilyName(name)) => Ok(name),
-                Ok(FontFamily::Generic(_)) |
-                Err(()) => Err(())
+                Ok(FontFamily::Generic(_)) => Err(StyleParseError::UnspecifiedError.into()),
+                Err(e) => Err(e)
             }
         }
     }
 </%helpers:longhand>
 
 ${helpers.single_keyword_system("font-style",
                                 "normal italic oblique",
                                 gecko_constant_prefix="NS_FONT_STYLE",
@@ -439,27 +440,31 @@ macro_rules! impl_gecko_keyword_from_tra
                     SpecifiedValue::Weight${weight} => dest.write_str("${weight}"),
                 % endfor
                 SpecifiedValue::System(_) => Ok(())
             }
         }
     }
 
     /// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
-        input.try(|input| {
-            match_ignore_ascii_case! { &try!(input.expect_ident()),
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
+        let result: Result<_, ParseError> = input.try(|input| {
+            let ident = try!(input.expect_ident());
+            (match_ignore_ascii_case! { &ident,
                 "normal" => Ok(SpecifiedValue::Normal),
                 "bold" => Ok(SpecifiedValue::Bold),
                 "bolder" => Ok(SpecifiedValue::Bolder),
                 "lighter" => Ok(SpecifiedValue::Lighter),
                 _ => Err(())
-            }
-        }).or_else(|()| {
+            }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
+        });
+        result.or_else(|_| {
             SpecifiedValue::from_int(input.expect_integer()?)
+                .map_err(|()| StyleParseError::UnspecifiedError.into())
         })
     }
 
     impl SpecifiedValue {
         pub fn from_int(kw: i32) -> Result<Self, ()> {
             match kw {
                 % for weight in range(100, 901, 100):
                     ${weight} => Ok(SpecifiedValue::Weight${weight}),
@@ -484,25 +489,25 @@ macro_rules! impl_gecko_keyword_from_tra
             } else {
                 None
             }
         }
     }
 
     /// Used in @font-face, where relative keywords are not allowed.
     impl Parse for computed_value::T {
-        fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
             match parse(context, input)? {
                 % for weight in range(100, 901, 100):
                     SpecifiedValue::Weight${weight} => Ok(computed_value::T::Weight${weight}),
                 % endfor
                 SpecifiedValue::Normal => Ok(computed_value::T::Weight400),
                 SpecifiedValue::Bold => Ok(computed_value::T::Weight700),
                 SpecifiedValue::Bolder |
-                SpecifiedValue::Lighter => Err(()),
+                SpecifiedValue::Lighter => Err(StyleParseError::UnspecifiedError.into()),
                 SpecifiedValue::System(..) => unreachable!(),
             }
         }
     }
 
     pub mod computed_value {
         #[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
@@ -678,27 +683,28 @@ macro_rules! impl_gecko_keyword_from_tra
         // This is not a real font keyword and will not parse
         // HTML font-size 7 corresponds to this value
         XXXLarge = 7,
     }
 
     pub use self::KeywordSize::*;
 
     impl KeywordSize {
-        pub fn parse(input: &mut Parser) -> Result<Self, ()> {
-            Ok(match_ignore_ascii_case! {&*input.expect_ident()?,
-                "xx-small" => XXSmall,
-                "x-small" => XSmall,
-                "small" => Small,
-                "medium" => Medium,
-                "large" => Large,
-                "x-large" => XLarge,
-                "xx-large" => XXLarge,
-                _ => return Err(())
-            })
+        pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+            let ident = input.expect_ident()?;
+            (match_ignore_ascii_case! {&*ident,
+                "xx-small" => Ok(XXSmall),
+                "x-small" => Ok(XSmall),
+                "small" => Ok(Small),
+                "medium" => Ok(Medium),
+                "large" => Ok(Large),
+                "x-large" => Ok(XLarge),
+                "xx-large" => Ok(XXLarge),
+                _ => Err(())
+            }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
         }
     }
 
     impl Default for KeywordSize {
         fn default() -> Self {
             Medium
         }
     }
@@ -901,39 +907,41 @@ macro_rules! impl_gecko_keyword_from_tra
         fn from_computed_value(computed: &computed_value::T) -> Self {
                 SpecifiedValue::Length(LengthOrPercentage::Length(
                         ToComputedValue::from_computed_value(computed)
                 ))
         }
     }
 
     /// <length> | <percentage> | <absolute-size> | <relative-size>
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         parse_quirky(context, input, AllowQuirks::No)
     }
 
     /// Parses a font-size, with quirks.
-    pub fn parse_quirky(context: &ParserContext,
-                        input: &mut Parser,
-                        allow_quirks: AllowQuirks)
-                        -> Result<SpecifiedValue, ()> {
+    pub fn parse_quirky<'i, 't>(context: &ParserContext,
+                                input: &mut Parser<'i, 't>,
+                                allow_quirks: AllowQuirks)
+                                -> Result<SpecifiedValue, ParseError<'i>> {
         use self::specified::LengthOrPercentage;
         if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative_quirky(context, i, allow_quirks)) {
             return Ok(SpecifiedValue::Length(lop))
         }
 
         if let Ok(kw) = input.try(KeywordSize::parse) {
             return Ok(SpecifiedValue::Keyword(kw, 1.))
         }
 
-        match_ignore_ascii_case! {&*input.expect_ident()?,
+        let ident = input.expect_ident()?;
+        (match_ignore_ascii_case! {&*ident,
             "smaller" => Ok(SpecifiedValue::Smaller),
             "larger" => Ok(SpecifiedValue::Larger),
             _ => Err(())
-        }
+        }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
     }
 
     impl SpecifiedValue {
         pub fn system_font(f: SystemFont) -> Self {
             SpecifiedValue::System(f)
         }
         pub fn get_system(&self) -> Option<SystemFont> {
             if let SpecifiedValue::System(s) = *self {
@@ -1145,17 +1153,18 @@ macro_rules! impl_gecko_keyword_from_tra
     }
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         SpecifiedValue::None
     }
 
     /// none | <number>
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         use values::specified::Number;
 
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue::None);
         }
 
         Ok(SpecifiedValue::Number(try!(Number::parse_non_negative(context, input))))
     }
@@ -1195,36 +1204,38 @@ macro_rules! impl_gecko_keyword_from_tra
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         SpecifiedValue { weight: true, style: true }
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut result = SpecifiedValue { weight: false, style: false };
-        match_ignore_ascii_case! { &try!(input.expect_ident()),
+        let ident = try!(input.expect_ident());
+        (match_ignore_ascii_case! { &ident,
             "none" => Ok(result),
             "weight" => {
                 result.weight = true;
                 if input.try(|input| input.expect_ident_matching("style")).is_ok() {
                     result.style = true;
                 }
                 Ok(result)
             },
             "style" => {
                 result.style = true;
                 if input.try(|input| input.expect_ident_matching("weight")).is_ok() {
                     result.weight = true;
                 }
                 Ok(result)
             },
             _ => Err(())
-        }
+        }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
     }
 
     #[cfg(feature = "gecko")]
     impl From<u8> for SpecifiedValue {
         fn from(bits: u8) -> SpecifiedValue {
             use gecko_bindings::structs;
 
             SpecifiedValue {
@@ -1359,55 +1370,57 @@ macro_rules! impl_gecko_keyword_from_tra
     /// normal |
     ///  [ stylistic(<feature-value-name>)           ||
     ///    historical-forms                          ||
     ///    styleset(<feature-value-name> #)          ||
     ///    character-variant(<feature-value-name> #) ||
     ///    swash(<feature-value-name>)               ||
     ///    ornaments(<feature-value-name>)           ||
     ///    annotation(<feature-value-name>) ]
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut result = VariantAlternates::empty();
 
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             return Ok(SpecifiedValue::Value(result))
         }
 
         while let Ok(ident) = input.try(|input| input.expect_ident()) {
             let flag = match_ignore_ascii_case! { &ident,
-                "stylistic" => STYLISTIC,
-                "historical-forms" => HISTORICAL_FORMS,
-                "styleset" => STYLESET,
-                "character-variant" => CHARACTER_VARIANT,
-                "swash" => SWASH,
-                "ornaments" => ORNAMENTS,
-                "annotation" => ANNOTATION,
-                _ => return Err(()),
+                "stylistic" => Some(STYLISTIC),
+                "historical-forms" => Some(HISTORICAL_FORMS),
+                "styleset" => Some(STYLESET),
+                "character-variant" => Some(CHARACTER_VARIANT),
+                "swash" => Some(SWASH),
+                "ornaments" => Some(ORNAMENTS),
+                "annotation" => Some(ANNOTATION),
+                _ => None,
             };
-            if result.intersects(flag) {
-                return Err(())
-            }
+            let flag = match flag {
+                Some(flag) if !result.intersects(flag) => flag,
+                _ => return Err(SelectorParseError::UnexpectedIdent(ident).into()),
+            };
             result.insert(flag);
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue::Value(result))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 </%helpers:longhand>
 
 #[cfg(any(feature = "gecko", feature = "testing"))]
 macro_rules! exclusive_value {
     (($value:ident, $set:expr) => $ident:ident) => {
         if $value.intersects($set) {
-            return Err(())
+            None
         } else {
-            $ident
+            Some($ident)
         }
     }
 }
 
 <%helpers:longhand name="font-variant-east-asian" products="gecko" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
@@ -1502,17 +1515,18 @@ macro_rules! exclusive_value {
         SpecifiedValue::Value(VariantEastAsian::empty())
     }
 
     /// normal | [ <east-asian-variant-values> || <east-asian-width-values> || ruby ]
     /// <east-asian-variant-values> = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ]
     /// <east-asian-width-values>   = [ full-width | proportional-width ]
     <% east_asian_variant_values = "JIS78 | JIS83 | JIS90 | JIS04 | SIMPLIFIED | TRADITIONAL" %>
     <% east_asian_width_values = "FULL_WIDTH | PROPORTIONAL_WIDTH" %>
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut result = VariantEastAsian::empty();
 
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             return Ok(SpecifiedValue::Value(result))
         }
 
         while let Ok(ident) = input.try(|input| input.expect_ident()) {
             let flag = match_ignore_ascii_case! { &ident,
@@ -1529,25 +1543,29 @@ macro_rules! exclusive_value {
                 "traditional" =>
                     exclusive_value!((result, ${east_asian_variant_values}) => TRADITIONAL),
                 "full-width" =>
                     exclusive_value!((result, ${east_asian_width_values}) => FULL_WIDTH),
                 "proportional-width" =>
                     exclusive_value!((result, ${east_asian_width_values}) => PROPORTIONAL_WIDTH),
                 "ruby" =>
                     exclusive_value!((result, RUBY) => RUBY),
-                _ => return Err(()),
+                _ => None,
+            };
+            let flag = match flag {
+                Some(flag) => flag,
+                None => return Err(SelectorParseError::UnexpectedIdent(ident).into()),
             };
             result.insert(flag);
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue::Value(result))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     #[cfg(feature = "gecko")]
     impl_gecko_keyword_from_trait!(VariantEastAsian, u16);
 </%helpers:longhand>
 
 <%helpers:longhand name="font-variant-ligatures" products="gecko" animation_value_type="discrete"
@@ -1654,17 +1672,18 @@ macro_rules! exclusive_value {
     /// <common-lig-values>        = [ common-ligatures | no-common-ligatures ]
     /// <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ]
     /// <historical-lig-values>    = [ historical-ligatures | no-historical-ligatures ]
     /// <contextual-alt-values>    = [ contextual | no-contextual ]
     <% common_lig_values = "COMMON_LIGATURES | NO_COMMON_LIGATURES" %>
     <% discretionary_lig_values = "DISCRETIONARY_LIGATURES | NO_DISCRETIONARY_LIGATURES" %>
     <% historical_lig_values = "HISTORICAL_LIGATURES | NO_HISTORICAL_LIGATURES" %>
     <% contextual_alt_values = "CONTEXTUAL | NO_CONTEXTUAL" %>
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut result = VariantLigatures::empty();
 
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             return Ok(SpecifiedValue::Value(result))
         }
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue::Value(NONE))
         }
@@ -1682,25 +1701,29 @@ macro_rules! exclusive_value {
                 "historical-ligatures" =>
                     exclusive_value!((result, ${historical_lig_values}) => HISTORICAL_LIGATURES),
                 "no-historical-ligatures" =>
                     exclusive_value!((result, ${historical_lig_values}) => NO_HISTORICAL_LIGATURES),
                 "contextual" =>
                     exclusive_value!((result, ${contextual_alt_values}) => CONTEXTUAL),
                 "no-contextual" =>
                     exclusive_value!((result, ${contextual_alt_values}) => NO_CONTEXTUAL),
-                _ => return Err(()),
+                _ => None,
+            };
+            let flag = match flag {
+                Some(flag) => flag,
+                None => return Err(SelectorParseError::UnexpectedIdent(ident).into()),
             };
             result.insert(flag);
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue::Value(result))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     #[cfg(feature = "gecko")]
     impl_gecko_keyword_from_trait!(VariantLigatures, u16);
 </%helpers:longhand>
 
 <%helpers:longhand name="font-variant-numeric" products="gecko" animation_value_type="discrete"
@@ -1803,50 +1826,55 @@ macro_rules! exclusive_value {
     ///    ordinal                   ||
     ///    slashed-zero ]
     /// <numeric-figure-values>   = [ lining-nums | oldstyle-nums ]
     /// <numeric-spacing-values>  = [ proportional-nums | tabular-nums ]
     /// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ]
     <% numeric_figure_values = "LINING_NUMS | OLDSTYLE_NUMS" %>
     <% numeric_spacing_values = "PROPORTIONAL_NUMS | TABULAR_NUMS" %>
     <% numeric_fraction_values = "DIAGONAL_FRACTIONS | STACKED_FRACTIONS" %>
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut result = VariantNumeric::empty();
 
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             return Ok(SpecifiedValue::Value(result))
         }
 
         while let Ok(ident) = input.try(|input| input.expect_ident()) {
             let flag = match_ignore_ascii_case! { &ident,
                 "ordinal" =>
                     exclusive_value!((result, ORDINAL) => ORDINAL),
                 "slashed-zero" =>
                     exclusive_value!((result, SLASHED_ZERO) => SLASHED_ZERO),
                 "lining-nums" =>
                     exclusive_value!((result, ${numeric_figure_values}) => LINING_NUMS ),
                 "oldstyle-nums" =>
-                    exclusive_value!((result, ${numeric_figure_values}) => OLDSTYLE_NUMS ),
+                    exclusive_value!((result, ${numeric_figure_values}) => OLDSTYLE_NUMS),
                 "proportional-nums" =>
-                    exclusive_value!((result, ${numeric_spacing_values}) => PROPORTIONAL_NUMS ),
+                    exclusive_value!((result, ${numeric_spacing_values}) => PROPORTIONAL_NUMS),
                 "tabular-nums" =>
-                    exclusive_value!((result, ${numeric_spacing_values}) => TABULAR_NUMS ),
+                    exclusive_value!((result, ${numeric_spacing_values}) => TABULAR_NUMS),
                 "diagonal-fractions" =>
-                    exclusive_value!((result, ${numeric_fraction_values}) => DIAGONAL_FRACTIONS ),
+                    exclusive_value!((result, ${numeric_fraction_values}) => DIAGONAL_FRACTIONS),
                 "stacked-fractions" =>
-                    exclusive_value!((result, ${numeric_fraction_values}) => STACKED_FRACTIONS ),
-                _ => return Err(()),
+                    exclusive_value!((result, ${numeric_fraction_values}) => STACKED_FRACTIONS),
+                _ => None,
+            };
+            let flag = match flag {
+                Some(flag) => flag,
+                None => return Err(SelectorParseError::UnexpectedIdent(ident).into()),
             };
             result.insert(flag);
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue::Value(result))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     #[cfg(feature = "gecko")]
     impl_gecko_keyword_from_trait!(VariantNumeric, u8);
 </%helpers:longhand>
 
 ${helpers.single_keyword_system("font-variant-position",
@@ -1885,17 +1913,18 @@ macro_rules! exclusive_value {
     }
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         SpecifiedValue::Value(FontSettings::Normal)
     }
 
     /// normal | <feature-tag-value>#
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         computed_value::T::parse(context, input).map(SpecifiedValue::Value)
     }
 </%helpers:longhand>
 
 <%
 # This spec link is too long to fit elsewhere
 variation_spec = """\
 https://drafts.csswg.org/css-fonts-4/#low-level-font-variation-settings-control-the-font-variation-settings-property\
@@ -1919,17 +1948,18 @@ https://drafts.csswg.org/css-fonts-4/#lo
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         FontSettings::Normal
     }
 
     /// normal | <feature-tag-value>#
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         computed_value::T::parse(context, input)
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="font-language-override" products="gecko" animation_value_type="discrete"
                    extra_prefixes="moz" boxed="True"
                    spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override">
     use properties::longhands::system_font::SystemFont;
@@ -2049,23 +2079,24 @@ https://drafts.csswg.org/css-fonts-4/#lo
                 } else {
                     unsafe { String::from_utf8_unchecked(buf.to_vec()) }
                 }
             )
         }
     }
 
     /// normal | <string>
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             Ok(SpecifiedValue::Normal)
         } else {
             input.expect_string().map(|cow| {
                 SpecifiedValue::Override(cow.into_owned())
-            })
+            }).map_err(|e| e.into())
         }
     }
 
     #[cfg(feature = "gecko")]
     impl From<u32> for computed_value::T {
         fn from(bits: u32) -> computed_value::T {
             computed_value::T(bits)
         }
@@ -2103,19 +2134,20 @@ https://drafts.csswg.org/css-fonts-4/#lo
         pub struct T(pub Atom);
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(atom!(""))
     }
 
-    pub fn parse(_context: &ParserContext, _input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, _input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         debug_assert!(false, "Should be set directly by presentation attributes only.");
-        Err(())
+        Err(StyleParseError::UnspecifiedError.into())
     }
 </%helpers:longhand>
 
 // MathML properties
 <%helpers:longhand name="-moz-script-size-multiplier" products="gecko" animation_value_type="none"
                    predefined_type="Number" gecko_ffi_name="mScriptSizeMultiplier"
                    spec="Internal (not web-exposed)"
                    internal="True" disable_when_testing="True">
@@ -2128,19 +2160,20 @@ https://drafts.csswg.org/css-fonts-4/#lo
         pub type T = f32;
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         ::gecko_bindings::structs::NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32
     }
 
-    pub fn parse(_context: &ParserContext, _input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, _input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         debug_assert!(false, "Should be set directly by presentation attributes only.");
-        Err(())
+        Err(StyleParseError::UnspecifiedError.into())
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="-moz-script-level" products="gecko" animation_value_type="none"
                    predefined_type="Integer" gecko_ffi_name="mScriptLevel"
                    spec="Internal (not web-exposed)"
                    internal="True" disable_when_testing="True" need_clone="True">
     use std::fmt;
@@ -2201,17 +2234,18 @@ https://drafts.csswg.org/css-fonts-4/#lo
             };
             cmp::min(int, i8::MAX as i32) as i8
         }
         fn from_computed_value(other: &computed_value::T) -> Self {
             SpecifiedValue::Absolute(*other as i32)
         }
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         if let Ok(i) = input.try(|i| i.expect_integer()) {
             return Ok(SpecifiedValue::Relative(i))
         }
         input.expect_ident_matching("auto")?;
         Ok(SpecifiedValue::Auto)
     }
 </%helpers:longhand>
 
@@ -2276,19 +2310,20 @@ https://drafts.csswg.org/css-fonts-4/#lo
         }
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         Au((NS_MATHML_DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * AU_PER_PT) as i32)
     }
 
-    pub fn parse(_context: &ParserContext, _input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, _input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         debug_assert!(false, "Should be set directly by presentation attributes only.");
-        Err(())
+        Err(StyleParseError::UnspecifiedError.into())
     }
 </%helpers:longhand>
 
 
 % if product == "gecko":
     pub mod system_font {
         //! We deal with system fonts here
         //!
@@ -2303,17 +2338,19 @@ https://drafts.csswg.org/css-fonts-4/#lo
         //! the specified system font. When the cascade function (in helpers)
         //! detects that a value has a system font, it will resolve it, and
         //! cache it on the ComputedValues. After this, it can be just fetched
         //! whenever a font longhand on the same element needs the system font.
 
         use app_units::Au;
         use cssparser::Parser;
         use properties::longhands;
+        use selectors::parser::SelectorParseError;
         use std::hash::{Hash, Hasher};
+        use style_traits::ParseError;
         use values::computed::{ToComputedValue, Context};
         <%
             system_fonts = """caption icon menu message-box small-caption status-bar
                               -moz-window -moz-document -moz-workspace -moz-desktop
                               -moz-info -moz-dialog -moz-button -moz-pull-down-menu
                               -moz-list -moz-field""".split()
             kw_font_props = """font_style font_variant_caps font_stretch
                                font_kerning font_variant_position font_variant_alternates
@@ -2422,23 +2459,24 @@ https://drafts.csswg.org/css-fonts-4/#lo
         pub struct ComputedSystemFont {
             % for name in SYSTEM_FONT_LONGHANDS:
                 pub ${name}: longhands::${name}::computed_value::T,
             % endfor
             pub system_font: SystemFont,
         }
 
         impl SystemFont {
-            pub fn parse(input: &mut Parser) -> Result<Self, ()> {
-                Ok(match_ignore_ascii_case! { &*input.expect_ident()?,
+            pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+                let ident = input.expect_ident()?;
+                (match_ignore_ascii_case! { &*ident,
                     % for font in system_fonts:
-                        "${font}" => SystemFont::${to_camel_case(font)},
+                        "${font}" => Ok(SystemFont::${to_camel_case(font)}),
                     % endfor
-                    _ => return Err(())
-                })
+                    _ => Err(())
+                }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
             }
         }
     }
 % else:
     pub mod system_font {
         use cssparser::Parser;
 
         // We don't parse system fonts, but in the interest of not littering
--- a/servo/components/style/properties/longhand/inherited_box.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_box.mako.rs
@@ -169,28 +169,29 @@
                     }
                     Ok(())
                 },
             }
         }
     }
 
     // from-image | <angle> | [<angle>? flip]
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("from-image")).is_ok() {
             // Handle from-image
             Ok(SpecifiedValue { angle: None, flipped: false })
         } else if input.try(|input| input.expect_ident_matching("flip")).is_ok() {
             // Handle flip
             Ok(SpecifiedValue { angle: Some(Angle::zero()), flipped: true })
         } else {
             // Handle <angle> | <angle> flip
             let angle = input.try(|input| Angle::parse(context, input)).ok();
             if angle.is_none() {
-                return Err(());
+                return Err(StyleParseError::UnspecifiedError.into());
             }
 
             let flipped = input.try(|input| input.expect_ident_matching("flip")).is_ok();
             Ok(SpecifiedValue { angle: angle, flipped: flipped })
         }
     }
 </%helpers:longhand>
 
--- a/servo/components/style/properties/longhand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_svg.mako.rs
@@ -178,55 +178,57 @@
     }
 
     impl SpecifiedValue {
         pub fn bits_at(&self, pos: u8) -> u8 {
             (self.0 >> pos * SHIFT) & MASK
         }
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         if let Ok(()) = input.try(|i| i.expect_ident_matching("normal")) {
             Ok(SpecifiedValue(0))
         } else {
             let mut value = 0;
             // bitfield representing what we've seen so far
             // bit 1 is fill, bit 2 is stroke, bit 3 is markers
             let mut seen = 0;
             let mut pos = 0;
 
             loop {
 
-                let result = input.try(|i| {
-                    match_ignore_ascii_case! { &i.expect_ident()?,
+                let result: Result<_, ParseError> = input.try(|i| {
+                    let ident = i.expect_ident()?;
+                    (match_ignore_ascii_case! { &ident,
                         "fill" => Ok(FILL),
                         "stroke" => Ok(STROKE),
                         "markers" => Ok(MARKERS),
                         _ => Err(())
-                    }
+                    }).map_err(|()| SelectorParseError::UnexpectedIdent(ident.into()).into())
                 });
 
                 match result {
                     Ok(val) => {
                         if (seen & (1 << val)) != 0 {
                             // don't parse the same ident twice
-                            return Err(())
+                            return Err(StyleParseError::UnspecifiedError.into())
                         } else {
                             value |= val << (pos * SHIFT);
                             seen |= 1 << val;
                             pos += 1;
                         }
                     }
-                    Err(()) => break,
+                    Err(_) => break,
                 }
             }
 
             if value == 0 {
                 // couldn't find any keyword
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             } else {
                 // fill in rest
                 for i in pos..COUNT {
                     for paint in &ALL {
                         // if not seen, set bit at position, mark as seen
                         if (seen & (1 << paint)) == 0 {
                             seen |= 1 << paint;
                             value |= paint << (i * SHIFT);
@@ -280,13 +282,14 @@
 
     pub type SpecifiedValue = CustomIdent;
 
     pub mod computed_value {
         pub type T = super::SpecifiedValue;
     }
 
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let i = input.expect_ident()?;
         CustomIdent::from_ident(i, &["all", "none", "auto"])
     }
 </%helpers:vector_longhand>
--- a/servo/components/style/properties/longhand/inherited_table.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_table.mako.rs
@@ -108,30 +108,31 @@
         fn from_computed_value(computed: &computed_value::T) -> Self {
             SpecifiedValue {
                 horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
                 vertical: Some(ToComputedValue::from_computed_value(&computed.vertical)),
             }
         }
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         let mut first = None;
         let mut second = None;
         match Length::parse_non_negative_quirky(context, input, AllowQuirks::Yes) {
-            Err(()) => (),
+            Err(_) => (),
             Ok(length) => {
                 first = Some(length);
                 if let Ok(len) = input.try(|i| Length::parse_non_negative_quirky(context, i, AllowQuirks::Yes)) {
                     second = Some(len);
                 }
             }
         }
         match (first, second) {
-            (None, None) => Err(()),
+            (None, None) => Err(StyleParseError::UnspecifiedError.into()),
             (Some(length), None) => {
                 Ok(SpecifiedValue {
                     horizontal: length,
                     vertical: None,
                 })
             }
             (Some(horizontal), Some(vertical)) => {
                 Ok(SpecifiedValue {
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -174,17 +174,18 @@
         use style_traits::ToCss;
 
         #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
         pub enum SpecifiedValue {
             Keyword(computed_value::T),
             MatchParent,
             MozCenterOrInherit,
         }
-        pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                             -> Result<SpecifiedValue, ParseError<'i>> {
             // MozCenterOrInherit cannot be parsed, only set directly on th elements
             if let Ok(key) = input.try(computed_value::T::parse) {
                 Ok(SpecifiedValue::Keyword(key))
             } else {
                 input.expect_ident_matching("match-parent")?;
                 Ok(SpecifiedValue::MatchParent)
             }
         }
@@ -250,17 +251,18 @@
             fn from_computed_value(computed: &computed_value::T) -> Self {
                 SpecifiedValue::Keyword(*computed)
             }
         }
     % else:
         use values::computed::ComputedValueAsSpecified;
         impl ComputedValueAsSpecified for SpecifiedValue {}
         pub use self::computed_value::T as SpecifiedValue;
-        pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                             -> Result<SpecifiedValue, ParseError<'i>> {
             computed_value::T::parse(input)
         }
     % endif
 </%helpers:longhand>
 
 ${helpers.predefined_type("letter-spacing",
                           "LetterSpacing",
                           "computed::LetterSpacing::normal()",
@@ -410,17 +412,18 @@
                           ignored_when_colors_disabled="True"
                           spec="https://drafts.csswg.org/css-backgrounds/#box-shadow">
     pub type SpecifiedValue = specified::Shadow;
     pub mod computed_value {
         use values::computed::Shadow;
         pub type T = Shadow;
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<specified::Shadow, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<specified::Shadow, ParseError<'i>> {
         specified::Shadow::parse(context, input, true)
     }
 </%helpers:vector_longhand>
 
 <%helpers:longhand name="text-emphasis-style" products="gecko" need_clone="True" boxed="True"
                    animation_value_type="none"
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style">
     use computed_values::writing_mode::T as writing_mode;
@@ -572,17 +575,18 @@
                 computed_value::T::Keyword(ref keyword) =>
                     SpecifiedValue::Keyword(KeywordValue::FillAndShape(keyword.fill,keyword.shape)),
                 computed_value::T::None => SpecifiedValue::None,
                 computed_value::T::String(ref string) => SpecifiedValue::String(string.clone())
             }
         }
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue::None);
         }
 
         if let Ok(s) = input.try(|input| input.expect_string()) {
             // Handle <string>
             return Ok(SpecifiedValue::String(s.into_owned()));
         }
@@ -598,17 +602,17 @@
             shape = input.try(ShapeKeyword::parse);
         }
 
         // At least one of shape or fill must be handled
         let keyword_value = match (fill, shape) {
             (Some(fill), Ok(shape)) => KeywordValue::FillAndShape(fill,shape),
             (Some(fill), Err(_)) => KeywordValue::Fill(fill),
             (None, Ok(shape)) => KeywordValue::Shape(shape),
-            _ => return Err(()),
+            _ => return Err(StyleParseError::UnspecifiedError.into()),
         };
         Ok(SpecifiedValue::Keyword(keyword_value))
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="text-emphasis-position" animation_value_type="discrete" products="gecko"
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-position">
     use values::computed::ComputedValueAsSpecified;
@@ -631,17 +635,18 @@
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
     no_viewport_percentage!(SpecifiedValue);
 
     pub fn get_initial_value() -> computed_value::T {
         SpecifiedValue(HorizontalWritingModeValue::Over, VerticalWritingModeValue::Right)
     }
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
        if let Ok(horizontal) = input.try(|input| HorizontalWritingModeValue::parse(input)) {
             let vertical = try!(VerticalWritingModeValue::parse(input));
             Ok(SpecifiedValue(horizontal, vertical))
         } else {
             let vertical = try!(VerticalWritingModeValue::parse(input));
             let horizontal = try!(HorizontalWritingModeValue::parse(input));
             Ok(SpecifiedValue(horizontal, vertical))
         }
--- a/servo/components/style/properties/longhand/list.mako.rs
+++ b/servo/components/style/properties/longhand/list.mako.rs
@@ -84,17 +84,18 @@
             computed_value::T::CounterStyle(CounterStyleOrNone::disc())
         }
 
         #[inline]
         pub fn get_initial_specified_value() -> SpecifiedValue {
             SpecifiedValue::CounterStyle(CounterStyleOrNone::disc())
         }
 
-        pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+        pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                             -> Result<SpecifiedValue, ParseError<'i>> {
             Ok(if let Ok(style) = input.try(|i| CounterStyleOrNone::parse(context, i)) {
                 SpecifiedValue::CounterStyle(style)
             } else {
                 SpecifiedValue::String(input.expect_string()?.into_owned())
             })
         }
     </%helpers:longhand>
 % endif
@@ -121,33 +122,33 @@
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(Either::Second(None_))
     }
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         SpecifiedValue(Either::Second(None_))
     }
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         % if product == "gecko":
         let mut value = input.try(|input| UrlOrNone::parse(context, input))?;
         if let Either::First(ref mut url) = value {
             url.build_image_value();
         }
         % else :
         let value = input.try(|input| UrlOrNone::parse(context, input))?;
         % endif
 
         return Ok(SpecifiedValue(value));
     }
 </%helpers:longhand>
 
 <%helpers:longhand name="quotes" animation_value_type="none"
                    spec="https://drafts.csswg.org/css-content/#propdef-quotes">
-    use cssparser::Token;
     use std::borrow::Cow;
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     pub use self::computed_value::T as SpecifiedValue;
 
     pub mod computed_value {
@@ -182,38 +183,40 @@
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(vec![
             ("\u{201c}".to_owned(), "\u{201d}".to_owned()),
             ("\u{2018}".to_owned(), "\u{2019}".to_owned()),
         ])
     }
 
-    pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue(Vec::new()))
         }
 
         let mut quotes = Vec::new();
         loop {
             let first = match input.next() {
                 Ok(Token::QuotedString(value)) => value.into_owned(),
-                Ok(_) => return Err(()),
-                Err(()) => break,
+                Ok(t) => return Err(BasicParseError::UnexpectedToken(t).into()),
+                Err(_) => break,
             };
             let second = match input.next() {
                 Ok(Token::QuotedString(value)) => value.into_owned(),
-                _ => return Err(()),
+                Ok(t) => return Err(BasicParseError::UnexpectedToken(t).into()),
+                Err(e) => return Err(e.into()),
             };
             quotes.push((first, second))
         }
         if !quotes.is_empty() {
             Ok(SpecifiedValue(quotes))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("-moz-image-region",
                           "ClipRectOrAuto",
                           "computed::ClipRectOrAuto::auto()",
                           animation_value_type="ComputedValue",
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -41,24 +41,25 @@
     pub fn get_initial_specified_value() -> SpecifiedValue {
         Either::Second(BorderStyle::none)
     }
 
     pub mod computed_value {
         pub type T = super::SpecifiedValue;
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         SpecifiedValue::parse(context, input)
             .and_then(|result| {
                 if let Either::Second(BorderStyle::hidden) = result {
                     // The outline-style property accepts the same values as
                     // border-style, except that 'hidden' is not a legal outline
                     // style.
-                    Err(())
+                    Err(SelectorParseError::UnexpectedIdent("hidden".into()).into())
                 } else {
                     Ok(result)
                 }
             })
     }
 </%helpers:longhand>
 
 ${helpers.predefined_type("outline-width",
--- a/servo/components/style/properties/longhand/pointing.mako.rs
+++ b/servo/components/style/properties/longhand/pointing.mako.rs
@@ -86,55 +86,61 @@
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T {
             images: vec![],
             keyword: computed_value::Keyword::Auto
         }
     }
 
     impl Parse for computed_value::Keyword {
-        fn parse(_context: &ParserContext, input: &mut Parser) -> Result<computed_value::Keyword, ()> {
+        fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<computed_value::Keyword, ParseError<'i>> {
             use std::ascii::AsciiExt;
             use style_traits::cursor::Cursor;
             let ident = try!(input.expect_ident());
             if ident.eq_ignore_ascii_case("auto") {
                 Ok(computed_value::Keyword::Auto)
             } else {
-                Cursor::from_css_keyword(&ident).map(computed_value::Keyword::Cursor)
+                Cursor::from_css_keyword(&ident)
+                    .map(computed_value::Keyword::Cursor)
+                    .map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
             }
         }
     }
 
     #[cfg(feature = "gecko")]
-    fn parse_image(context: &ParserContext, input: &mut Parser) -> Result<computed_value::Image, ()> {
+    fn parse_image<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                           -> Result<computed_value::Image, ParseError<'i>> {
         Ok(computed_value::Image {
             url: try!(SpecifiedUrl::parse(context, input)),
             hotspot: match input.try(|input| input.expect_number()) {
                 Ok(number) => Some((number, try!(input.expect_number()))),
-                Err(()) => None,
+                Err(_) => None,
             },
         })
     }
 
     #[cfg(not(feature = "gecko"))]
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         computed_value::Keyword::parse(context, input)
     }
 
     /// cursor: [<url> [<number> <number>]?]# [auto | default | ...]
     #[cfg(feature = "gecko")]
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut images = vec![];
         loop {
             match input.try(|input| parse_image(context, input)) {
                 Ok(mut image) => {
                     image.url.build_image_value();
                     images.push(image)
                 }
-                Err(()) => break,
+                Err(_) => break,
             }
             try!(input.expect_comma());
         }
 
         Ok(computed_value::T {
             images: images,
             keyword: try!(computed_value::Keyword::parse(context, input)),
         })
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -334,47 +334,52 @@ macro_rules! impl_align_conversions {
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T {
             autoflow: computed_value::AutoFlow::Row,
             dense: false
         }
     }
 
     /// [ row | column ] || dense
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         use self::computed_value::AutoFlow;
 
         let mut value = None;
         let mut dense = false;
 
         while !input.is_exhausted() {
-            match_ignore_ascii_case! { &input.expect_ident()?,
+            let ident = input.expect_ident()?;
+            let success = match_ignore_ascii_case! { &ident,
                 "row" if value.is_none() => {
                     value = Some(AutoFlow::Row);
-                    continue
+                    true
                 },
                 "column" if value.is_none() => {
                     value = Some(AutoFlow::Column);
-                    continue
+                    true
                 },
                 "dense" if !dense => {
                     dense = true;
-                    continue
+                    true
                 },
-                _ => return Err(())
+                _ => false
+            };
+            if !success {
+                return Err(SelectorParseError::UnexpectedIdent(ident).into());
             }
         }
 
         if value.is_some() || dense {
             Ok(computed_value::T {
                 autoflow: value.unwrap_or(AutoFlow::Row),
                 dense: dense,
             })
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     #[cfg(feature = "gecko")]
     impl From<u8> for SpecifiedValue {
         fn from(bits: u8) -> SpecifiedValue {
             use gecko_bindings::structs;
             use self::computed_value::AutoFlow;
@@ -430,17 +435,18 @@ macro_rules! impl_align_conversions {
 
     pub type SpecifiedValue = Either<TemplateAreas, None_>;
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         Either::Second(None_)
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         SpecifiedValue::parse(context, input)
     }
 
     #[derive(Clone, PartialEq)]
     pub struct TemplateAreas {
         pub areas: Box<[NamedArea]>,
         pub strings: Box<[Box<str>]>,
         pub width: u32,
@@ -452,23 +458,25 @@ macro_rules! impl_align_conversions {
         pub rows: Range<u32>,
         pub columns: Range<u32>,
     }
 
     no_viewport_percentage!(TemplateAreas);
     impl ComputedValueAsSpecified for TemplateAreas {}
 
     impl Parse for TemplateAreas {
-        fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+        fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<Self, ParseError<'i>> {
             let mut strings = vec![];
             while let Ok(string) = input.try(Parser::expect_string) {
                 strings.push(string.into_owned().into_boxed_str());
             }
 
             TemplateAreas::from_vec(strings)
+                .map_err(|()| StyleParseError::UnspecifiedError.into())
         }
     }
 
     impl TemplateAreas {
         pub fn from_vec(strings: Vec<Box<str>>) -> Result<TemplateAreas, ()> {
             if strings.is_empty() {
                 return Err(());
             }
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -124,17 +124,18 @@
     pub use ::properties::longhands::background_size::single_value as single_value;
     pub use ::properties::longhands::background_size::computed_value as computed_value;
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         background_size::get_initial_value()
     }
 
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue,ParseError<'i>> {
         background_size::parse(context, input)
     }
 </%helpers:longhand>
 
 ${helpers.single_keyword("mask-composite",
                          "add subtract intersect exclude",
                          vector=True,
                          products="gecko",
--- a/servo/components/style/properties/longhand/table.mako.rs
+++ b/servo/components/style/properties/longhand/table.mako.rs
@@ -35,12 +35,13 @@
     }
 
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T(1)
     }
 
     // never parse it, only set via presentation attribute
-    fn parse(_: &ParserContext, _: &mut Parser) -> Result<SpecifiedValue, ()> {
-        Err(())
+    fn parse<'i, 't>(_: &ParserContext, _: &mut Parser<'i, 't>)
+                     -> Result<SpecifiedValue, ParseError<'i>> {
+        Err(StyleParseError::UnspecifiedError.into())
     }
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -99,32 +99,34 @@
     #[inline]
     pub fn get_initial_value() -> computed_value::T {
         computed_value::T {
             first: Side::Clip,
             second: Side::Clip,
             sides_are_logical: true,
         }
     }
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let first = try!(Side::parse(context, input));
         let second = input.try(|input| Side::parse(context, input)).ok();
         Ok(SpecifiedValue {
             first: first,
             second: second,
         })
     }
     impl Parse for Side {
-        fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Side, ()> {
+        fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<Side, ParseError<'i>> {
             if let Ok(ident) = input.try(|input| input.expect_ident()) {
-                match_ignore_ascii_case! { &ident,
+                (match_ignore_ascii_case! { &ident,
                     "clip" => Ok(Side::Clip),
                     "ellipsis" => Ok(Side::Ellipsis),
                     _ => Err(())
-                }
+                }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
             } else {
                 Ok(Side::String(try!(input.expect_string()).into_owned().into_boxed_str()))
             }
         }
     }
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -211,44 +213,49 @@
     #[inline] pub fn get_initial_value() -> computed_value::T {
         computed_value::none
     }
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         SpecifiedValue::empty()
     }
     /// none | [ underline || overline || line-through || blink ]
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         let mut result = SpecifiedValue::empty();
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(result)
         }
         let mut empty = true;
 
-        while input.try(|input| {
-                if let Ok(ident) = input.expect_ident() {
-                    match_ignore_ascii_case! { &ident,
-                        "underline" => if result.contains(UNDERLINE) { return Err(()) }
-                                       else { empty = false; result.insert(UNDERLINE) },
-                        "overline" => if result.contains(OVERLINE) { return Err(()) }
-                                      else { empty = false; result.insert(OVERLINE) },
-                        "line-through" => if result.contains(LINE_THROUGH) { return Err(()) }
-                                          else { empty = false; result.insert(LINE_THROUGH) },
-                        "blink" => if result.contains(BLINK) { return Err(()) }
-                                   else { empty = false; result.insert(BLINK) },
-                        _ => return Err(())
+        loop {
+            let result: Result<_, ParseError> = input.try(|input| {
+                match input.expect_ident() {
+                    Ok(ident) => {
+                        (match_ignore_ascii_case! { &ident,
+                            "underline" => if result.contains(UNDERLINE) { Err(()) }
+                                           else { empty = false; result.insert(UNDERLINE); Ok(()) },
+                            "overline" => if result.contains(OVERLINE) { Err(()) }
+                                          else { empty = false; result.insert(OVERLINE); Ok(()) },
+                            "line-through" => if result.contains(LINE_THROUGH) { Err(()) }
+                                              else { empty = false; result.insert(LINE_THROUGH); Ok(()) },
+                            "blink" => if result.contains(BLINK) { Err(()) }
+                                       else { empty = false; result.insert(BLINK); Ok(()) },
+                            _ => Err(())
+                        }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
                     }
-                } else {
-                    return Err(());
+                    Err(e) => return Err(e.into())
                 }
-                Ok(())
-            }).is_ok() {
+            });
+            if result.is_err() {
+                break;
+            }
         }
 
-        if !empty { Ok(result) } else { Err(()) }
+        if !empty { Ok(result) } else { Err(StyleParseError::UnspecifiedError.into()) }
     }
 
     % if product == "servo":
         fn cascade_property_custom(_declaration: &PropertyDeclaration,
                                    _inherited_style: &ComputedValues,
                                    context: &mut computed::Context,
                                    _cacheable: &mut bool,
                                    _error_reporter: &ParseErrorReporter) {
--- a/servo/components/style/properties/longhand/ui.mako.rs
+++ b/servo/components/style/properties/longhand/ui.mako.rs
@@ -71,21 +71,22 @@
 
     #[inline]
     pub fn get_initial_specified_value() -> SpecifiedValue {
         computed_value::T(false)
     }
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
 
-    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+    pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<SpecifiedValue, ParseError<'i>> {
         match try!(input.expect_integer()) {
             0 => Ok(computed_value::T(false)),
             1 => Ok(computed_value::T(true)),
-            _ => Err(()),
+            _ => Err(StyleParseError::UnspecifiedError.into()),
         }
     }
 
     impl From<u8> for SpecifiedValue {
         fn from(bits: u8) -> SpecifiedValue {
             SpecifiedValue(bits == 1)
         }
     }
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -15,32 +15,34 @@ use std::collections::HashSet;
 use std::fmt;
 use std::mem;
 use std::ops::Deref;
 use stylearc::{Arc, UniqueArc};
 
 use app_units::Au;
 #[cfg(feature = "servo")] use cssparser::RGBA;
 use cssparser::{Parser, TokenSerializationType, serialize_identifier};
+use cssparser::ParserInput;
 use error_reporting::ParseErrorReporter;
 #[cfg(feature = "servo")] use euclid::side_offsets::SideOffsets2D;
 use computed_values;
 use context::QuirksMode;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::bindings;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID};
 #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide};
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use parser::{PARSING_MODE_DEFAULT, Parse, ParserContext};
 use properties::animated_properties::TransitionProperty;
 #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
+use selectors::parser::SelectorParseError;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
-use style_traits::{HasViewportPercentage, ToCss};
+use style_traits::{HasViewportPercentage, ToCss, ParseError, PropertyDeclarationParseError};
 use stylesheets::{CssRuleType, MallocSizeOf, MallocSizeOfFn, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
 use values::generics::text::LineHeight;
 use values::computed;
 use cascade_info::CascadeInfo;
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use style_adjuster::StyleAdjuster;
 #[cfg(feature = "servo")] use values::specified::BorderStyle;
@@ -180,16 +182,17 @@ macro_rules! unwrap_or_initial {
 }
 
 /// A module with code for all the shorthand css properties, and a few
 /// serialization helpers.
 #[allow(missing_docs)]
 pub mod shorthands {
     use cssparser::Parser;
     use parser::{Parse, ParserContext};
+    use style_traits::{ParseError, StyleParseError};
     use values::specified;
 
     <%include file="/shorthand/serialize.mako.rs" />
     <%include file="/shorthand/background.mako.rs" />
     <%include file="/shorthand/border.mako.rs" />
     <%include file="/shorthand/box.mako.rs" />
     <%include file="/shorthand/column.mako.rs" />
     <%include file="/shorthand/font.mako.rs" />
@@ -208,19 +211,21 @@ pub mod shorthands {
     <% data.declare_shorthand("all",
                               [p.name for p in data.longhands if p.name not in ['direction', 'unicode-bidi']],
                               spec="https://drafts.csswg.org/css-cascade-3/#all-shorthand") %>
     pub mod all {
         use cssparser::Parser;
         use parser::ParserContext;
         use properties::{SourcePropertyDeclaration, AllShorthand, ShorthandId, UnparsedValue};
         use stylearc::Arc;
+        use style_traits::{ParseError, StyleParseError};
 
-        pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
-                          context: &ParserContext, input: &mut Parser) -> Result<(), ()> {
+        pub fn parse_into<'i, 't>(declarations: &mut SourcePropertyDeclaration,
+                                  context: &ParserContext, input: &mut Parser<'i, 't>)
+                                  -> Result<(), ParseError<'i>> {
             // This function is like the parse() that is generated by
             // helpers:shorthand, but since the only values for the 'all'
             // shorthand when not just a single CSS-wide keyword is one
             // with variable references, we can make this function a
             // little simpler.
             //
             // FIXME(heycam) Try to share code with the helpers:shorthand
             // definition.
@@ -234,17 +239,17 @@ pub mod shorthands {
                 declarations.all_shorthand = AllShorthand::WithVariables(Arc::new(UnparsedValue {
                     css: css.into_owned(),
                     first_token_type: first_token_type,
                     url_data: context.url_data.clone(),
                     from_shorthand: Some(ShorthandId::All),
                 }));
                 Ok(())
             } else {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             }
         }
     }
 }
 
 /// A module with all the code related to animated properties.
 ///
 /// This needs to be "included" by mako at least after all longhand modules,
@@ -409,51 +414,53 @@ impl PropertyDeclarationIdSet {
                 % if property.boxed:
                     where F: FnOnce(&DeclaredValue<Box<longhands::${property.ident}::SpecifiedValue>>)
                 % else:
                     where F: FnOnce(&DeclaredValue<longhands::${property.ident}::SpecifiedValue>)
                 % endif
         {
             f(&
                 ::custom_properties::substitute(css, first_token_type, custom_properties)
+                .ok()
                 .and_then(|css| {
                     // As of this writing, only the base URL is used for property values:
                     //
                     // FIXME(pcwalton): Cloning the error reporter is slow! But so are custom
                     // properties, so whatever...
                     let context = ParserContext::new(Origin::Author,
                                                      url_data,
                                                      error_reporter,
                                                      None,
                                                      PARSING_MODE_DEFAULT,
                                                      quirks_mode);
-                    Parser::new(&css).parse_entirely(|input| {
+                    let mut input = ParserInput::new(&css);
+                    Parser::new(&mut input).parse_entirely(|input| {
                         match from_shorthand {
                             None => {
                                 longhands::${property.ident}
                                          ::parse_specified(&context, input).map(DeclaredValueOwned::Value)
                             }
                             Some(ShorthandId::All) => {
                                 // No need to parse the 'all' shorthand as anything other than a CSS-wide
                                 // keyword, after variable substitution.
-                                Err(())
+                                Err(SelectorParseError::UnexpectedIdent("all".into()).into())
                             }
                             % for shorthand in data.shorthands_except_all():
                                 % if property in shorthand.sub_properties:
                                     Some(ShorthandId::${shorthand.camel_case}) => {
                                         shorthands::${shorthand.ident}::parse_value(&context, input)
                                         .map(|result| {
                                             DeclaredValueOwned::Value(result.${property.ident})
                                         })
                                     }
                                 % endif
                             % endfor
                             _ => unreachable!()
                         }
-                    })
+                    }).ok()
                 })
                 .unwrap_or(
                     // Invalid at computed-value time.
                     DeclaredValueOwned::CSSWideKeyword(
                         % if property.style_struct.inherited:
                             CSSWideKeyword::Inherit
                         % else:
                             CSSWideKeyword::Initial
@@ -495,20 +502,21 @@ impl CSSWideKeyword {
             "inherit" => Some(CSSWideKeyword::Inherit),
             "unset" => Some(CSSWideKeyword::Unset),
             _ => None
         }
     }
 }
 
 impl Parse for CSSWideKeyword {
-    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let ident = input.expect_ident()?;
         input.expect_exhausted()?;
-        CSSWideKeyword::from_ident(&ident).ok_or(())
+        CSSWideKeyword::from_ident(&ident)
+            .ok_or(SelectorParseError::UnexpectedIdent(ident).into())
     }
 }
 
 bitflags! {
     /// A set of flags for properties.
     pub flags PropertyFlags: u8 {
         /// This property requires a stacking context.
         const CREATES_STACKING_CONTEXT = 1 << 0,
@@ -941,17 +949,17 @@ impl ToCss for PropertyId {
         }
     }
 }
 
 impl PropertyId {
     /// Returns a given property from the string `s`.
     ///
     /// Returns Err(()) for unknown non-custom properties
-    pub fn parse(property_name: Cow<str>) -> Result<Self, ()> {
+    pub fn parse<'i>(property_name: Cow<'i, str>) -> Result<Self, ParseError<'i>> {
         if let Ok(name) = ::custom_properties::parse_name(&property_name) {
             return Ok(PropertyId::Custom(::custom_properties::Name::from(name)))
         }
 
         // FIXME(https://github.com/rust-lang/rust/issues/33156): remove this enum and use PropertyId
         // when stable Rust allows destructors in statics.
         pub enum StaticId {
             Longhand(LonghandId),
@@ -966,17 +974,17 @@ impl PropertyId {
                         % endfor
                     % endfor
                 % endfor
             }
         }
         match static_id(&property_name) {
             Some(&StaticId::Longhand(id)) => Ok(PropertyId::Longhand(id)),
             Some(&StaticId::Shorthand(id)) => Ok(PropertyId::Shorthand(id)),
-            None => Err(()),
+            None => Err(SelectorParseError::UnexpectedIdent(property_name).into()),
         }
     }
 
     /// Returns a property id from Gecko's nsCSSPropertyID.
     #[cfg(feature = "gecko")]
     #[allow(non_upper_case_globals)]
     pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
         use gecko_bindings::structs::*;
@@ -1090,34 +1098,16 @@ impl HasViewportPercentage for PropertyD
             PropertyDeclaration::CSSWideKeyword(..) => false,
             PropertyDeclaration::Custom(_, ref val) => {
                 val.borrow().has_viewport_percentage()
             }
         }
     }
 }
 
-/// The result of parsing a property declaration.
-#[derive(Eq, PartialEq, Copy, Clone)]
-pub enum PropertyDeclarationParseError {
-    /// The property declaration was for an unknown property.
-    UnknownProperty,
-    /// The property declaration was for a disabled experimental property.
-    ExperimentalProperty,
-    /// The property declaration contained an invalid value.
-    InvalidValue,
-    /// The declaration contained an animation property, and we were parsing
-    /// this as a keyframe block (so that property should be ignored).
-    ///
-    /// See: https://drafts.csswg.org/css-animations/#keyframes
-    AnimationPropertyInKeyframeBlock,
-    /// The property is not allowed within a page rule.
-    NotAllowedInPageRule,
-}
-
 impl fmt::Debug for PropertyDeclaration {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         try!(self.id().to_css(f));
         try!(f.write_str(": "));
         self.to_css(f)
     }
 }
 
@@ -1402,19 +1392,19 @@ impl PropertyDeclaration {
         debug_assert!(rule_type == CssRuleType::Keyframe ||
                       rule_type == CssRuleType::Page ||
                       rule_type == CssRuleType::Style,
                       "Declarations are only expected inside a keyframe, page, or style rule.");
         match id {
             PropertyId::Custom(name) => {
                 let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
                     Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword),
-                    Err(()) => match ::custom_properties::SpecifiedValue::parse(context, input) {
+                    Err(_) => match ::custom_properties::SpecifiedValue::parse(context, input) {
                         Ok(value) => DeclaredValueOwned::Value(value),
-                        Err(()) => return Err(PropertyDeclarationParseError::InvalidValue),
+                        Err(_) => return Err(PropertyDeclarationParseError::InvalidValue),
                     }
                 };
                 declarations.push(PropertyDeclaration::Custom(name, value));
                 Ok(())
             }
             PropertyId::Longhand(id) => match id {
             % for property in data.longhands:
                 LonghandId::${property.camel_case} => {
@@ -1437,17 +1427,17 @@ impl PropertyDeclaration {
 
                         ${property_pref_check(property)}
 
                         match longhands::${property.ident}::parse_declared(context, input) {
                             Ok(value) => {
                                 declarations.push(value);
                                 Ok(())
                             },
-                            Err(()) => Err(PropertyDeclarationParseError::InvalidValue),
+                            Err(_) => Err(PropertyDeclarationParseError::InvalidValue),
                         }
                     % else:
                         Err(PropertyDeclarationParseError::UnknownProperty)
                     % endif
                 }
             % endfor
             },
             PropertyId::Shorthand(id) => match id {
@@ -1480,19 +1470,19 @@ impl PropertyDeclaration {
                                     declarations.push(PropertyDeclaration::CSSWideKeyword(
                                         LonghandId::${sub_property.camel_case},
                                         keyword,
                                     ));
                                 % endfor
                             % endif
                             Ok(())
                         },
-                        Err(()) => {
+                        Err(_) => {
                             shorthands::${shorthand.ident}::parse_into(declarations, context, input)
-                                .map_err(|()| PropertyDeclarationParseError::InvalidValue)
+                                .map_err(|_| PropertyDeclarationParseError::InvalidValue)
                         }
                     }
                 }
             % endfor
             }
         }
     }
 }
--- a/servo/components/style/properties/shorthand/background.mako.rs
+++ b/servo/components/style/properties/shorthand/background.mako.rs
@@ -27,27 +27,28 @@
                 background_origin::single_value::SpecifiedValue::padding_box =>
                     background_clip::single_value::SpecifiedValue::padding_box,
                 background_origin::single_value::SpecifiedValue::border_box =>
                     background_clip::single_value::SpecifiedValue::border_box,
             }
         }
     }
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut background_color = None;
 
         % for name in "image position_x position_y repeat size attachment origin clip".split():
             let mut background_${name} = background_${name}::SpecifiedValue(Vec::new());
         % endfor
         try!(input.parse_comma_separated(|input| {
             // background-color can only be in the last element, so if it
             // is parsed anywhere before, the value is invalid.
             if background_color.is_some() {
-                return Err(());
+                return Err(StyleParseError::UnspecifiedError.into());
             }
 
             % for name in "image position repeat size attachment origin clip".split():
                 let mut ${name} = None;
             % endfor
             loop {
                 if background_color.is_none() {
                     if let Ok(value) = input.try(|i| Color::parse(context, i)) {
@@ -102,17 +103,17 @@
                         background_${name}.0.push(bg_${name});
                     } else {
                         background_${name}.0.push(background_${name}::single_value
                                                                     ::get_initial_specified_value());
                     }
                 % endfor
                 Ok(())
             } else {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             }
         }));
 
         Ok(expanded! {
              background_color: background_color.unwrap_or(Color::transparent()),
              background_image: background_image,
              background_position_x: background_position_x,
              background_position_y: background_position_y,
@@ -189,30 +190,31 @@
 
 <%helpers:shorthand name="background-position"
                     sub_properties="background-position-x background-position-y"
                     spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position">
     use properties::longhands::{background_position_x, background_position_y};
     use values::specified::AllowQuirks;
     use values::specified::position::Position;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut position_x = background_position_x::SpecifiedValue(Vec::new());
         let mut position_y = background_position_y::SpecifiedValue(Vec::new());
         let mut any = false;
 
         input.parse_comma_separated(|input| {
             let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?;
             position_x.0.push(value.horizontal);
             position_y.0.push(value.vertical);
             any = true;
             Ok(())
         })?;
         if !any {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
 
         Ok(expanded! {
             background_position_x: position_x,
             background_position_y: position_y,
         })
     }
 
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -15,17 +15,18 @@
 
 <%helpers:shorthand name="border-width" sub_properties="${
         ' '.join('border-%s-width' % side
                  for side in PHYSICAL_SIDES)}"
     spec="https://drafts.csswg.org/css-backgrounds/#border-width">
     use values::generics::rect::Rect;
     use values::specified::{AllowQuirks, BorderSideWidth};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let rect = Rect::parse_with(context, input, |_, i| {
             BorderSideWidth::parse_quirky(context, i, AllowQuirks::Yes)
         })?;
         Ok(expanded! {
             border_top_width: rect.0,
             border_right_width: rect.1,
             border_bottom_width: rect.2,
             border_left_width: rect.3,
@@ -38,20 +39,20 @@
             let ${side} = &self.border_${side}_width;
             % endfor
             Rect::new(top, right, bottom, left).to_css(dest)
         }
     }
 </%helpers:shorthand>
 
 
-pub fn parse_border(context: &ParserContext, input: &mut Parser)
-                 -> Result<(specified::Color,
-                            specified::BorderStyle,
-                            specified::BorderSideWidth), ()> {
+pub fn parse_border<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                            -> Result<(specified::Color,
+                                       specified::BorderStyle,
+                                       specified::BorderSideWidth), ParseError<'i>> {
     use values::specified::{Color, BorderStyle, BorderSideWidth};
     let _unused = context;
     let mut color = None;
     let mut style = None;
     let mut width = None;
     let mut any = false;
     loop {
         if color.is_none() {
@@ -77,17 +78,17 @@ pub fn parse_border(context: &ParserCont
         }
         break
     }
     if any {
         Ok((color.unwrap_or_else(|| Color::currentcolor()),
             style.unwrap_or(BorderStyle::none),
             width.unwrap_or(BorderSideWidth::Medium)))
     } else {
-        Err(())
+        Err(StyleParseError::UnspecifiedError.into())
     }
 }
 
 % for side, logical in ALL_SIDES:
     <%
         spec = "https://drafts.csswg.org/css-backgrounds/#border-%s" % side
         if logical:
             spec = "https://drafts.csswg.org/css-logical-props/#propdef-border-%s" % side
@@ -96,17 +97,18 @@ pub fn parse_border(context: &ParserCont
         name="border-${side}"
         sub_properties="${' '.join(
             'border-%s-%s' % (side, prop)
             for prop in ['color', 'style', 'width']
         )}"
         alias="${maybe_moz_logical_alias(product, (side, logical), '-moz-border-%s')}"
         spec="${spec}">
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let (color, style, width) = try!(super::parse_border(context, input));
         Ok(expanded! {
             border_${to_rust_ident(side)}_color: color,
             border_${to_rust_ident(side)}_style: style,
             border_${to_rust_ident(side)}_width: width
         })
     }
 
@@ -134,17 +136,18 @@ pub fn parse_border(context: &ParserCont
         for side in PHYSICAL_SIDES) if product == 'gecko' else ''}"
     spec="https://drafts.csswg.org/css-backgrounds/#border">
 
     % if product == "gecko":
         use properties::longhands::{_moz_border_top_colors, _moz_border_right_colors,
                                     _moz_border_bottom_colors, _moz_border_left_colors};
     % endif
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         use properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
         use properties::longhands::{border_image_source, border_image_width};
 
         let (color, style, width) = try!(super::parse_border(context, input));
         Ok(expanded! {
             % for side in PHYSICAL_SIDES:
                 border_${side}_color: color.clone(),
                 border_${side}_style: style,
@@ -204,17 +207,18 @@ pub fn parse_border(context: &ParserCont
 <%helpers:shorthand name="border-radius" sub_properties="${' '.join(
     'border-%s-radius' % (corner)
      for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left']
 )}" extra_prefixes="webkit" spec="https://drafts.csswg.org/css-backgrounds/#border-radius">
     use values::generics::rect::Rect;
     use values::specified::border::BorderRadius;
     use parser::Parse;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let radii = try!(BorderRadius::parse(context, input));
         Ok(expanded! {
             border_top_left_radius: radii.top_left,
             border_top_right_radius: radii.top_right,
             border_bottom_right_radius: radii.bottom_right,
             border_bottom_left_radius: radii.bottom_left,
         })
     }
@@ -237,44 +241,45 @@ pub fn parse_border(context: &ParserCont
 </%helpers:shorthand>
 
 <%helpers:shorthand name="border-image" sub_properties="border-image-outset
     border-image-repeat border-image-slice border-image-source border-image-width"
     extra_prefixes="moz webkit" spec="https://drafts.csswg.org/css-backgrounds-3/#border-image">
     use properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
     use properties::longhands::{border_image_source, border_image_width};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         % for name in "outset repeat slice source width".split():
             let mut border_image_${name} = border_image_${name}::get_initial_specified_value();
         % endfor
 
-        try!(input.try(|input| {
+        let result: Result<_, ParseError> = input.try(|input| {
             % for name in "outset repeat slice source width".split():
                 let mut ${name} = None;
             % endfor
             loop {
                 if slice.is_none() {
                     if let Ok(value) = input.try(|input| border_image_slice::parse(context, input)) {
                         slice = Some(value);
                         // Parse border image width and outset, if applicable.
-                        let maybe_width_outset: Result<_, ()> = input.try(|input| {
+                        let maybe_width_outset: Result<_, ParseError> = input.try(|input| {
                             try!(input.expect_delim('/'));
 
                             // Parse border image width, if applicable.
                             let w = input.try(|input|
                                 border_image_width::parse(context, input)).ok();
 
                             // Parse border image outset if applicable.
                             let o = input.try(|input| {
                                 try!(input.expect_delim('/'));
                                 border_image_outset::parse(context, input)
                             }).ok();
                             if w.is_none() && o.is_none() {
-                               Err(())
+                               Err(StyleParseError::UnspecifiedError.into())
                             }
                             else {
                                Ok((w, o))
                             }
                         });
                         if let Ok((w, o)) = maybe_width_outset {
                             width = w;
                             outset = o;
@@ -300,19 +305,20 @@ pub fn parse_border(context: &ParserCont
             if any {
                 % for name in "outset repeat slice source width".split():
                     if let Some(b_${name}) = ${name} {
                         border_image_${name} = b_${name};
                     }
                 % endfor
                 Ok(())
             } else {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             }
-        }));
+        });
+        try!(result);
 
         Ok(expanded! {
             % for name in "outset repeat slice source width".split():
                 border_image_${name}: border_image_${name},
             % endfor
          })
     }
 
--- a/servo/components/style/properties/shorthand/box.mako.rs
+++ b/servo/components/style/properties/shorthand/box.mako.rs
@@ -6,39 +6,43 @@
 
 <%helpers:shorthand name="overflow" sub_properties="overflow-x overflow-y"
                     spec="https://drafts.csswg.org/css-overflow/#propdef-overflow">
     use properties::longhands::overflow_x::parse as parse_overflow;
     % if product == "gecko":
         use properties::longhands::overflow_x::SpecifiedValue;
     % endif
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         % if product == "gecko":
-            let moz_kw_found = input.try(|i| match_ignore_ascii_case! {
-                &i.expect_ident()?,
-                "-moz-scrollbars-horizontal" => {
-                    Ok(expanded! {
-                        overflow_x: SpecifiedValue::scroll,
-                        overflow_y: SpecifiedValue::hidden,
-                    })
-                }
-                "-moz-scrollbars-vertical" => {
-                    Ok(expanded! {
-                        overflow_x: SpecifiedValue::hidden,
-                        overflow_y: SpecifiedValue::scroll,
-                    })
-                }
-                "-moz-scrollbars-none" => {
-                    Ok(expanded! {
-                        overflow_x: SpecifiedValue::hidden,
-                        overflow_y: SpecifiedValue::hidden,
-                    })
-                }
-                _ => Err(())
+            let moz_kw_found = input.try(|i| {
+                let ident = i.expect_ident()?;
+                (match_ignore_ascii_case! {
+                    &ident,
+                    "-moz-scrollbars-horizontal" => {
+                        Ok(expanded! {
+                            overflow_x: SpecifiedValue::scroll,
+                            overflow_y: SpecifiedValue::hidden,
+                        })
+                    }
+                    "-moz-scrollbars-vertical" => {
+                        Ok(expanded! {
+                            overflow_x: SpecifiedValue::hidden,
+                            overflow_y: SpecifiedValue::scroll,
+                        })
+                    }
+                    "-moz-scrollbars-none" => {
+                        Ok(expanded! {
+                            overflow_x: SpecifiedValue::hidden,
+                            overflow_y: SpecifiedValue::hidden,
+                        })
+                    }
+                    _ => Err(())
+                }).map_err(|()| SelectorParseError::UnexpectedIdent(ident).into())
             });
             if moz_kw_found.is_ok() {
                 return moz_kw_found
             }
         % endif
         let overflow = try!(parse_overflow(context, input));
         Ok(expanded! {
             overflow_x: overflow,
@@ -83,24 +87,26 @@ macro_rules! try_parse_one {
                                     transition-timing-function
                                     transition-delay"
                     spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
     use parser::Parse;
     % for prop in "delay duration property timing_function".split():
     use properties::longhands::transition_${prop};
     % endfor
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         struct SingleTransition {
             % for prop in "property duration timing_function delay".split():
             transition_${prop}: transition_${prop}::SingleSpecifiedValue,
             % endfor
         }
 
-        fn parse_one_transition(context: &ParserContext, input: &mut Parser) -> Result<SingleTransition,()> {
+        fn parse_one_transition<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                        -> Result<SingleTransition,ParseError<'i>> {
             % for prop in "property duration timing_function delay".split():
             let mut ${prop} = None;
             % endfor
 
             let mut parsed = 0;
             loop {
                 parsed += 1;
 
@@ -118,17 +124,17 @@ macro_rules! try_parse_one {
             if parsed != 0 {
                 Ok(SingleTransition {
                     % for prop in "property duration timing_function delay".split():
                     transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
                                                                                  ::get_initial_specified_value),
                     % endfor
                 })
             } else {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             }
         }
 
         % for prop in "property duration timing_function delay".split():
         let mut ${prop}s = Vec::new();
         % endfor
 
         if input.try(|input| input.expect_ident_matching("none")).is_err() {
@@ -197,24 +203,26 @@ macro_rules! try_parse_one {
         props = "name duration timing_function delay iteration_count \
                  direction fill_mode play_state".split()
     %>
     use parser::Parse;
     % for prop in props:
     use properties::longhands::animation_${prop};
     % endfor
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         struct SingleAnimation {
             % for prop in props:
             animation_${prop}: animation_${prop}::SingleSpecifiedValue,
             % endfor
         }
 
-        fn parse_one_animation(context: &ParserContext, input: &mut Parser) -> Result<SingleAnimation,()> {
+        fn parse_one_animation<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                       -> Result<SingleAnimation,ParseError<'i>> {
             % for prop in props:
             let mut ${prop} = None;
             % endfor
 
             let mut parsed = 0;
             // NB: Name must be the last one here so that keywords valid for other
             // longhands are not interpreted as names.
             //
@@ -232,17 +240,17 @@ macro_rules! try_parse_one {
                 try_parse_one!(context, input, name, animation_name);
 
                 parsed -= 1;
                 break
             }
 
             // If nothing is parsed, this is an invalid entry.
             if parsed == 0 {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             } else {
                 Ok(SingleAnimation {
                     % for prop in props:
                     animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value
                                                               ::get_initial_specified_value),
                     % endfor
                 })
             }
@@ -298,17 +306,18 @@ macro_rules! try_parse_one {
     }
 </%helpers:shorthand>
 
 <%helpers:shorthand name="scroll-snap-type" products="gecko"
                     sub_properties="scroll-snap-type-x scroll-snap-type-y"
                     spec="https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-snap-type">
     use properties::longhands::scroll_snap_type_x;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let result = try!(scroll_snap_type_x::parse(context, input));
         Ok(expanded! {
             scroll_snap_type_x: result,
             scroll_snap_type_y: result,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
@@ -326,17 +335,18 @@ macro_rules! try_parse_one {
 
 
 <%helpers:shorthand name="-moz-transform" products="gecko"
                     sub_properties="transform"
                     flags="SHORTHAND_ALIAS_PROPERTY"
                     spec="Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/transform">
     use properties::longhands::transform;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         Ok(expanded! {
             transform: transform::parse_prefixed(context, input)?,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.transform.to_css(dest)
--- a/servo/components/style/properties/shorthand/column.mako.rs
+++ b/servo/components/style/properties/shorthand/column.mako.rs
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <%helpers:shorthand name="columns" sub_properties="column-count column-width" experimental="True"
                     extra_prefixes="moz" spec="https://drafts.csswg.org/css-multicol/#propdef-columns">
     use properties::longhands::{column_count, column_width};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
 
         let mut column_count = None;
         let mut column_width = None;
         let mut autos = 0;
 
         loop {
             if input.try(|input| input.expect_ident_matching("auto")).is_ok() {
                 // Leave the options to None, 'auto' is the initial value.
@@ -35,17 +36,17 @@
                 }
             }
 
             break
         }
 
         let values = autos + column_count.iter().len() + column_width.iter().len();
         if values == 0 || values > 2 {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         } else {
             Ok(expanded! {
                 column_count: unwrap_or_initial!(column_count),
                 column_width: unwrap_or_initial!(column_width),
             })
         }
     }
 
@@ -60,17 +61,18 @@
 </%helpers:shorthand>
 
 <%helpers:shorthand name="column-rule" products="gecko" extra_prefixes="moz"
     sub_properties="column-rule-width column-rule-style column-rule-color"
     spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule">
     use properties::longhands::{column_rule_width, column_rule_style};
     use properties::longhands::column_rule_color;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         % for name in "width style color".split():
         let mut column_rule_${name} = None;
         % endfor
         let mut any = false;
 
         loop {
             % for name in "width style color".split():
             if column_rule_${name}.is_none() {
@@ -87,17 +89,17 @@
         }
         if any {
             Ok(expanded! {
                 column_rule_width: unwrap_or_initial!(column_rule_width),
                 column_rule_style: unwrap_or_initial!(column_rule_style),
                 column_rule_color: unwrap_or_initial!(column_rule_color),
             })
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.column_rule_width.to_css(dest)?;
             dest.write_str(" ")?;
             self.column_rule_style.to_css(dest)?;
--- a/servo/components/style/properties/shorthand/font.mako.rs
+++ b/servo/components/style/properties/shorthand/font.mako.rs
@@ -33,17 +33,18 @@
     %>
     % if product == "gecko" or data.testing:
         % for prop in gecko_sub_properties:
             use properties::longhands::font_${prop};
         % endfor
     % endif
     use self::font_family::SpecifiedValue as FontFamily;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut nb_normals = 0;
         let mut style = None;
         let mut variant_caps = None;
         let mut weight = None;
         let mut stretch = None;
         let size;
         % if product == "gecko":
             if let Ok(sys) = input.try(SystemFont::parse) {
@@ -92,17 +93,17 @@
             break
         }
         #[inline]
         fn count<T>(opt: &Option<T>) -> u8 {
             if opt.is_some() { 1 } else { 0 }
         }
         if size.is_none() ||
            (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals) > 4 {
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
         let line_height = if input.try(|input| input.expect_delim('/')).is_ok() {
             Some(try!(LineHeight::parse(context, input)))
         } else {
             None
         };
         let family = FontFamily::parse(input)?;
         Ok(expanded! {
@@ -234,17 +235,18 @@
     use properties::longhands::font_variant_caps;
     <% gecko_sub_properties = "alternates east_asian ligatures numeric position".split() %>
     % if product == "gecko" or data.testing:
         % for prop in gecko_sub_properties:
             use properties::longhands::font_variant_${prop};
         % endfor
     % endif
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut nb_normals = 0;
         let mut caps = None;
         loop {
             // Special-case 'normal' because it is valid in each of
             // all sub properties.
             // Leaves the values to None, 'normal' is the initial value for each of them.
             if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
                 nb_normals += 1;
@@ -259,17 +261,17 @@
             break
         }
         #[inline]
         fn count<T>(opt: &Option<T>) -> u8 {
             if opt.is_some() { 1 } else { 0 }
         }
         let count = count(&caps) + nb_normals;
         if count == 0 || count > 1 {
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
         Ok(expanded! {
             font_variant_caps: unwrap_or_initial!(font_variant_caps, caps),
             // FIXME: Bug 1356134 - parse all sub properties.
             % if product == "gecko" or data.testing:
                 % for name in gecko_sub_properties:
                     font_variant_${name}: font_variant_${name}::get_initial_specified_value(),
                 % endfor
--- a/servo/components/style/properties/shorthand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/shorthand/inherited_svg.mako.rs
@@ -4,17 +4,18 @@
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <%helpers:shorthand name="marker" products="gecko"
     sub_properties="marker-start marker-end marker-mid"
     spec="https://www.w3.org/TR/SVG2/painting.html#MarkerShorthand">
     use values::specified::UrlOrNone;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         use parser::Parse;
         let url = UrlOrNone::parse(context, input)?;
 
         Ok(expanded! {
             marker_start: url.clone(),
             marker_mid: url.clone(),
             marker_end: url,
         })
--- a/servo/components/style/properties/shorthand/inherited_text.mako.rs
+++ b/servo/components/style/properties/shorthand/inherited_text.mako.rs
@@ -4,17 +4,18 @@
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <%helpers:shorthand name="text-emphasis" products="gecko" sub_properties="text-emphasis-color
     text-emphasis-style"
     spec="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-property">
     use properties::longhands::{text_emphasis_color, text_emphasis_style};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut color = None;
         let mut style = None;
 
         loop {
             if color.is_none() {
                 if let Ok(value) = input.try(|input| text_emphasis_color::parse(context, input)) {
                     color = Some(value);
                     continue
@@ -29,17 +30,17 @@
             break
         }
         if color.is_some() || style.is_some() {
             Ok(expanded! {
                 text_emphasis_color: unwrap_or_initial!(text_emphasis_color, color),
                 text_emphasis_style: unwrap_or_initial!(text_emphasis_style, style),
             })
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.text_emphasis_style.to_css(dest)?;
             dest.write_str(" ")?;
             self.text_emphasis_color.to_css(dest)
@@ -51,17 +52,18 @@
 // https://compat.spec.whatwg.org/
 <%helpers:shorthand name="-webkit-text-stroke"
                     sub_properties="-webkit-text-stroke-color
                                     -webkit-text-stroke-width"
                     products="gecko"
                     spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke">
     use properties::longhands::{_webkit_text_stroke_color, _webkit_text_stroke_width};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut color = None;
         let mut width = None;
         loop {
             if color.is_none() {
                 if let Ok(value) = input.try(|input| _webkit_text_stroke_color::parse(context, input)) {
                     color = Some(value);
                     continue
                 }
@@ -77,17 +79,17 @@
         }
 
         if color.is_some() || width.is_some() {
             Ok(expanded! {
                 _webkit_text_stroke_color: unwrap_or_initial!(_webkit_text_stroke_color, color),
                 _webkit_text_stroke_width: unwrap_or_initial!(_webkit_text_stroke_width, width),
             })
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self._webkit_text_stroke_width.to_css(dest)?;
             dest.write_str(" ")?;
             self._webkit_text_stroke_color.to_css(dest)
--- a/servo/components/style/properties/shorthand/list.mako.rs
+++ b/servo/components/style/properties/shorthand/list.mako.rs
@@ -5,26 +5,27 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <%helpers:shorthand name="list-style"
                     sub_properties="list-style-image list-style-position list-style-type"
                     spec="https://drafts.csswg.org/css-lists/#propdef-list-style">
     use properties::longhands::{list_style_image, list_style_position, list_style_type};
     use values::{Either, None_};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         // `none` is ambiguous until we've finished parsing the shorthands, so we count the number
         // of times we see it.
         let mut nones = 0u8;
         let (mut image, mut position, mut list_style_type, mut any) = (None, None, None, false);
         loop {
             if input.try(|input| input.expect_ident_matching("none")).is_ok() {
                 nones = nones + 1;
                 if nones > 2 {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent("none".into()).into())
                 }
                 any = true;
                 continue
             }
 
             if image.is_none() {
                 if let Ok(value) = input.try(|input| list_style_image::parse(context, input)) {
                     image = Some(value);
@@ -99,17 +100,17 @@
             }
             (true, 0, list_style_type, image) => {
                 Ok(expanded! {
                     list_style_position: position,
                     list_style_image: unwrap_or_initial!(list_style_image, image),
                     list_style_type: unwrap_or_initial!(list_style_type),
                 })
             }
-            _ => Err(()),
+            _ => Err(StyleParseError::UnspecifiedError.into()),
         }
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.list_style_position.to_css(dest)?;
             dest.write_str(" ")?;
             self.list_style_image.to_css(dest)?;
--- a/servo/components/style/properties/shorthand/mask.mako.rs
+++ b/servo/components/style/properties/shorthand/mask.mako.rs
@@ -30,17 +30,18 @@
                     mask_clip::single_value::SpecifiedValue::stroke_box,
                 mask_origin::single_value::SpecifiedValue::view_box =>
                     mask_clip::single_value::SpecifiedValue::view_box,
                 % endif
             }
         }
     }
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         % for name in "image mode position_x position_y size repeat origin clip composite".split():
             let mut mask_${name} = mask_${name}::SpecifiedValue(Vec::new());
         % endfor
 
         try!(input.parse_comma_separated(|input| {
             % for name in "image mode position size repeat origin clip composite".split():
                 let mut ${name} = None;
             % endfor
@@ -98,17 +99,17 @@
                         mask_${name}.0.push(m_${name});
                     } else {
                         mask_${name}.0.push(mask_${name}::single_value
                                                         ::get_initial_specified_value());
                     }
                 % endfor
                 Ok(())
             } else {
-                Err(())
+                Err(StyleParseError::UnspecifiedError.into())
             }
         }));
 
         Ok(expanded! {
             % for name in "image mode position_x position_y size repeat origin clip composite".split():
                 mask_${name}: mask_${name},
             % endfor
          })
@@ -175,30 +176,31 @@
 
 <%helpers:shorthand name="mask-position" products="gecko" extra_prefixes="webkit"
                     sub_properties="mask-position-x mask-position-y"
                     spec="https://drafts.csswg.org/css-masks-4/#the-mask-position">
     use properties::longhands::{mask_position_x,mask_position_y};
     use values::specified::position::Position;
     use parser::Parse;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut position_x = mask_position_x::SpecifiedValue(Vec::new());
         let mut position_y = mask_position_y::SpecifiedValue(Vec::new());
         let mut any = false;
 
         input.parse_comma_separated(|input| {
             let value = Position::parse(context, input)?;
             position_x.0.push(value.horizontal);
             position_y.0.push(value.vertical);
             any = true;
             Ok(())
         })?;
         if any == false {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
 
         Ok(expanded! {
             mask_position_x: position_x,
             mask_position_y: position_y,
         })
     }
 
--- a/servo/components/style/properties/shorthand/outline.mako.rs
+++ b/servo/components/style/properties/shorthand/outline.mako.rs
@@ -5,17 +5,18 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <%helpers:shorthand name="outline" sub_properties="outline-color outline-style outline-width"
                     spec="https://drafts.csswg.org/css-ui/#propdef-outline">
     use properties::longhands::{outline_color, outline_width, outline_style};
     use values::specified;
     use parser::Parse;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let _unused = context;
         let mut color = None;
         let mut style = None;
         let mut width = None;
         let mut any = false;
         loop {
             if color.is_none() {
                 if let Ok(value) = input.try(|i| specified::Color::parse(context, i)) {
@@ -42,17 +43,17 @@
         }
         if any {
             Ok(expanded! {
                 outline_color: unwrap_or_initial!(outline_color, color),
                 outline_style: unwrap_or_initial!(outline_style, style),
                 outline_width: unwrap_or_initial!(outline_width, width),
             })
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             try!(self.outline_width.to_css(dest));
             try!(write!(dest, " "));
             try!(self.outline_style.to_css(dest));
@@ -66,17 +67,18 @@
 <%helpers:shorthand name="-moz-outline-radius" sub_properties="${' '.join(
     '-moz-outline-radius-%s' % corner
     for corner in ['topleft', 'topright', 'bottomright', 'bottomleft']
 )}" products="gecko" spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)">
     use values::generics::rect::Rect;
     use values::specified::border::BorderRadius;
     use parser::Parse;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let radii = try!(BorderRadius::parse(context, input));
         Ok(expanded! {
             _moz_outline_radius_topleft: radii.top_left,
             _moz_outline_radius_topright: radii.top_right,
             _moz_outline_radius_bottomright: radii.bottom_right,
             _moz_outline_radius_bottomleft: radii.bottom_left,
         })
     }
--- a/servo/components/style/properties/shorthand/position.mako.rs
+++ b/servo/components/style/properties/shorthand/position.mako.rs
@@ -3,17 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <%helpers:shorthand name="flex-flow" sub_properties="flex-direction flex-wrap" extra_prefixes="webkit"
                     spec="https://drafts.csswg.org/css-flexbox/#flex-flow-property">
     use properties::longhands::{flex_direction, flex_wrap};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut direction = None;
         let mut wrap = None;
         loop {
             if direction.is_none() {
                 if let Ok(value) = input.try(|input| flex_direction::parse(context, input)) {
                     direction = Some(value);
                     continue
                 }
@@ -23,17 +24,17 @@
                     wrap = Some(value);
                     continue
                 }
             }
             break
         }
 
         if direction.is_none() && wrap.is_none() {
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
         Ok(expanded! {
             flex_direction: unwrap_or_initial!(flex_direction, direction),
             flex_wrap: unwrap_or_initial!(flex_wrap, wrap),
         })
     }
 
 
@@ -45,24 +46,25 @@
         }
     }
 </%helpers:shorthand>
 
 <%helpers:shorthand name="flex" sub_properties="flex-grow flex-shrink flex-basis" extra_prefixes="webkit"
                     spec="https://drafts.csswg.org/css-flexbox/#flex-property">
     use values::specified::Number;
 
-    fn parse_flexibility(context: &ParserContext, input: &mut Parser)
-                         -> Result<(Number, Option<Number>),()> {
+    fn parse_flexibility<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                 -> Result<(Number, Option<Number>),ParseError<'i>> {
         let grow = try!(Number::parse_non_negative(context, input));
         let shrink = input.try(|i| Number::parse_non_negative(context, i)).ok();
         Ok((grow, shrink))
     }
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut grow = None;
         let mut shrink = None;
         let mut basis = None;
 
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(expanded! {
                 flex_grow: Number::new(0.0),
                 flex_shrink: Number::new(0.0),
@@ -82,17 +84,17 @@
                     basis = Some(value);
                     continue
                 }
             }
             break
         }
 
         if grow.is_none() && basis.is_none() {
-            return Err(())
+            return Err(StyleParseError::UnspecifiedError.into())
         }
         Ok(expanded! {
             flex_grow: grow.unwrap_or(Number::new(1.0)),
             flex_shrink: shrink.unwrap_or(Number::new(1.0)),
             // Per spec, this should be SpecifiedValue::zero(), but all
             // browsers currently agree on using `0%`. This is a spec
             // change which hasn't been adopted by browsers:
             // https://github.com/w3c/csswg-drafts/commit/2c446befdf0f686217905bdd7c92409f6bd3921b
@@ -113,17 +115,18 @@
     }
 </%helpers:shorthand>
 
 <%helpers:shorthand name="grid-gap" sub_properties="grid-row-gap grid-column-gap"
                     spec="https://drafts.csswg.org/css-grid/#propdef-grid-gap"
                     products="gecko">
   use properties::longhands::{grid_row_gap, grid_column_gap};
 
-  pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+  pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                             -> Result<Longhands, ParseError<'i>> {
       let row_gap = grid_row_gap::parse(context, input)?;
       let column_gap = input.try(|input| grid_column_gap::parse(context, input)).unwrap_or(row_gap.clone());
 
       Ok(expanded! {
         grid_row_gap: row_gap,
         grid_column_gap: column_gap,
       })
   }
@@ -147,17 +150,18 @@
                     spec="https://drafts.csswg.org/css-grid/#propdef-grid-${kind}"
                     products="gecko">
     use values::specified::GridLine;
     use parser::Parse;
 
     // NOTE: Since both the shorthands have the same code, we should (re-)use code from one to implement
     // the other. This might not be a big deal for now, but we should consider looking into this in the future
     // to limit the amount of code generated.
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let start = input.try(|i| GridLine::parse(context, i))?;
         let end = if input.try(|i| i.expect_delim('/')).is_ok() {
             GridLine::parse(context, input)?
         } else {
             let mut line = GridLine::default();
             if start.line_num.is_none() && !start.is_span {
                 line.ident = start.ident.clone();       // ident from start value should be taken
             }
@@ -184,17 +188,18 @@
 <%helpers:shorthand name="grid-area"
                     sub_properties="grid-row-start grid-row-end grid-column-start grid-column-end"
                     spec="https://drafts.csswg.org/css-grid/#propdef-grid-area"
                     products="gecko">
     use values::specified::GridLine;
     use parser::Parse;
 
     // The code is the same as `grid-{row,column}` except that this can have four values at most.
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         fn line_with_ident_from(other: &GridLine) -> GridLine {
             let mut this = GridLine::default();
             if other.line_num.is_none() && !other.is_span {
                 this.ident = other.ident.clone();
             }
 
             this
         }
@@ -254,18 +259,19 @@
     use properties::longhands::grid_template_rows;
     use properties::longhands::grid_template_areas::TemplateAreas;
     use values::{Either, None_};
     use values::generics::grid::{TrackSize, TrackList, TrackListType, concat_serialize_idents};
     use values::specified::TrackListOrNone;
     use values::specified::grid::parse_line_names;
 
     /// Parsing for `<grid-template>` shorthand (also used by `grid` shorthand).
-    pub fn parse_grid_template(context: &ParserContext, input: &mut Parser)
-                               -> Result<(TrackListOrNone, TrackListOrNone, Either<TemplateAreas, None_>), ()> {
+    pub fn parse_grid_template<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                                       -> Result<(TrackListOrNone, TrackListOrNone, Either<TemplateAreas, None_>),
+                                                 ParseError<'i>> {
         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
             return Ok((Either::Second(None_), Either::Second(None_), Either::Second(None_)))
         }
 
         let first_line_names = input.try(parse_line_names).unwrap_or(vec![]);
         if let Ok(s) = input.try(Parser::expect_string) {
             let mut strings = vec![];
             let mut values = vec![];
@@ -290,28 +296,29 @@
                     },
                 };
             }
 
             if line_names.len() == values.len() {
                 line_names.push(vec![]);        // should be one longer than track sizes
             }
 
-            let template_areas = TemplateAreas::from_vec(strings)?;
+            let template_areas = TemplateAreas::from_vec(strings)
+                .map_err(|()| StyleParseError::UnspecifiedError)?;
             let template_rows = TrackList {
                 list_type: TrackListType::Normal,
                 values: values,
                 line_names: line_names,
                 auto_repeat: None,
             };
 
             let template_cols = if input.try(|i| i.expect_delim('/')).is_ok() {
                 let track_list = TrackList::parse(context, input)?;
                 if track_list.list_type != TrackListType::Explicit {
-                    return Err(())
+                    return Err(StyleParseError::UnspecifiedError.into())
                 }
 
                 Either::First(track_list)
             } else {
                 Either::Second(None_)
             };
 
             Ok((Either::First(template_rows), template_cols, Either::First(template_areas)))
@@ -321,17 +328,18 @@
                 list.line_names[0] = first_line_names;      // won't panic
             }
 
             Ok((template_rows, grid_template_rows::parse(context, input)?, Either::Second(None_)))
         }
     }
 
     #[inline]
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let (rows, columns, areas) = parse_grid_template(context, input)?;
         Ok(expanded! {
             grid_template_rows: rows,
             grid_template_columns: columns,
             grid_template_areas: areas,
         })
     }
 
@@ -405,25 +413,27 @@
                     disable_when_testing="True"
                     products="gecko">
     use properties::longhands::{grid_auto_columns, grid_auto_rows, grid_auto_flow};
     use properties::longhands::{grid_template_columns, grid_template_rows};
     use properties::longhands::grid_auto_flow::computed_value::{AutoFlow, T as SpecifiedAutoFlow};
     use values::{Either, None_};
     use values::specified::{LengthOrPercentage, TrackSize};
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let mut temp_rows = Either::Second(None_);
         let mut temp_cols = Either::Second(None_);
         let mut temp_areas = Either::Second(None_);
         let mut auto_rows = TrackSize::default();
         let mut auto_cols = TrackSize::default();
         let mut flow = grid_auto_flow::get_initial_value();
 
-        fn parse_auto_flow(input: &mut Parser, is_row: bool) -> Result<SpecifiedAutoFlow, ()> {
+        fn parse_auto_flow<'i, 't>(input: &mut Parser<'i, 't>, is_row: bool)
+                                   -> Result<SpecifiedAutoFlow, ParseError<'i>> {
             let mut auto_flow = None;
             let mut dense = false;
             for _ in 0..2 {
                 if input.try(|i| i.expect_ident_matching("auto-flow")).is_ok() {
                     auto_flow = if is_row {
                         Some(AutoFlow::Row)
                     } else {
                         Some(AutoFlow::Column)
@@ -435,17 +445,17 @@
                 }
             }
 
             auto_flow.map(|flow| {
                 SpecifiedAutoFlow {
                     autoflow: flow,
                     dense: dense,
                 }
-            }).ok_or(())
+            }).ok_or(StyleParseError::UnspecifiedError.into())
         }
 
         if let Ok((rows, cols, areas)) = input.try(|i| super::grid_template::parse_grid_template(context, i)) {
             temp_rows = rows;
             temp_cols = cols;
             temp_areas = areas;
         } else if let Ok(rows) = input.try(|i| grid_template_rows::parse(context, i)) {
             temp_rows = rows;
@@ -501,25 +511,26 @@
 </%helpers:shorthand>
 
 <%helpers:shorthand name="place-content" sub_properties="align-content justify-content"
                     spec="https://drafts.csswg.org/css-align/#propdef-place-content"
                     products="gecko" disable_when_testing="True">
     use properties::longhands::align_content;
     use properties::longhands::justify_content;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let align = align_content::parse(context, input)?;
         if align.has_extra_flags() {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
         let justify = input.try(|input| justify_content::parse(context, input))
                            .unwrap_or(justify_content::SpecifiedValue::from(align));
         if justify.has_extra_flags() {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
 
         Ok(expanded! {
             align_content: align,
             justify_content: justify,
         })
     }
 
@@ -536,24 +547,25 @@
 </%helpers:shorthand>
 
 <%helpers:shorthand name="place-self" sub_properties="align-self justify-self"
                     spec="https://drafts.csswg.org/css-align/#place-self-property"
                     products="gecko" disable_when_testing="True">
     use values::specified::align::AlignJustifySelf;
     use parser::Parse;
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let align = AlignJustifySelf::parse(context, input)?;
         if align.has_extra_flags() {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
         let justify = input.try(|input| AlignJustifySelf::parse(context, input)).unwrap_or(align.clone());
         if justify.has_extra_flags() {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
 
         Ok(expanded! {
             align_self: align,
             justify_self: justify,
         })
     }
 
@@ -577,25 +589,26 @@
     use parser::Parse;
 
     impl From<AlignItems> for JustifyItems {
         fn from(align: AlignItems) -> JustifyItems {
             JustifyItems(align.0)
         }
     }
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         let align = AlignItems::parse(context, input)?;
         if align.has_extra_flags() {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
         let justify = input.try(|input| JustifyItems::parse(context, input))
                            .unwrap_or(JustifyItems::from(align));
         if justify.has_extra_flags() {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
 
         Ok(expanded! {
             align_items: align,
             justify_items: justify,
         })
     }
 
--- a/servo/components/style/properties/shorthand/text.mako.rs
+++ b/servo/components/style/properties/shorthand/text.mako.rs
@@ -11,17 +11,18 @@
 
     % if product == "gecko" or data.testing:
         use values::specified;
         use properties::longhands::{text_decoration_line, text_decoration_style, text_decoration_color};
     % else:
         use properties::longhands::text_decoration_line;
     % endif
 
-    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
+    pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                               -> Result<Longhands, ParseError<'i>> {
         % if product == "gecko" or data.testing:
             let (mut line, mut style, mut color, mut any) = (None, None, None, false);
         % else:
             let (mut line, mut any) = (None, false);
         % endif
 
         loop {
             macro_rules! parse_component {
@@ -42,17 +43,17 @@
                 parse_component!(style, text_decoration_style);
                 parse_component!(color, text_decoration_color);
             % endif
 
             break;
         }
 
         if !any {
-            return Err(());
+            return Err(StyleParseError::UnspecifiedError.into());
         }
 
         Ok(expanded! {
             text_decoration_line: unwrap_or_initial!(text_decoration_line, line),
 
             % if product == "gecko" or data.testing:
                 text_decoration_style: unwrap_or_initial!(text_decoration_style, style),
                 text_decoration_color: unwrap_or_initial!(text_decoration_color, color),
--- a/servo/components/style/selector_parser.rs
+++ b/servo/components/style/selector_parser.rs
@@ -1,20 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! The pseudo-classes and pseudo-elements supported by the style system.
 
 #![deny(missing_docs)]
 
-use cssparser::Parser as CssParser;
+use cssparser::{Parser as CssParser, ParserInput};
 use selectors::Element;
 use selectors::parser::SelectorList;
 use std::fmt::Debug;
+use style_traits::ParseError;
 use stylesheets::{Origin, Namespaces};
 
 /// A convenient alias for the type that represents an attribute value used for
 /// selector parser implementation.
 pub type AttrValue = <SelectorImpl as ::selectors::SelectorImpl>::AttrValue;
 
 #[cfg(feature = "servo")]
 pub use servo::selector_parser::*;
@@ -54,23 +55,24 @@ pub struct SelectorParser<'a> {
 }
 
 impl<'a> SelectorParser<'a> {
     /// Parse a selector list with an author origin and without taking into
     /// account namespaces.
     ///
     /// This is used for some DOM APIs like `querySelector`.
     pub fn parse_author_origin_no_namespace(input: &str)
-                                            -> Result<SelectorList<SelectorImpl>, ()> {
+                                            -> Result<SelectorList<SelectorImpl>, ParseError> {
         let namespaces = Namespaces::default();
         let parser = SelectorParser {
             stylesheet_origin: Origin::Author,
             namespaces: &namespaces,
         };
-        SelectorList::parse(&parser, &mut CssParser::new(input))
+        let mut input = ParserInput::new(input);
+        SelectorList::parse(&parser, &mut CssParser::new(&mut input))
     }
 
     /// Whether we're parsing selectors in a user-agent stylesheet.
     pub fn in_user_agent_stylesheet(&self) -> bool {
         matches!(self.stylesheet_origin, Origin::UserAgent)
     }
 }
 
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -8,19 +8,20 @@ use app_units::Au;
 use context::QuirksMode;
 use cssparser::{Parser, RGBA};
 use euclid::{Size2D, TypedSize2D};
 use font_metrics::ServoMetricsProvider;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
+use selectors::parser::SelectorParseError;
 use std::fmt;
 use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
-use style_traits::{CSSPixel, ToCss};
+use style_traits::{CSSPixel, ToCss, ParseError};
 use style_traits::viewport::ViewportConstraints;
 use values::computed::{self, ToComputedValue};
 use values::specified;
 
 /// A device is a structure that represents the current media a given document
 /// is displayed in.
 ///
 /// This is the struct against which media queries are evaluated.
@@ -146,33 +147,34 @@ impl Expression {
 
     /// Parse a media expression of the form:
     ///
     /// ```
     /// (media-feature: media-value)
     /// ```
     ///
     /// Only supports width and width ranges for now.
-    pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+                         -> Result<Self, ParseError<'i>> {
         try!(input.expect_parenthesis_block());
         input.parse_nested_block(|input| {
             let name = try!(input.expect_ident());
             try!(input.expect_colon());
             // TODO: Handle other media features
             Ok(Expression(match_ignore_ascii_case! { &name,
                 "min-width" => {
                     ExpressionKind::Width(Range::Min(try!(specified::Length::parse_non_negative(context, input))))
                 },
                 "max-width" => {
                     ExpressionKind::Width(Range::Max(try!(specified::Length::parse_non_negative(context, input))))
                 },
                 "width" => {
                     ExpressionKind::Width(Range::Eq(try!(specified::Length::parse_non_negative(context, input))))
                 },
-                _ => return Err(())
+                _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
             }))
         })
     }
 
     /// Evaluate this expression and return whether it matches the current
     /// device.
     pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
         let viewport_size = device.au_viewport_size();
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -11,24 +11,25 @@ use attr::{AttrIdentifier, AttrValue};
 use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
 use dom::{OpaqueNode, TElement, TNode};
 use element_state::ElementState;
 use fnv::FnvHashMap;
 use restyle_hints::ElementSnapshot;
 use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser};
 use selectors::Element;
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::parser::SelectorMethods;
+use selectors::parser::{SelectorMethods, SelectorParseError};
 use selectors::visitor::SelectorVisitor;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
 use std::fmt::Debug;
 use std::mem;
 use std::ops::{Deref, DerefMut};
+use style_traits::{ParseError, StyleParseError};
 
 /// A pseudo-element, both public and private.
 ///
 /// NB: If you add to this list, be sure to update `each_pseudo_element` too.
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 #[repr(usize)]
@@ -295,20 +296,22 @@ impl ::selectors::SelectorImpl for Selec
     type ClassName = Atom;
     type LocalName = LocalName;
     type NamespacePrefix = Prefix;
     type NamespaceUrl = Namespace;
     type BorrowedLocalName = LocalName;
     type BorrowedNamespaceUrl = Namespace;
 }
 
-impl<'a> ::selectors::Parser for SelectorParser<'a> {
+impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
     type Impl = SelectorImpl;
+    type Error = StyleParseError<'i>;
 
-    fn parse_non_ts_pseudo_class(&self, name: Cow<str>) -> Result<NonTSPseudoClass, ()> {
+    fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
+                                 -> Result<NonTSPseudoClass, ParseError<'i>> {
         use self::NonTSPseudoClass::*;
         let pseudo_class = match_ignore_ascii_case! { &name,
             "active" => Active,
             "any-link" => AnyLink,
             "checked" => Checked,
             "disabled" => Disabled,
             "enabled" => Enabled,
             "focus" => Focus,
@@ -318,127 +321,128 @@ impl<'a> ::selectors::Parser for Selecto
             "link" => Link,
             "placeholder-shown" => PlaceholderShown,
             "read-write" => ReadWrite,
             "read-only" => ReadOnly,
             "target" => Target,
             "visited" => Visited,
             "-servo-nonzero-border" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(());
+                    return Err(SelectorParseError::UnexpectedIdent(
+                        "-servo-nonzero-border".into()).into());
                 }
                 ServoNonZeroBorder
             },
-            _ => return Err(())
+            _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into()),
         };
 
         Ok(pseudo_class)
     }
 
-    fn parse_non_ts_functional_pseudo_class(&self,
-                                            name: Cow<str>,
-                                            parser: &mut CssParser)
-                                            -> Result<NonTSPseudoClass, ()> {
+    fn parse_non_ts_functional_pseudo_class<'t>(&self,
+                                                name: Cow<'i, str>,
+                                                parser: &mut CssParser<'i, 't>)
+                                                -> Result<NonTSPseudoClass, ParseError<'i>> {
         use self::NonTSPseudoClass::*;
         let pseudo_class = match_ignore_ascii_case!{ &name,
             "lang" => {
                 Lang(parser.expect_ident_or_string()?.into_owned().into_boxed_str())
             }
             "-servo-case-sensitive-type-attr" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(());
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into());
                 }
                 ServoCaseSensitiveTypeAttr(Atom::from(parser.expect_ident()?))
             }
-            _ => return Err(())
+            _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
         };
 
         Ok(pseudo_class)
     }
 
-    fn parse_pseudo_element(&self, name: Cow<str>)
-                            -> Result<PseudoElement, ()> {
+    fn parse_pseudo_element(&self, name: Cow<'i, str>) -> Result<PseudoElement, ParseError<'i>> {
         use self::PseudoElement::*;
         let pseudo_element = match_ignore_ascii_case! { &name,
             "before" => Before,
             "after" => After,
             "selection" => Selection,
             "-servo-details-summary" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 DetailsSummary
             },
             "-servo-details-content" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 DetailsContent
             },
             "-servo-text" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoText
             },
             "-servo-input-text" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoInputText
             },
             "-servo-table-wrapper" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoTableWrapper
             },
             "-servo-anonymous-table-wrapper" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoAnonymousTableWrapper
             },
             "-servo-anonymous-table" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoAnonymousTable
             },
             "-servo-anonymous-table-row" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoAnonymousTableRow
             },
             "-servo-anonymous-table-cell" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoAnonymousTableCell
             },
             "-servo-anonymous-block" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoAnonymousBlock
             },
             "-servo-inline-block-wrapper" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoInlineBlockWrapper
             },
             "-servo-input-absolute" => {
                 if !self.in_user_agent_stylesheet() {
-                    return Err(())
+                    return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
                 ServoInlineAbsolute
             },
-            _ => return Err(())
+            _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
+
         };
 
         Ok(pseudo_element)
     }
 
     fn default_namespace(&self) -> Option<Namespace> {
         self.namespaces.default.as_ref().map(|&(ref ns, _)| ns.clone())
     }
--- a/servo/components/style/servo/url.rs
+++ b/servo/components/style/servo/url.rs
@@ -8,17 +8,17 @@ use cssparser::CssStringWriter;
 use parser::ParserContext;
 use servo_url::ServoUrl;
 use std::borrow::Cow;
 use std::fmt::{self, Write};
 // Note: We use std::sync::Arc rather than stylearc::Arc here because the
 // nonzero optimization is important in keeping the size of SpecifiedUrl below
 // the threshold.
 use std::sync::Arc;
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError};
 
 /// A specified url() value for servo.
 ///
 /// Servo eagerly resolves SpecifiedUrls, which it can then take advantage of
 /// when computing values. In contrast, Gecko uses a different URL backend, so
 /// eagerly resolving with rust-url would be duplicated work.
 ///
 /// However, this approach is still not necessarily optimal: See
@@ -38,17 +38,17 @@ pub struct SpecifiedUrl {
 }
 
 impl SpecifiedUrl {
     /// Try to parse a URL from a string value that is a valid CSS token for a
     /// URL. Never fails - the API is only fallible to be compatible with the
     /// gecko version.
     pub fn parse_from_string<'a>(url: Cow<'a, str>,
                                  context: &ParserContext)
-                                 -> Result<Self, ()> {
+                                 -> Result<Self, ParseError<'a>> {
         let serialization = Arc::new(url.into_owned());
         let resolved = context.url_data.join(&serialization).ok();
         Ok(SpecifiedUrl {
             original: Some(serialization),
             resolved: resolved,
         })
     }
 
--- a/servo/components/style/stylesheets/document_rule.rs
+++ b/servo/components/style/stylesheets/document_rule.rs
@@ -1,22 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document)
 //! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4.
 //! We implement the prefixed `@-moz-document`.
 
-use cssparser::{Parser, Token, SourceLocation};
+use cssparser::{Parser, Token, SourceLocation, BasicParseError};
 use media_queries::Device;
 use parser::{Parse, ParserContext};
 use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt;
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError, StyleParseError};
 use stylearc::Arc;
 use stylesheets::CssRules;
 use values::specified::url::SpecifiedUrl;
 
 #[derive(Debug)]
 /// A @-moz-document rule
 pub struct DocumentRule {
     /// The parsed condition
@@ -85,42 +85,43 @@ pub enum UrlMatchingFunction {
 macro_rules! parse_quoted_or_unquoted_string {
     ($input:ident, $url_matching_function:expr) => {
         $input.parse_nested_block(|input| {
             let start = input.position();
             input.parse_entirely(|input| {
                 match input.next() {
                     Ok(Token::QuotedString(value)) =>
                         Ok($url_matching_function(value.into_owned())),
-                    _ => Err(()),
+                    Ok(t) => Err(BasicParseError::UnexpectedToken(t).into()),
+                    Err(e) => Err(e.into()),
                 }
-            }).or_else(|_| {
+            }).or_else(|_: ParseError| {
                 while let Ok(_) = input.next() {}
                 Ok($url_matching_function(input.slice_from(start).to_string()))
             })
         })
     }
 }
 
 impl UrlMatchingFunction {
     /// Parse a URL matching function for a`@document` rule's condition.
-    pub fn parse(context: &ParserContext, input: &mut Parser)
-        -> Result<UrlMatchingFunction, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+        -> Result<UrlMatchingFunction, ParseError<'i>> {
         if input.try(|input| input.expect_function_matching("url-prefix")).is_ok() {
             parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::UrlPrefix)
         } else if input.try(|input| input.expect_function_matching("domain")).is_ok() {
             parse_quoted_or_unquoted_string!(input, UrlMatchingFunction::Domain)
         } else if input.try(|input| input.expect_function_matching("regexp")).is_ok() {
             input.parse_nested_block(|input| {
                 Ok(UrlMatchingFunction::RegExp(input.expect_string()?.into_owned()))
             })
         } else if let Ok(url) = input.try(|input| SpecifiedUrl::parse(context, input)) {
             Ok(UrlMatchingFunction::Url(url))
         } else {
-            Err(())
+            Err(StyleParseError::UnspecifiedError.into())
         }
     }
 
     #[cfg(feature = "gecko")]
     /// Evaluate a URL matching function.
     pub fn evaluate(&self, device: &Device) -> bool {
         use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
         use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction;
@@ -184,18 +185,18 @@ impl ToCss for UrlMatchingFunction {
 /// The `@document` rule's condition is written as a comma-separated list of
 /// URL matching functions, and the condition evaluates to true whenever any
 /// one of those functions evaluates to true.
 #[derive(Clone, Debug)]
 pub struct DocumentCondition(Vec<UrlMatchingFunction>);
 
 impl DocumentCondition {
     /// Parse a document condition.
-    pub fn parse(context: &ParserContext, input: &mut Parser)
-        -> Result<Self, ()> {
+    pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
+        -> Result<Self, ParseError<'i>> {
         input.parse_comma_separated(|input| UrlMatchingFunction::parse(context, input))
              .map(DocumentCondition)
     }
 
     /// Evaluate a document condition.
     pub fn evaluate(&self, device: &Device) -> bool {
         self.0.iter().any(|ref url_matching_function|
             url_matching_function.evaluate(device)
--- a/servo/components/style/stylesheets/keyframes_rule.rs
+++ b/servo/components/style/stylesheets/keyframes_rule.rs
@@ -1,26 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Keyframes: https://drafts.csswg.org/css-animations/#keyframes
 
-use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
+use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser, ParserInput};
 use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule, SourceLocation};
-use error_reporting::NullReporter;
+use error_reporting::{NullReporter, ContextualParseError};
 use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId};
 use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration};
 use properties::LonghandIdSet;
 use properties::animated_properties::TransitionProperty;
 use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
+use selectors::parser::SelectorParseError;
 use shared_lock::{DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard};
+use std::borrow::Cow;
 use std::fmt;
-use style_traits::ToCss;
+use style_traits::{ToCss, ParseError, StyleParseError};
 use stylearc::Arc;
 use stylesheets::{CssRuleType, Stylesheet};
 use stylesheets::rule_parser::VendorPrefix;
 use values::KeyframesName;
 
 /// A [`@keyframes`][keyframes] rule.
 ///
 /// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
@@ -56,17 +58,18 @@ impl ToCssWithGuard for KeyframesRule {
 
 impl KeyframesRule {
     /// Returns the index of the last keyframe that matches the given selector.
     /// If the selector is not valid, or no keyframe is found, returns None.
     ///
     /// Related spec:
     /// https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule
     pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> {
-        if let Ok(selector) = Parser::new(selector).parse_entirely(KeyframeSelector::parse) {
+        let mut input = ParserInput::new(selector);
+        if let Ok(selector) = Parser::new(&mut input).parse_entirely(KeyframeSelector::parse) {
             for (i, keyframe) in self.keyframes.iter().enumerate().rev() {
                 if keyframe.read_with(guard).selector == selector {
                     return Some(i);
                 }
             }
         }
         None
     }
@@ -115,27 +118,27 @@ impl ToCss for KeyframePercentage {
 impl KeyframePercentage {
     /// Trivially constructs a new `KeyframePercentage`.
     #[inline]
     pub fn new(value: f32) -> KeyframePercentage {
         debug_assert!(value >= 0. && value <= 1.);
         KeyframePercentage(value)
     }
 
-    fn parse(input: &mut Parser) -> Result<KeyframePercentage, ()> {
+    fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<KeyframePercentage, ParseError<'i>> {
         let percentage = if input.try(|input| input.expect_ident_matching("from")).is_ok() {
             KeyframePercentage::new(0.)
         } else if input.try(|input| input.expect_ident_matching("to")).is_ok() {
             KeyframePercentage::new(1.)
         } else {
             let percentage = try!(input.expect_percentage());
             if percentage >= 0. && percentage <= 1. {
                 KeyframePercentage::new(percentage)
             } else {
-                return Err(());
+                return Err(StyleParseError::UnspecifiedError.into());
             }
         };
 
         Ok(percentage)
     }
 }
 
 /// A keyframes selector is a list of percentages or from/to symbols, which are
@@ -163,17 +166,17 @@ impl KeyframeSelector {
     }
 
     /// A dummy public function so we can write a unit test for this.
     pub fn new_for_unit_testing(percentages: Vec<KeyframePercentage>) -> KeyframeSelector {
         KeyframeSelector(percentages)
     }
 
     /// Parse a keyframe selector from CSS input.
-    pub fn parse(input: &mut Parser) -> Result<Self, ()> {
+    pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         input.parse_comma_separated(KeyframePercentage::parse)
              .map(KeyframeSelector)
     }
 }
 
 /// A keyframe.