merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 10 Jun 2017 11:18:21 +0200
changeset 413803 91dc9525c422f11041da33b008b14a8117ed9a40
parent 413789 a305f9e66fa5b6faec4804ace67bb69f3c5de2d3 (diff)
parent 413802 f4b350008c452a890b207afac42fbc530e1e8ec0 (current diff)
child 413804 49c375200aabad932ccfad27986a6fb7b79e9db8
child 413808 158930f1c0d31038b5015cdf60221e50bebcfd89
child 413858 b7e57100a79cc71dba99280b3a2b4aba719d8565
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone55.0a1
first release with
nightly linux32
91dc9525c422 / 55.0a1 / 20170610100201 / files
nightly linux64
91dc9525c422 / 55.0a1 / 20170610100201 / files
nightly mac
91dc9525c422 / 55.0a1 / 20170610030207 / files
nightly win32
91dc9525c422 / 55.0a1 / 20170610030207 / files
nightly win64
91dc9525c422 / 55.0a1 / 20170610030207 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: FeTjZsgM7om
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -68,16 +68,20 @@ support-files =
 skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
 [browser_urlbarFocusedCmdK.js]
 [browser_urlbarHashChangeProxyState.js]
 [browser_urlbarKeepStateAcrossTabSwitches.js]
 [browser_urlbarOneOffs.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
+[browser_urlbarOneOffs_searchSuggestions.js]
+support-files =
+  searchSuggestionEngine.xml
+  searchSuggestionEngine.sjs
 [browser_urlbarPrivateBrowsingWindowChange.js]
 [browser_urlbarRaceWithTabs.js]
 [browser_urlbarRevert.js]
 [browser_urlbarSearchSingleWordNotification.js]
 [browser_urlbarSearchSuggestions.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
--- a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
@@ -191,17 +191,17 @@ add_task(async function oneOffClick() {
   let typedValue = "foo.bar";
   await promiseAutocompleteResultPopup(typedValue);
 
   assertState(0, -1, typedValue);
 
   let oneOffs = gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true);
   let resultsPromise =
     BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
-                                   "http://mochi.test:8888/");
+                                   "http://mochi.test:8888/?terms=foo.bar");
   EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
   await resultsPromise;
 
   gBrowser.removeTab(gBrowser.selectedTab);
 });
 
 // Presses the Return key when a one-off is selected.
 add_task(async function oneOffReturn() {
@@ -215,17 +215,17 @@ add_task(async function oneOffReturn() {
   assertState(0, -1, typedValue);
 
   // Alt+Down to select the first one-off.
   EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
   assertState(0, 0, typedValue);
 
   let resultsPromise =
     BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
-                                   "http://mochi.test:8888/");
+                                   "http://mochi.test:8888/?terms=foo.bar");
   EventUtils.synthesizeKey("VK_RETURN", {})
   await resultsPromise;
 
   gBrowser.removeTab(gBrowser.selectedTab);
 });
 
 
 function assertState(result, oneOff, textValue = undefined) {
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs_searchSuggestions.js
@@ -0,0 +1,115 @@
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+add_task(async function init() {
+  await PlacesTestUtils.clearHistory();
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.oneOffSearches", true],
+          ["browser.urlbar.suggest.searches", true]],
+  });
+  let engine = await promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let oldCurrentEngine = Services.search.currentEngine;
+  Services.search.moveEngine(engine, 0);
+  Services.search.currentEngine = engine;
+  registerCleanupFunction(async function() {
+    Services.search.currentEngine = oldCurrentEngine;
+
+    await PlacesTestUtils.clearHistory();
+    // Make sure the popup is closed for the next test.
+    gURLBar.blur();
+    Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+  });
+});
+
+// Presses the Return key when a one-off is selected after selecting a search
+// suggestion.
+add_task(async function oneOffReturnAfterSuggestion() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+  let typedValue = "foo";
+  await promiseAutocompleteResultPopup(typedValue, window, true);
+  await BrowserTestUtils.waitForCondition(suggestionsPresent,
+                                          "waiting for suggestions");
+  assertState(0, -1, typedValue);
+
+  // Down to select the first search suggestion.
+  EventUtils.synthesizeKey("VK_DOWN", {})
+  assertState(1, -1, "foofoo");
+
+  // Down to select the next search suggestion.
+  EventUtils.synthesizeKey("VK_DOWN", {})
+  assertState(2, -1, "foobar");
+
+  // Alt+Down to select the first one-off.
+  EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
+  assertState(2, 0, "foobar");
+
+  let resultsPromise =
+    BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+                                   `http://mochi.test:8888/?terms=foobar`);
+  EventUtils.synthesizeKey("VK_RETURN", {});
+  await resultsPromise;
+
+  await PlacesTestUtils.clearHistory();
+  await BrowserTestUtils.removeTab(tab);
+});
+
+// Clicks a one-off engine after selecting a search suggestion.
+add_task(async function oneOffClickAfterSuggestion() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+  let typedValue = "foo";
+  await promiseAutocompleteResultPopup(typedValue, window, true);
+  await BrowserTestUtils.waitForCondition(suggestionsPresent,
+                                          "waiting for suggestions");
+  assertState(0, -1, typedValue);
+
+  // Down to select the first search suggestion.
+  EventUtils.synthesizeKey("VK_DOWN", {})
+  assertState(1, -1, "foofoo");
+
+  // Down to select the next search suggestion.
+  EventUtils.synthesizeKey("VK_DOWN", {})
+  assertState(2, -1, "foobar");
+
+  let oneOffs = gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true);
+  let resultsPromise =
+    BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+                                   `http://mochi.test:8888/?terms=foobar`);
+  EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
+  await resultsPromise;
+
+  await PlacesTestUtils.clearHistory();
+  await BrowserTestUtils.removeTab(tab);
+});
+
+function assertState(result, oneOff, textValue = undefined) {
+  Assert.equal(gURLBar.popup.selectedIndex, result,
+               "Expected result should be selected");
+  Assert.equal(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, oneOff,
+               "Expected one-off should be selected");
+  if (textValue !== undefined) {
+    Assert.equal(gURLBar.textValue, textValue, "Expected textValue");
+  }
+}
+
+async function hidePopup() {
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+  await promisePopupHidden(gURLBar.popup);
+}
+
+function suggestionsPresent() {
+  let controller = gURLBar.popup.input.controller;
+  let matchCount = controller.matchCount;
+  for (let i = 0; i < matchCount; i++) {
+    let url = controller.getValueAt(i);
+    let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+    if (mozActionMatch) {
+      let [, type, paramStr] = mozActionMatch;
+      let params = JSON.parse(paramStr);
+      if (type == "searchengine" && "searchSuggestion" in params) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
--- a/browser/base/content/test/urlbar/searchSuggestionEngine.xml
+++ b/browser/base/content/test/urlbar/searchSuggestionEngine.xml
@@ -1,9 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
 <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/urlbar/searchSuggestionEngine.sjs?{searchTerms}"/>
-<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform">
+  <Param name="terms" value="{searchTerms}"/>
+</Url>
 </SearchPlugin>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -668,16 +668,23 @@ file, You can obtain one at http://mozil
 
           this._loadURL(url, browser, postData, where, openUILinkParams,
                         mayInheritPrincipal);
         ]]></body>
       </method>
 
       <property name="oneOffSearchQuery">
         <getter><![CDATA[
+          // If the user has selected a search suggestion, chances are they
+          // want to use the one off search engine to search for that suggestion,
+          // not the string that they manually entered into the location bar.
+          let action = this._parseActionUrl(this.value);
+          if (action && action.type == "searchengine") {
+            return action.params.input;
+          }
           // this.textValue may be an autofilled string.  Search only with the
           // portion that the user typed, if any, by preferring the autocomplete
           // controller's searchString (including handleEnterInstance.searchString).
           return this.handleEnterSearchString ||
                  this.mController.searchString ||
                  this.textValue;
         ]]></getter>
       </property>
--- a/browser/extensions/shield-recipe-client/skin/osx/Heartbeat.css
+++ b/browser/extensions/shield-recipe-client/skin/osx/Heartbeat.css
@@ -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/. */
 
 /* Notification overrides for Heartbeat UI */
 
 notification.heartbeat {
-  background-image: linear-gradient(-179deg, #FBFBFB 0%, #EBEBEB 100%);
-  border-bottom: 1px solid #C1C1C1;
+  background-image: linear-gradient(-179deg, #FBFBFB 0%, #EBEBEB 100%) !important;
+  border-bottom: 1px solid #C1C1C1 !important;
   height: 40px;
 }
 
 /* In themes/osx/global/notification.css the close icon is inverted because notifications
    on OSX are usually dark. Heartbeat is light, so override that behaviour. */
 
 notification.heartbeat[type="info"] .close-icon:not(:hover) {
   -moz-image-region: rect(0, 16px, 16px, 0) !important;
--- a/browser/extensions/shield-recipe-client/skin/shared/Heartbeat.css
+++ b/browser/extensions/shield-recipe-client/skin/shared/Heartbeat.css
@@ -1,23 +1,20 @@
 /* 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/. */
 
 /* Notification overrides for Heartbeat UI */
 
 notification.heartbeat {
-  background-color: #F1F1F1;
-  border-bottom: 1px solid #C1C1C1;
+  background-color: #F1F1F1 !important;
+  border-bottom: 1px solid #C1C1C1 !important;
   height: 40px;
 }
 
-/* In themes/osx/global/notification.css the close icon is inverted because notifications
-   on OSX are usually dark. Heartbeat is light, so override that behaviour. */
-
 @keyframes pulse-onshow {
   0% {
     opacity: 0;
     transform: scale(1);
   }
 
   25% {
     opacity: 1;
@@ -47,27 +44,27 @@ notification.heartbeat {
   }
 
   100% {
     transform: scale(1);
   }
 }
 
 .messageText.heartbeat {
-  color: #333;
+  color: #333 !important;
   margin-inline-end: 12px !important; /* The !important is required to override OSX default style. */
   margin-inline-start: 0;
   text-shadow: none;
 }
 
 .messageImage.heartbeat {
-  height: 24px;
-  margin-inline-end: 8px;
-  margin-inline-start: 8px;
-  width: 24px;
+  height: 24px !important;
+  margin-inline-end: 8px !important;
+  margin-inline-start: 8px !important;
+  width: 24px !important;
 }
 
 .messageImage.heartbeat.pulse-onshow {
   animation-duration: 1.5s;
   animation-iteration-count: 1;
   animation-name: pulse-onshow;
   animation-timing-function: cubic-bezier(0.7, 1.8, 0.9, 1.1);
 }
@@ -76,27 +73,27 @@ notification.heartbeat {
   animation-duration: 1s;
   animation-iteration-count: 2;
   animation-name: pulse-twice;
   animation-timing-function: linear;
 }
 
 /* Learn More link styles */
 .heartbeat > .text-link {
-  color: #0095DD;
-  margin-inline-start: 0;
+  color: #0095DD !important;
+  margin-inline-start: 0 !important;
 }
 
 .heartbeat > .text-link:hover {
-  color: #008ACB;
-  text-decoration: none;
+  color: #008ACB !important;
+  text-decoration: none !important;
 }
 
 .heartbeat > .text-link:hover:active {
-  color: #006B9D;
+  color: #006B9D !important;
 }
 
 /* Heartbeat UI Rating Star Classes */
 .heartbeat > #star-rating-container {
   display: -moz-box;
   margin-bottom: 4px;
 }
 
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -610,22 +610,26 @@ description > html|a {
 .help-button:link,
 .help-button:visited {
   color: var(--in-content-category-text);
   text-decoration: none;
 }
 
 .search-tooltip {
   position: absolute;
-  pointer-events: none;
   padding: 0 10px;
   bottom: 100%;
   background-color: #ffe900;
 }
 
+.search-tooltip:hover,
+.search-tooltip:hover::before {
+  filter: opacity(10%);
+}
+
 .search-tooltip::before {
   position: absolute;
   content: "";
   border: 6px solid transparent;
   border-top-color: #ffe900;
   top: 100%;
   offset-inline-start: calc(50% - 6px);
 }
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -2021,25 +2021,23 @@ CanvasRenderingContext2D::InitializeWith
   if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
     // Cf comment in EnsureTarget
     mTarget->PushClipRect(gfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
+void
 CanvasRenderingContext2D::SetIsOpaque(bool aIsOpaque)
 {
   if (aIsOpaque != mOpaque) {
     mOpaque = aIsOpaque;
     ClearTarget();
   }
-
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::SetIsIPC(bool aIsIPC)
 {
   if (aIsIPC != mIPC) {
     mIPC = aIsIPC;
     ClearTarget();
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -456,17 +456,17 @@ public:
   {
     EnsureTarget();
     if (aOutAlphaType) {
       *aOutAlphaType = (mOpaque ? gfxAlphaType::Opaque : gfxAlphaType::Premult);
     }
     return mTarget->Snapshot();
   }
 
-  NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
+  virtual void SetIsOpaque(bool aIsOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                          Layer* aOldLayer,
                                          LayerManager* aManager,
                                          bool aMirror = false) override;
   virtual bool ShouldForceInactiveLayer(LayerManager* aManager) override;
   void MarkContextClean() override;
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -232,23 +232,19 @@ CanvasRenderingContextHelper::UpdateCont
 {
   if (!mCurrentContext)
     return NS_OK;
 
   nsIntSize sz = GetWidthHeight();
 
   nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
 
-  nsresult rv = currentContext->SetIsOpaque(GetOpaqueAttr());
-  if (NS_FAILED(rv)) {
-    mCurrentContext = nullptr;
-    return rv;
-  }
+  currentContext->SetIsOpaque(GetOpaqueAttr());
 
-  rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
+  nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
                                          aRvForDictionaryInit);
   if (NS_FAILED(rv)) {
     mCurrentContext = nullptr;
     return rv;
   }
 
   rv = currentContext->SetDimensions(sz.width, sz.height);
   if (NS_FAILED(rv)) {
--- a/dom/canvas/ImageBitmapRenderingContext.cpp
+++ b/dom/canvas/ImageBitmapRenderingContext.cpp
@@ -195,20 +195,19 @@ ImageBitmapRenderingContext::GetSurfaceS
   RefPtr<SourceSurface> surface = mImage->GetAsSourceSurface();
   if (surface->GetSize() != IntSize(mWidth, mHeight)) {
     return MatchWithIntrinsicSize();
   }
 
   return surface.forget();
 }
 
-NS_IMETHODIMP
+void
 ImageBitmapRenderingContext::SetIsOpaque(bool aIsOpaque)
 {
-  return NS_OK;
 }
 
 bool
 ImageBitmapRenderingContext::GetIsOpaque()
 {
   return false;
 }
 
--- a/dom/canvas/ImageBitmapRenderingContext.h
+++ b/dom/canvas/ImageBitmapRenderingContext.h
@@ -61,17 +61,17 @@ public:
   virtual mozilla::UniquePtr<uint8_t[]> GetImageBuffer(int32_t* aFormat) override;
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const char16_t* aEncoderOptions,
                             nsIInputStream** aStream) override;
 
   virtual already_AddRefed<mozilla::gfx::SourceSurface>
   GetSurfaceSnapshot(gfxAlphaType* aOutAlphaType) override;
 
-  NS_IMETHOD SetIsOpaque(bool aIsOpaque) override;
+  virtual void SetIsOpaque(bool aIsOpaque) override;
   virtual bool GetIsOpaque() override;
   NS_IMETHOD Reset() override;
   virtual already_AddRefed<Layer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                  Layer* aOldLayer,
                                                  LayerManager* aManager,
                                                  bool aMirror = false) override;
   virtual void MarkContextClean() override;
 
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -377,17 +377,17 @@ public:
     virtual UniquePtr<uint8_t[]> GetImageBuffer(int32_t* out_format) override;
     NS_IMETHOD GetInputStream(const char* mimeType,
                               const char16_t* encoderOptions,
                               nsIInputStream** out_stream) override;
 
     virtual already_AddRefed<mozilla::gfx::SourceSurface>
     GetSurfaceSnapshot(gfxAlphaType* out_alphaType) override;
 
-    NS_IMETHOD SetIsOpaque(bool) override { return NS_OK; };
+    virtual void SetIsOpaque(bool) override {};
     bool GetIsOpaque() override { return false; }
     NS_IMETHOD SetContextOptions(JSContext* cx,
                                  JS::Handle<JS::Value> options,
                                  ErrorResult& aRvForDictionaryInit) override;
 
     NS_IMETHOD SetIsIPC(bool) override {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -121,17 +121,17 @@ public:
   // if one is returned.
   virtual already_AddRefed<mozilla::gfx::SourceSurface>
   GetSurfaceSnapshot(gfxAlphaType* out_alphaType = nullptr) = 0;
 
   // If this context is opaque, the backing store of the canvas should
   // be created as opaque; all compositing operators should assume the
   // dst alpha is always 1.0.  If this is never called, the context
   // defaults to false (not opaque).
-  NS_IMETHOD SetIsOpaque(bool isOpaque) = 0;
+  virtual void SetIsOpaque(bool isOpaque) = 0;
   virtual bool GetIsOpaque() = 0;
 
   // Invalidate this context and release any held resources, in preperation
   // for possibly reinitializing with SetDimensions/InitializeWithSurface.
   NS_IMETHOD Reset() = 0;
 
   // Return the CanvasLayer for this context, creating
   // one for the given layer manager if not available.
--- a/dom/plugins/test/testplugin/nptest.cpp
+++ b/dom/plugins/test/testplugin/nptest.cpp
@@ -522,17 +522,17 @@ static void sendBufferToFrame(NPP instan
         outbuf.replace(i, 1, "");
         i -= 1;
       }
       else {
         int ascii = outbuf[i];
         if (!((ascii >= ',' && ascii <= ';') ||
               (ascii >= 'A' && ascii <= 'Z') ||
               (ascii >= 'a' && ascii <= 'z'))) {
-          char hex[8];
+          char hex[10];
           sprintf(hex, "%%%x", ascii);
           outbuf.replace(i, 1, hex);
           i += 2;
         }
       }
     }
 
     NPError err = NPN_GetURL(instance, outbuf.c_str(),
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -2461,20 +2461,20 @@ nsCSSRendering::DetermineBackgroundColor
       aDrawBackgroundColor = true;
     } else {
       bgColor = NS_RGBA(0,0,0,0);
     }
   }
 
   // We can skip painting the background color if a background image is opaque.
   nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
-  bool xFullRepeat = repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
-                     repeat.mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
-  bool yFullRepeat = repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
-                     repeat.mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
+  bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat ||
+                     repeat.mXRepeat == StyleImageLayerRepeat::Round;
+  bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat ||
+                     repeat.mYRepeat == StyleImageLayerRepeat::Round;
   if (aDrawBackgroundColor &&
       xFullRepeat && yFullRepeat &&
       bg->BottomLayer().mImage.IsOpaque() &&
       bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
     aDrawBackgroundColor = false;
   }
 
   return bgColor;
@@ -2962,17 +2962,18 @@ nsCSSRendering::ComputeRoundedSize(nscoo
 // Apply the CSS image sizing algorithm as it applies to background images.
 // See http://www.w3.org/TR/css3-background/#the-background-size .
 // aIntrinsicSize is the size that the background image 'would like to be'.
 // It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
 static nsSize
 ComputeDrawnSizeForBackground(const CSSSizeOrRatio& aIntrinsicSize,
                               const nsSize& aBgPositioningArea,
                               const nsStyleImageLayers::Size& aLayerSize,
-                              uint8_t aXRepeat, uint8_t aYRepeat)
+                              StyleImageLayerRepeat aXRepeat,
+                              StyleImageLayerRepeat aYRepeat)
 {
   nsSize imageSize;
 
   // Size is dictated by cover or contain rules.
   if (aLayerSize.mWidthType == nsStyleImageLayers::Size::eContain ||
       aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover) {
     nsImageRenderer::FitType fitType =
       aLayerSize.mWidthType == nsStyleImageLayers::Size::eCover
@@ -3000,38 +3001,38 @@ ComputeDrawnSizeForBackground(const CSSS
 
   // See https://www.w3.org/TR/css3-background/#background-size .
   // "If 'background-repeat' is 'round' for one (or both) dimensions, there is a second
   //  step. The UA must scale the image in that dimension (or both dimensions) so that
   //  it fits a whole number of times in the background positioning area."
   // "If 'background-repeat' is 'round' for one dimension only and if 'background-size'
   //  is 'auto' for the other dimension, then there is a third step: that other dimension
   //  is scaled so that the original aspect ratio is restored."
-  bool isRepeatRoundInBothDimensions = aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND &&
-                                       aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND;
+  bool isRepeatRoundInBothDimensions = aXRepeat == StyleImageLayerRepeat::Round  &&
+                                       aYRepeat == StyleImageLayerRepeat::Round;
 
   // Calculate the rounded size only if the background-size computation
   // returned a correct size for the image.
-  if (imageSize.width && aXRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
+  if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) {
     imageSize.width =
       nsCSSRendering::ComputeRoundedSize(imageSize.width,
                                          aBgPositioningArea.width);
     if (!isRepeatRoundInBothDimensions &&
         aLayerSize.mHeightType == nsStyleImageLayers::Size::DimensionType::eAuto) {
       // Restore intrinsic rato
       if (aIntrinsicSize.mRatio.width) {
         float scale = float(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width;
         imageSize.height = NSCoordSaturatingNonnegativeMultiply(imageSize.width, scale);
       }
     }
   }
 
   // Calculate the rounded size only if the background-size computation
   // returned a correct size for the image.
-  if (imageSize.height && aYRepeat == NS_STYLE_IMAGELAYER_REPEAT_ROUND) {
+  if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) {
     imageSize.height =
       nsCSSRendering::ComputeRoundedSize(imageSize.height,
                                          aBgPositioningArea.height);
     if (!isRepeatRoundInBothDimensions &&
         aLayerSize.mWidthType == nsStyleImageLayers::Size::DimensionType::eAuto) {
       // Restore intrinsic rato
       if (aIntrinsicSize.mRatio.height) {
         float scale = float(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height;
@@ -3181,18 +3182,18 @@ nsCSSRendering::PrepareImageLayer(nsPres
   nsRect bgClipRect = aBGClipRect;
 
   if (NS_STYLE_IMAGELAYER_ATTACHMENT_FIXED == aLayer.mAttachment &&
       !transformedFixed &&
       (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) {
     bgClipRect = positionArea + aBorderArea.TopLeft();
   }
 
-  int repeatX = aLayer.mRepeat.mXRepeat;
-  int repeatY = aLayer.mRepeat.mYRepeat;
+  StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat;
+  StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat;
 
   // Scale the image as specified for background-size and background-repeat.
   // Also as required for proper background positioning when background-position
   // is defined with percentages.
   CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
   nsSize bgPositionSize = positionArea.Size();
   nsSize imageSize = ComputeDrawnSizeForBackground(intrinsicSize,
                                                    bgPositionSize,
@@ -3213,58 +3214,58 @@ nsCSSRendering::PrepareImageLayer(nsPres
   nsPoint imageTopLeft;
 
   // Compute the position of the background now that the background's size is
   // determined.
   nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition,
                                             bgPositionSize, imageSize,
                                             &imageTopLeft, &state.mAnchor);
   state.mRepeatSize = imageSize;
-  if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+  if (repeatX == StyleImageLayerRepeat::Space) {
     bool isRepeat;
     state.mRepeatSize.width = ComputeSpacedRepeatSize(imageSize.width,
                                                       bgPositionSize.width,
                                                       isRepeat);
     if (isRepeat) {
       imageTopLeft.x = 0;
       state.mAnchor.x = 0;
     } else {
-      repeatX = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+      repeatX = StyleImageLayerRepeat::NoRepeat;
     }
   }
 
-  if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+  if (repeatY == StyleImageLayerRepeat::Space) {
     bool isRepeat;
     state.mRepeatSize.height = ComputeSpacedRepeatSize(imageSize.height,
                                                        bgPositionSize.height,
                                                        isRepeat);
     if (isRepeat) {
       imageTopLeft.y = 0;
       state.mAnchor.y = 0;
     } else {
-      repeatY = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
+      repeatY = StyleImageLayerRepeat::NoRepeat;
     }
   }
 
   imageTopLeft += positionArea.TopLeft();
   state.mAnchor += positionArea.TopLeft();
   state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
   state.mFillArea = state.mDestArea;
 
   ExtendMode repeatMode = ExtendMode::CLAMP;
-  if (repeatX == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
-      repeatX == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
-      repeatX == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+  if (repeatX == StyleImageLayerRepeat::Repeat ||
+      repeatX == StyleImageLayerRepeat::Round ||
+      repeatX == StyleImageLayerRepeat::Space) {
     state.mFillArea.x = bgClipRect.x;
     state.mFillArea.width = bgClipRect.width;
     repeatMode = ExtendMode::REPEAT_X;
   }
-  if (repeatY == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
-      repeatY == NS_STYLE_IMAGELAYER_REPEAT_ROUND ||
-      repeatY == NS_STYLE_IMAGELAYER_REPEAT_SPACE) {
+  if (repeatY == StyleImageLayerRepeat::Repeat ||
+      repeatY == StyleImageLayerRepeat::Round ||
+      repeatY == StyleImageLayerRepeat::Space) {
     state.mFillArea.y = bgClipRect.y;
     state.mFillArea.height = bgClipRect.height;
 
     /***
      * We're repeating on the X axis already,
      * so if we have to repeat in the Y axis,
      * we really need to repeat in both directions.
      */
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -3598,18 +3598,18 @@ nsDisplayBackgroundImage::GetOpaqueRegio
   // which expects frames to be sent to it in content order, not reverse
   // content order which we'll produce here.
   // Of course, if there's only one frame in the flow, it doesn't matter.
   if (mFrame->StyleBorder()->mBoxDecorationBreak ==
         StyleBoxDecorationBreak::Clone ||
       (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) {
     const nsStyleImageLayers::Layer& layer = mBackgroundStyle->mImage.mLayers[mLayer];
     if (layer.mImage.IsOpaque() && layer.mBlendMode == NS_STYLE_BLEND_NORMAL &&
-        layer.mRepeat.mXRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE &&
-        layer.mRepeat.mYRepeat != NS_STYLE_IMAGELAYER_REPEAT_SPACE &&
+        layer.mRepeat.mXRepeat != StyleImageLayerRepeat::Space &&
+        layer.mRepeat.mYRepeat != StyleImageLayerRepeat::Space &&
         layer.mClip != StyleGeometryBox::Text) {
       result = GetInsideClipRegion(this, layer.mClip, mBounds, mBackgroundRect);
     }
   }
 
   return result;
 }
 
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -12308,17 +12308,17 @@ CSSParserImpl::ParseImageLayersItem(
 {
   // Fill in the values that the shorthand will set if we don't find
   // other values.
   aState.mImage->mValue.SetNoneValue();
   aState.mAttachment->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL,
                                          eCSSUnit_Enumerated);
   aState.mClip->mValue.SetEnumValue(StyleGeometryBox::BorderBox);
 
-  aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
+  aState.mRepeat->mXValue.SetIntValue(uint8_t(StyleImageLayerRepeat::Repeat),
                                       eCSSUnit_Enumerated);
   aState.mRepeat->mYValue.Reset();
 
   RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2);
   RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2);
   aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array);
   aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array);
 
@@ -12596,18 +12596,18 @@ bool
 CSSParserImpl::ParseImageLayerRepeatValues(nsCSSValuePair& aValue)
 {
   nsCSSValue& xValue = aValue.mXValue;
   nsCSSValue& yValue = aValue.mYValue;
 
   if (ParseEnum(xValue, nsCSSProps::kImageLayerRepeatKTable)) {
     int32_t value = xValue.GetIntValue();
     // For single values set yValue as eCSSUnit_Null.
-    if (value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X ||
-        value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y ||
+    if (value == uint8_t(StyleImageLayerRepeat::RepeatX) ||
+        value == uint8_t(StyleImageLayerRepeat::RepeatY) ||
         !ParseEnum(yValue, nsCSSProps::kImageLayerRepeatPartKTable)) {
       // the caller will fail cases like "repeat-x no-repeat"
       // by expecting a list separator or an end property.
       yValue.Reset();
     }
     return true;
   }
 
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -931,30 +931,30 @@ const KTableEntry nsCSSProps::kImageLaye
   { eCSSKeyword_top, NS_STYLE_IMAGELAYER_POSITION_TOP },
   { eCSSKeyword_bottom, NS_STYLE_IMAGELAYER_POSITION_BOTTOM },
   { eCSSKeyword_left, NS_STYLE_IMAGELAYER_POSITION_LEFT },
   { eCSSKeyword_right, NS_STYLE_IMAGELAYER_POSITION_RIGHT },
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kImageLayerRepeatKTable[] = {
-  { eCSSKeyword_no_repeat,  NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT },
-  { eCSSKeyword_repeat,     NS_STYLE_IMAGELAYER_REPEAT_REPEAT },
-  { eCSSKeyword_repeat_x,   NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X },
-  { eCSSKeyword_repeat_y,   NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y },
-  { eCSSKeyword_round,      NS_STYLE_IMAGELAYER_REPEAT_ROUND},
-  { eCSSKeyword_space,      NS_STYLE_IMAGELAYER_REPEAT_SPACE},
+  { eCSSKeyword_no_repeat,  StyleImageLayerRepeat::NoRepeat },
+  { eCSSKeyword_repeat,     StyleImageLayerRepeat::Repeat },
+  { eCSSKeyword_repeat_x,   StyleImageLayerRepeat::RepeatX },
+  { eCSSKeyword_repeat_y,   StyleImageLayerRepeat::RepeatY },
+  { eCSSKeyword_round,      StyleImageLayerRepeat::Round},
+  { eCSSKeyword_space,      StyleImageLayerRepeat::Space},
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kImageLayerRepeatPartKTable[] = {
-  { eCSSKeyword_no_repeat,  NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT },
-  { eCSSKeyword_repeat,     NS_STYLE_IMAGELAYER_REPEAT_REPEAT },
-  { eCSSKeyword_round,      NS_STYLE_IMAGELAYER_REPEAT_ROUND},
-  { eCSSKeyword_space,      NS_STYLE_IMAGELAYER_REPEAT_SPACE},
+  { eCSSKeyword_no_repeat,  StyleImageLayerRepeat::NoRepeat },
+  { eCSSKeyword_repeat,     StyleImageLayerRepeat::Repeat },
+  { eCSSKeyword_round,      StyleImageLayerRepeat::Round},
+  { eCSSKeyword_space,      StyleImageLayerRepeat::Space},
   { eCSSKeyword_UNKNOWN, -1 }
 };
 
 const KTableEntry nsCSSProps::kImageLayerSizeKTable[] = {
   { eCSSKeyword_contain, NS_STYLE_IMAGELAYER_SIZE_CONTAIN },
   { eCSSKeyword_cover,   NS_STYLE_IMAGELAYER_SIZE_COVER },
   { eCSSKeyword_UNKNOWN, -1 }
 };
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -2471,29 +2471,29 @@ already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetImageLayerRepeat(const nsStyleImageLayers& aLayers)
 {
   RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(true);
 
   for (uint32_t i = 0, i_end = aLayers.mRepeatCount; i < i_end; ++i) {
     RefPtr<nsDOMCSSValueList> itemList = GetROCSSValueList(false);
     RefPtr<nsROCSSPrimitiveValue> valX = new nsROCSSPrimitiveValue;
 
-    const uint8_t& xRepeat = aLayers.mLayers[i].mRepeat.mXRepeat;
-    const uint8_t& yRepeat = aLayers.mLayers[i].mRepeat.mYRepeat;
+    const StyleImageLayerRepeat xRepeat = aLayers.mLayers[i].mRepeat.mXRepeat;
+    const StyleImageLayerRepeat yRepeat = aLayers.mLayers[i].mRepeat.mYRepeat;
 
     bool hasContraction = true;
     unsigned contraction;
     if (xRepeat == yRepeat) {
-      contraction = xRepeat;
-    } else if (xRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
-               yRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT) {
-      contraction = NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X;
-    } else if (xRepeat == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT &&
-               yRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT) {
-      contraction = NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y;
+      contraction = uint8_t(xRepeat);
+    } else if (xRepeat == StyleImageLayerRepeat::Repeat &&
+               yRepeat == StyleImageLayerRepeat::NoRepeat) {
+      contraction = uint8_t(StyleImageLayerRepeat::RepeatX);
+    } else if (xRepeat == StyleImageLayerRepeat::NoRepeat &&
+               yRepeat == StyleImageLayerRepeat::Repeat) {
+      contraction = uint8_t(StyleImageLayerRepeat::RepeatY);
     } else {
       hasContraction = false;
     }
 
     RefPtr<nsROCSSPrimitiveValue> valY;
     if (hasContraction) {
       valX->SetIdent(nsCSSProps::ValueToKeywordEnum(contraction,
                                          nsCSSProps::kImageLayerRepeatKTable));
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -6737,52 +6737,54 @@ struct BackgroundItemComputer<nsCSSValue
                            RuleNodeCacheConditions& aConditions)
   {
     NS_ASSERTION(aSpecifiedValue->mXValue.GetUnit() == eCSSUnit_Enumerated &&
                  (aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Enumerated ||
                   aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null),
                  "Invalid unit");
 
     bool hasContraction = true;
-    uint8_t value = aSpecifiedValue->mXValue.GetIntValue();
+    StyleImageLayerRepeat value =
+      static_cast<StyleImageLayerRepeat>(aSpecifiedValue->mXValue.GetIntValue());
     switch (value) {
-    case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X:
-      aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
-      aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
-      break;
-    case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y:
-      aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
-      aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+      case StyleImageLayerRepeat::RepeatX:
+      aComputedValue.mXRepeat = StyleImageLayerRepeat::Repeat;
+      aComputedValue.mYRepeat = StyleImageLayerRepeat::NoRepeat;
+      break;
+      case StyleImageLayerRepeat::RepeatY:
+      aComputedValue.mXRepeat = StyleImageLayerRepeat::NoRepeat;
+      aComputedValue.mYRepeat = StyleImageLayerRepeat::Repeat;
       break;
     default:
-      NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
-                   value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
-                   value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
-                   value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
+      NS_ASSERTION(value == StyleImageLayerRepeat::NoRepeat ||
+                   value == StyleImageLayerRepeat::Repeat ||
+                   value == StyleImageLayerRepeat::Space ||
+                   value == StyleImageLayerRepeat::Round, "Unexpected value");
       aComputedValue.mXRepeat = value;
       hasContraction = false;
       break;
     }
 
     if (hasContraction) {
       NS_ASSERTION(aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null,
                    "Invalid unit.");
       return;
     }
 
     switch (aSpecifiedValue->mYValue.GetUnit()) {
     case eCSSUnit_Null:
       aComputedValue.mYRepeat = aComputedValue.mXRepeat;
       break;
     case eCSSUnit_Enumerated:
-      value = aSpecifiedValue->mYValue.GetIntValue();
-      NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
-                   value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
-                   value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
-                   value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
+      value =
+        static_cast<StyleImageLayerRepeat>(aSpecifiedValue->mYValue.GetIntValue());
+      NS_ASSERTION(value == StyleImageLayerRepeat::NoRepeat ||
+                   value == StyleImageLayerRepeat::Repeat ||
+                   value == StyleImageLayerRepeat::Space ||
+                   value == StyleImageLayerRepeat::Round, "Unexpected value");
       aComputedValue.mYRepeat = value;
       break;
     default:
       NS_NOTREACHED("Unexpected CSS value");
       break;
     }
   }
 };
--- a/layout/style/nsStyleConsts.h
+++ b/layout/style/nsStyleConsts.h
@@ -344,22 +344,25 @@ enum class FillMode : uint8_t;
 // The parser code depends on |ing these values together.
 #define NS_STYLE_IMAGELAYER_POSITION_CENTER          (1<<0)
 #define NS_STYLE_IMAGELAYER_POSITION_TOP             (1<<1)
 #define NS_STYLE_IMAGELAYER_POSITION_BOTTOM          (1<<2)
 #define NS_STYLE_IMAGELAYER_POSITION_LEFT            (1<<3)
 #define NS_STYLE_IMAGELAYER_POSITION_RIGHT           (1<<4)
 
 // See nsStyleImageLayers
-#define NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT         0x00
-#define NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X          0x01
-#define NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y          0x02
-#define NS_STYLE_IMAGELAYER_REPEAT_REPEAT            0x03
-#define NS_STYLE_IMAGELAYER_REPEAT_SPACE             0x04
-#define NS_STYLE_IMAGELAYER_REPEAT_ROUND             0x05
+enum class StyleImageLayerRepeat : uint8_t {
+  NoRepeat = 0x00,
+  RepeatX,
+  RepeatY,
+  Repeat,
+  Space,
+  Round
+};
+
 
 // See nsStyleImageLayers
 #define NS_STYLE_IMAGELAYER_SIZE_CONTAIN             0
 #define NS_STYLE_IMAGELAYER_SIZE_COVER               1
 
 // Mask mode
 #define NS_STYLE_MASK_MODE_ALPHA                0
 #define NS_STYLE_MASK_MODE_LUMINANCE            1
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -690,35 +690,35 @@ struct nsStyleImageLayers {
     bool operator!=(const Size& aOther) const {
       return !(*this == aOther);
     }
   };
 
   struct Repeat;
   friend struct Repeat;
   struct Repeat {
-    uint8_t mXRepeat, mYRepeat;
+    mozilla::StyleImageLayerRepeat mXRepeat, mYRepeat;
 
     // Initialize nothing
     Repeat() {}
 
     bool IsInitialValue() const {
-      return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT &&
-             mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+      return mXRepeat == mozilla::StyleImageLayerRepeat::Repeat &&
+             mYRepeat == mozilla::StyleImageLayerRepeat::Repeat;
     }
 
     bool DependsOnPositioningAreaSize() const {
-      return mXRepeat == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
-             mYRepeat == NS_STYLE_IMAGELAYER_REPEAT_SPACE;
+      return mXRepeat == mozilla::StyleImageLayerRepeat::Space ||
+             mYRepeat == mozilla::StyleImageLayerRepeat::Space;
     }
 
     // Initialize to initial values
     void SetInitialValues() {
-      mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
-      mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
+      mXRepeat = mozilla::StyleImageLayerRepeat::Repeat;
+      mYRepeat = mozilla::StyleImageLayerRepeat::Repeat;
     }
 
     bool operator==(const Repeat& aOther) const {
       return mXRepeat == aOther.mXRepeat &&
              mYRepeat == aOther.mYRepeat;
     }
     bool operator!=(const Repeat& aOther) const {
       return !(*this == aOther);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -304,18 +304,16 @@ PeerConnectionImpl::PeerConnectionImpl(c
   , mCertificate(nullptr)
   , mPrivacyRequested(false)
   , mSTSThread(nullptr)
   , mAllowIceLoopback(false)
   , mAllowIceLinkLocal(false)
   , mForceIceTcp(false)
   , mMedia(nullptr)
   , mUuidGen(MakeUnique<PCUuidGenerator>())
-  , mNumAudioStreams(0)
-  , mNumVideoStreams(0)
   , mHaveConfiguredCodecs(false)
   , mHaveDataStream(false)
   , mAddCandidateErrorCount(0)
   , mTrickle(true) // TODO(ekr@rtfm.com): Use pref
   , mNegotiationNeeded(false)
   , mPrivateWindow(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -2335,31 +2333,29 @@ PeerConnectionImpl::AddTrack(MediaStream
   aTrack.AddPrincipalChangeObserver(this);
   PrincipalChanged(&aTrack);
 
   if (aTrack.AsAudioStreamTrack()) {
     res = AddTrackToJsepSession(SdpMediaSection::kAudio, streamId, trackId);
     if (NS_FAILED(res)) {
       return res;
     }
-    mNumAudioStreams++;
   }
 
   if (aTrack.AsVideoStreamTrack()) {
     if (!Preferences::GetBool("media.peerconnection.video.enabled", true)) {
       // Before this code was moved, this would silently ignore just like it
       // does now. Is this actually what we want to do?
       return NS_OK;
     }
 
     res = AddTrackToJsepSession(SdpMediaSection::kVideo, streamId, trackId);
     if (NS_FAILED(res)) {
       return res;
     }
-    mNumVideoStreams++;
   }
   OnNegotiationNeeded();
   return NS_OK;
 }
 
 RefPtr<MediaPipeline>
 PeerConnectionImpl::GetMediaPipelineForTrack(MediaStreamTrack& aRecvTrack)
 {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -783,22 +783,16 @@ private:
   std::string mPreviousIceUfrag; // used during rollback of ice restart
   std::string mPreviousIcePwd; // used during rollback of ice restart
 
   // Start time of ICE, used for telemetry
   mozilla::TimeStamp mIceStartTime;
   // Start time of call used for Telemetry
   mozilla::TimeStamp mStartTime;
 
-  // Temporary: used to prevent multiple audio streams or multiple video streams
-  // in a single PC. This is tied up in the IETF discussion around proper
-  // representation of multiple streams in SDP, and strongly related to
-  // Bug 840728.
-  int mNumAudioStreams;
-  int mNumVideoStreams;
   bool mHaveConfiguredCodecs;
 
   bool mHaveDataStream;
 
   unsigned int mAddCandidateErrorCount;
 
   bool mTrickle;
 
--- a/netwerk/base/nsProtocolProxyService.cpp
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -508,16 +508,18 @@ nsProtocolProxyService::~nsProtocolProxy
     NS_ASSERTION(mHostFiltersArray.Length() == 0 && mFilters == nullptr &&
                  mPACMan == nullptr, "what happened to xpcom-shutdown?");
 }
 
 // nsProtocolProxyService methods
 nsresult
 nsProtocolProxyService::Init()
 {
+    NS_NewNamedThread("SysProxySetting", getter_AddRefs(mProxySettingThread));
+
     // failure to access prefs is non-fatal
     nsCOMPtr<nsIPrefBranch> prefBranch =
             do_GetService(NS_PREFSERVICE_CONTRACTID);
     if (prefBranch) {
         // monitor proxy prefs
         prefBranch->AddObserver(PROXY_PREF_BRANCH, this, false);
 
         // read all prefs
@@ -527,18 +529,16 @@ nsProtocolProxyService::Init()
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     if (obs) {
         // register for shutdown notification so we can clean ourselves up
         // properly.
         obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
         obs->AddObserver(this, NS_NETWORK_LINK_TOPIC, false);
     }
 
-    NS_NewNamedThread("SysProxySetting", getter_AddRefs(mProxySettingThread));
-
     return NS_OK;
 }
 
 // ReloadNetworkPAC() checks if there's a non-networked PAC in use then avoids
 // to call ReloadPAC()
 nsresult
 nsProtocolProxyService::ReloadNetworkPAC()
 {
@@ -585,20 +585,16 @@ nsProtocolProxyService::ReloadNetworkPAC
 }
 
 nsresult
 nsProtocolProxyService::AsyncConfigureFromPAC(bool aForceReload,
                                               bool aResetPACThread)
 {
     MOZ_ASSERT(NS_IsMainThread());
 
-    if (NS_WARN_IF(!mProxySettingThread)) {
-        return NS_ERROR_NOT_INITIALIZED;
-    }
-
     bool mainThreadOnly;
     nsresult rv = mSystemProxySettings->GetMainThreadOnly(&mainThreadOnly);
     if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
     }
 
     nsCOMPtr<nsIRunnable> req =
         new AsyncGetPACURIRequest(this,
@@ -606,16 +602,20 @@ nsProtocolProxyService::AsyncConfigureFr
                                   mSystemProxySettings,
                                   mainThreadOnly,
                                   aForceReload,
                                   aResetPACThread);
 
     if (mainThreadOnly) {
         return req->Run();
     }
+
+    if (NS_WARN_IF(!mProxySettingThread)) {
+        return NS_ERROR_NOT_INITIALIZED;
+    }
     return mProxySettingThread->Dispatch(req, nsIEventTarget::DISPATCH_NORMAL);
 }
 
 nsresult
 nsProtocolProxyService::OnAsyncGetPACURI(bool aForceReload,
                                          bool aResetPACThread,
                                          nsresult aResult,
                                          const nsACString& aUri)
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -12,16 +12,19 @@
 #include "SandboxChroot.h"
 #include "SandboxFilter.h"
 #include "SandboxInternal.h"
 #include "SandboxLogging.h"
 #include "SandboxReporterClient.h"
 #include "SandboxUtil.h"
 
 #include <dirent.h>
+#ifdef NIGHTLY_BUILD
+#include "dlfcn.h"
+#endif
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/futex.h>
 #include <pthread.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -502,16 +505,32 @@ SetCurrentProcessSandbox(UniquePtr<sandb
     if (info.Test(SandboxInfo::kVerbose)) {
       SANDBOX_LOG_ERROR("no tsync support; using signal broadcast");
     }
     BroadcastSetThreadSandbox(&fprog);
   }
   MOZ_RELEASE_ASSERT(!gChrootHelper, "forgot to chroot");
 }
 
+#ifdef NIGHTLY_BUILD
+static bool
+IsLibPresent(const char* aName)
+{
+  if (const auto handle = dlopen(aName, RTLD_LAZY | RTLD_NOLOAD)) {
+    dlclose(handle);
+    return true;
+  }
+  return false;
+}
+
+static const Array<const char*, 1> kLibsThatWillCrash {
+  "libesets_pac.so",
+};
+#endif // NIGHTLY_BUILD
+
 void
 SandboxEarlyInit(GeckoProcessType aType)
 {
   const SandboxInfo info = SandboxInfo::Get();
   if (info.Test(SandboxInfo::kUnexpectedThreads)) {
     return;
   }
   MOZ_RELEASE_ASSERT(IsSingleThreaded());
@@ -519,16 +538,22 @@ SandboxEarlyInit(GeckoProcessType aType)
   // Set gSandboxCrashOnError if appropriate.  This doesn't need to
   // happen this early, but for now it's here so that I don't need to
   // add NSPR dependencies for PR_GetEnv.
   //
   // This also means that users with "unexpected threads" setups won't
   // crash even on nightly.
 #ifdef NIGHTLY_BUILD
   gSandboxCrashOnError = true;
+  for (const char* name : kLibsThatWillCrash) {
+    if (IsLibPresent(name)) {
+      gSandboxCrashOnError = false;
+      break;
+    }
+  }
 #endif
   if (const char* envVar = getenv("MOZ_SANDBOX_CRASH_ON_ERROR")) {
     if (envVar[0]) {
       gSandboxCrashOnError = envVar[0] != '0';
     }
   }
 
   // Which kinds of resource isolation (of those that need to be set
--- 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/constellation/constellation.rs
+++ b/servo/components/constellation/constellation.rs
@@ -795,16 +795,22 @@ impl<Message, LTF, STF> Constellation<Me
             .and_then(|pipeline| pipeline.parent_info);
         if let Some((parent_id, _)) = parent_info {
             if let Some(parent) = self.pipelines.get_mut(&parent_id) {
                 parent.add_child(browsing_context_id);
             }
         }
     }
 
+    fn add_pending_change(&mut self, change: SessionHistoryChange)
+    {
+        self.handle_load_start_msg(change.new_pipeline_id);
+        self.pending_changes.push(change);
+    }
+
     /// Handles loading pages, navigation, and granting access to the compositor
     #[allow(unsafe_code)]
     fn handle_request(&mut self) {
         enum Request {
             Script(FromScriptMsg),
             Compositor(FromCompositorMsg),
             Layout(FromLayoutMsg),
             NetworkListener((PipelineId, FetchResponseMsg)),
@@ -1413,18 +1419,17 @@ impl<Message, LTF, STF> Constellation<Me
 
         warn!("creating replacement pipeline for about:failure");
 
         let new_pipeline_id = PipelineId::new();
         let load_data = LoadData::new(failure_url, None, None, None);
         let sandbox = IFrameSandboxState::IFrameSandboxed;
         self.new_pipeline(new_pipeline_id, browsing_context_id, top_level_browsing_context_id, parent_info,
                           window_size, load_data.clone(), sandbox, false);
-        self.handle_load_start_msg(new_pipeline_id);
-        self.pending_changes.push(SessionHistoryChange {
+        self.add_pending_change(SessionHistoryChange {
             top_level_browsing_context_id: top_level_browsing_context_id,
             browsing_context_id: browsing_context_id,
             new_pipeline_id: new_pipeline_id,
             load_data: load_data,
             replace_instant: None,
         });
     }
 
@@ -1471,18 +1476,17 @@ impl<Message, LTF, STF> Constellation<Me
         self.new_pipeline(pipeline_id,
                           browsing_context_id,
                           top_level_browsing_context_id,
                           None,
                           Some(window_size),
                           load_data.clone(),
                           sandbox,
                           false);
-        self.handle_load_start_msg(pipeline_id);
-        self.pending_changes.push(SessionHistoryChange {
+        self.add_pending_change(SessionHistoryChange {
             top_level_browsing_context_id: top_level_browsing_context_id,
             browsing_context_id: browsing_context_id,
             new_pipeline_id: pipeline_id,
             load_data: load_data,
             replace_instant: None,
         });
     }
 
@@ -1568,18 +1572,17 @@ impl<Message, LTF, STF> Constellation<Me
         let replace_instant = if load_info.info.replace {
             self.browsing_contexts.get(&load_info.info.browsing_context_id)
                 .map(|browsing_context| browsing_context.instant)
         } else {
             None
         };
 
         // Create the new pipeline, attached to the parent and push to pending changes
-        self.handle_load_start_msg(load_info.info.new_pipeline_id);
-        self.pending_changes.push(SessionHistoryChange {
+        self.add_pending_change(SessionHistoryChange {
             top_level_browsing_context_id: load_info.info.top_level_browsing_context_id,
             browsing_context_id: load_info.info.browsing_context_id,
             new_pipeline_id: load_info.info.new_pipeline_id,
             load_data: load_data.clone(),
             replace_instant: replace_instant,
         });
 
         self.new_pipeline(load_info.info.new_pipeline_id,
@@ -1634,18 +1637,17 @@ impl<Message, LTF, STF> Constellation<Me
             self.browsing_contexts.get(&browsing_context_id).map(|browsing_context| browsing_context.instant)
         } else {
             None
         };
 
         assert!(!self.pipelines.contains_key(&new_pipeline_id));
         self.pipelines.insert(new_pipeline_id, pipeline);
 
-        self.handle_load_start_msg(new_pipeline_id);
-        self.pending_changes.push(SessionHistoryChange {
+        self.add_pending_change(SessionHistoryChange {
             top_level_browsing_context_id: top_level_browsing_context_id,
             browsing_context_id: browsing_context_id,
             new_pipeline_id: new_pipeline_id,
             load_data: load_data,
             replace_instant: replace_instant,
         });
     }
 
@@ -1786,18 +1788,17 @@ impl<Message, LTF, STF> Constellation<Me
                     None => {
                         warn!("Browsing context {} loaded after closure.", browsing_context_id);
                         return None;
                     }
                 };
                 let new_pipeline_id = PipelineId::new();
                 let sandbox = IFrameSandboxState::IFrameUnsandboxed;
                 let replace_instant = if replace { Some(timestamp) } else { None };
-                self.handle_load_start_msg(new_pipeline_id);
-                self.pending_changes.push(SessionHistoryChange {
+                self.add_pending_change(SessionHistoryChange {
                     top_level_browsing_context_id: top_level_id,
                     browsing_context_id: browsing_context_id,
                     new_pipeline_id: new_pipeline_id,
                     load_data: load_data.clone(),
                     replace_instant: replace_instant,
                 });
                 self.new_pipeline(new_pipeline_id,
                                   browsing_context_id,
@@ -2175,18 +2176,17 @@ impl<Message, LTF, STF> Constellation<Me
                                  None,
                                  browsing_context.size,
                                  false),
                     },
                     None => return warn!("no browsing context to traverse"),
                 };
                 self.new_pipeline(new_pipeline_id, browsing_context_id, top_level_id, parent_info,
                                   window_size, load_data.clone(), sandbox, is_private);
-                self.handle_load_start_msg(new_pipeline_id);
-                self.pending_changes.push(SessionHistoryChange {
+                self.add_pending_change(SessionHistoryChange {
                     top_level_browsing_context_id: top_level_id,
                     browsing_context_id: browsing_context_id,
                     new_pipeline_id: new_pipeline_id,
                     load_data: load_data,
                     replace_instant: Some(entry.instant),
                 });
                 return;
             }
--- 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/generated/structs_debug.rs
+++ b/servo/components/style/gecko/generated/structs_debug.rs
@@ -253,23 +253,16 @@ pub mod root {
     pub const NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING:
               ::std::os::raw::c_uint =
         127;
     pub const NS_STYLE_IMAGELAYER_POSITION_CENTER: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_IMAGELAYER_POSITION_TOP: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_IMAGELAYER_POSITION_BOTTOM: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_IMAGELAYER_POSITION_LEFT: ::std::os::raw::c_uint = 8;
     pub const NS_STYLE_IMAGELAYER_POSITION_RIGHT: ::std::os::raw::c_uint = 16;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT: ::std::os::raw::c_uint =
-        0;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X: ::std::os::raw::c_uint = 1;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y: ::std::os::raw::c_uint = 2;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_REPEAT: ::std::os::raw::c_uint = 3;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_SPACE: ::std::os::raw::c_uint = 4;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_ROUND: ::std::os::raw::c_uint = 5;
     pub const NS_STYLE_IMAGELAYER_SIZE_CONTAIN: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_IMAGELAYER_SIZE_COVER: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_MASK_MODE_ALPHA: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_MASK_MODE_LUMINANCE: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_MASK_MODE_MATCH_SOURCE: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_BG_INLINE_POLICY_EACH_BOX: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_BG_INLINE_POLICY_CONTINUOUS: ::std::os::raw::c_uint =
         1;
@@ -6415,16 +6408,26 @@ pub mod root {
         pub enum StyleOrient {
             Inline = 0,
             Block = 1,
             Horizontal = 2,
             Vertical = 3,
         }
         #[repr(u8)]
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+        pub enum StyleImageLayerRepeat {
+            NoRepeat = 0,
+            RepeatX = 1,
+            RepeatY = 2,
+            Repeat = 3,
+            Space = 4,
+            Round = 5,
+        }
+        #[repr(u8)]
+        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub enum StyleDisplay {
             None = 0,
             Block = 1,
             FlowRoot = 2,
             Inline = 3,
             InlineBlock = 4,
             ListItem = 5,
             Table = 6,
@@ -29972,18 +29975,18 @@ pub mod root {
                     mHeightType ) ));
     }
     impl Clone for nsStyleImageLayers_Size {
         fn clone(&self) -> Self { *self }
     }
     #[repr(C)]
     #[derive(Debug, Copy)]
     pub struct nsStyleImageLayers_Repeat {
-        pub mXRepeat: u8,
-        pub mYRepeat: u8,
+        pub mXRepeat: root::mozilla::StyleImageLayerRepeat,
+        pub mYRepeat: root::mozilla::StyleImageLayerRepeat,
     }
     #[test]
     fn bindgen_test_layout_nsStyleImageLayers_Repeat() {
         assert_eq!(::std::mem::size_of::<nsStyleImageLayers_Repeat>() , 2usize
                    , concat ! (
                    "Size of: " , stringify ! ( nsStyleImageLayers_Repeat ) ));
         assert_eq! (::std::mem::align_of::<nsStyleImageLayers_Repeat>() ,
                     1usize , concat ! (
--- a/servo/components/style/gecko/generated/structs_release.rs
+++ b/servo/components/style/gecko/generated/structs_release.rs
@@ -253,23 +253,16 @@ pub mod root {
     pub const NS_STYLE_IMAGELAYER_CLIP_MOZ_ALMOST_PADDING:
               ::std::os::raw::c_uint =
         127;
     pub const NS_STYLE_IMAGELAYER_POSITION_CENTER: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_IMAGELAYER_POSITION_TOP: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_IMAGELAYER_POSITION_BOTTOM: ::std::os::raw::c_uint = 4;
     pub const NS_STYLE_IMAGELAYER_POSITION_LEFT: ::std::os::raw::c_uint = 8;
     pub const NS_STYLE_IMAGELAYER_POSITION_RIGHT: ::std::os::raw::c_uint = 16;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT: ::std::os::raw::c_uint =
-        0;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X: ::std::os::raw::c_uint = 1;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y: ::std::os::raw::c_uint = 2;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_REPEAT: ::std::os::raw::c_uint = 3;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_SPACE: ::std::os::raw::c_uint = 4;
-    pub const NS_STYLE_IMAGELAYER_REPEAT_ROUND: ::std::os::raw::c_uint = 5;
     pub const NS_STYLE_IMAGELAYER_SIZE_CONTAIN: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_IMAGELAYER_SIZE_COVER: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_MASK_MODE_ALPHA: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_MASK_MODE_LUMINANCE: ::std::os::raw::c_uint = 1;
     pub const NS_STYLE_MASK_MODE_MATCH_SOURCE: ::std::os::raw::c_uint = 2;
     pub const NS_STYLE_BG_INLINE_POLICY_EACH_BOX: ::std::os::raw::c_uint = 0;
     pub const NS_STYLE_BG_INLINE_POLICY_CONTINUOUS: ::std::os::raw::c_uint =
         1;
@@ -6271,16 +6264,26 @@ pub mod root {
         pub enum StyleOrient {
             Inline = 0,
             Block = 1,
             Horizontal = 2,
             Vertical = 3,
         }
         #[repr(u8)]
         #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+        pub enum StyleImageLayerRepeat {
+            NoRepeat = 0,
+            RepeatX = 1,
+            RepeatY = 2,
+            Repeat = 3,
+            Space = 4,
+            Round = 5,
+        }
+        #[repr(u8)]
+        #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
         pub enum StyleDisplay {
             None = 0,
             Block = 1,
             FlowRoot = 2,
             Inline = 3,
             InlineBlock = 4,
             ListItem = 5,
             Table = 6,
@@ -29427,18 +29430,18 @@ pub mod root {
                     mHeightType ) ));
     }
     impl Clone for nsStyleImageLayers_Size {
         fn clone(&self) -> Self { *self }
     }
     #[repr(C)]
     #[derive(Debug, Copy)]
     pub struct nsStyleImageLayers_Repeat {
-        pub mXRepeat: u8,
-        pub mYRepeat: u8,
+        pub mXRepeat: root::mozilla::StyleImageLayerRepeat,
+        pub mYRepeat: root::mozilla::StyleImageLayerRepeat,
     }
     #[test]
     fn bindgen_test_layout_nsStyleImageLayers_Repeat() {
         assert_eq!(::std::mem::size_of::<nsStyleImageLayers_Repeat>() , 2usize
                    , concat ! (
                    "Size of: " , stringify ! ( nsStyleImageLayers_Repeat ) ));
         assert_eq! (::std::mem::align_of::<nsStyleImageLayers_Repeat>() ,
                     1usize , concat ! (
--- 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/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -2923,35 +2923,32 @@ fn static_assert() {
         else:
             image_layers_field = "mMask"
             struct_name = "SVG"
     %>
 
     <%self:simple_image_array_property name="repeat" shorthand="${shorthand}" field_name="mRepeat">
         use properties::longhands::${shorthand}_repeat::single_value::computed_value::RepeatKeyword;
         use gecko_bindings::structs::nsStyleImageLayers_Repeat;
-        use gecko_bindings::structs::NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
-        use gecko_bindings::structs::NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
-        use gecko_bindings::structs::NS_STYLE_IMAGELAYER_REPEAT_SPACE;
-        use gecko_bindings::structs::NS_STYLE_IMAGELAYER_REPEAT_ROUND;
-
-        fn to_ns(repeat: RepeatKeyword) -> u32 {
+        use gecko_bindings::structs::StyleImageLayerRepeat;
+
+        fn to_ns(repeat: RepeatKeyword) -> StyleImageLayerRepeat {
             match repeat {
-                RepeatKeyword::Repeat => NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
-                RepeatKeyword::Space => NS_STYLE_IMAGELAYER_REPEAT_SPACE,
-                RepeatKeyword::Round => NS_STYLE_IMAGELAYER_REPEAT_ROUND,
-                RepeatKeyword::NoRepeat => NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT,
+                RepeatKeyword::Repeat => StyleImageLayerRepeat::Repeat,
+                RepeatKeyword::Space => StyleImageLayerRepeat::Space,
+                RepeatKeyword::Round => StyleImageLayerRepeat::Round,
+                RepeatKeyword::NoRepeat => StyleImageLayerRepeat::NoRepeat,
             }
         }
 
         let repeat_x = to_ns(servo.0);
         let repeat_y = to_ns(servo.1);
         nsStyleImageLayers_Repeat {
-              mXRepeat: repeat_x as u8,
-              mYRepeat: repeat_y as u8,
+              mXRepeat: repeat_x,
+              mYRepeat: repeat_y,
         }
     </%self:simple_image_array_property>
 
     <% impl_simple_image_array_property("clip", shorthand, image_layers_field, "mClip", struct_name) %>
     <% impl_simple_image_array_property("origin", shorthand, image_layers_field, "mOrigin", struct_name) %>
 
     % for orientation in ["x", "y"]:
     pub fn copy_${shorthand}_position_${orientation}_from(&mut self, other: &Self) {
--- 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&