Bug 1107378 part 2: Make the CSS Parser call out to the unprefixing service, when it detects a vendor-prefixed property name (if pref is enabled). r=dbaron
☠☠ backed out by 68df163b1792 ☠ ☠
authorDaniel Holbert <dholbert@cs.stanford.edu>
Thu, 26 Feb 2015 12:07:06 -0800
changeset 249279 960037d0fc98f36d1ed9e8c47a03b677f4f5c778
parent 249278 23fb39cb0f9761b56d8207abc6aa431aa1addf43
child 249280 5a8d5e8ff5241612e67fdbe3a952806a19153ba1
push id7860
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:46:02 +0000
treeherdermozilla-aurora@8ac636cd51f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs1107378
milestone39.0a1
Bug 1107378 part 2: Make the CSS Parser call out to the unprefixing service, when it detects a vendor-prefixed property name (if pref is enabled). r=dbaron
layout/style/nsCSSParser.cpp
modules/libpref/init/all.js
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -33,16 +33,17 @@
 #include "nsCSSPseudoClasses.h"
 #include "nsCSSPseudoElements.h"
 #include "nsNameSpaceManager.h"
 #include "nsXMLNameSpaceMap.h"
 #include "nsError.h"
 #include "nsIMediaList.h"
 #include "nsStyleUtil.h"
 #include "nsIPrincipal.h"
+#include "nsICSSUnprefixingService.h"
 #include "prprf.h"
 #include "nsContentUtils.h"
 #include "nsAutoPtr.h"
 #include "CSSCalc.h"
 #include "nsMediaFeatures.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsRuleData.h"
@@ -51,16 +52,17 @@
 #include "gfxFontFamilyList.h"
 
 using namespace mozilla;
 
 typedef nsCSSProps::KTableValue KTableValue;
 
 // pref-backed bool values (hooked up in nsCSSParser::Startup)
 static bool sOpentypeSVGEnabled;
+static bool sUnprefixingServiceEnabled;
 
 const uint32_t
 nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
 #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
                  stylestruct_, stylestructoffset_, animtype_)                 \
   parsevariant_,
 #define CSS_PROP_LIST_INCLUDE_LOGICAL
 #include "nsCSSPropList.h"
@@ -411,16 +413,113 @@ protected:
   };
 
   // the caller must hold on to aString until parsing is done
   void InitScanner(nsCSSScanner& aScanner,
                    css::ErrorReporter& aReporter,
                    nsIURI* aSheetURI, nsIURI* aBaseURI,
                    nsIPrincipal* aSheetPrincipal);
   void ReleaseScanner(void);
+
+  /**
+   * This is a RAII class which behaves like an "AutoRestore<>" for our parser
+   * input state. When instantiated, this class saves the current parser input
+   * state (in a CSSParserInputState object), and it restores the parser to
+   * that state when destructed, unless "DoNotRestore()" has been called.
+  */
+  class MOZ_STACK_CLASS nsAutoCSSParserInputStateRestorer {
+    public:
+      explicit nsAutoCSSParserInputStateRestorer(CSSParserImpl* aParser
+                                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+        : mParser(aParser),
+          mShouldRestore(true)
+      {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        mParser->SaveInputState(mSavedState);
+      }
+
+      void DoNotRestore()
+      {
+        mShouldRestore = false;
+      }
+
+      ~nsAutoCSSParserInputStateRestorer()
+      {
+        if (mShouldRestore) {
+          mParser->RestoreSavedInputState(mSavedState);
+        }
+      }
+
+    private:
+      MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+      CSSParserImpl* mParser;
+      CSSParserInputState mSavedState;
+      bool mShouldRestore;
+  };
+
+  /**
+   * This is a RAII class which creates a temporary nsCSSScanner for the given
+   * string, and reconfigures aParser to use *that* scanner instead of its
+   * existing scanner, until we go out of scope.  (This allows us to rewrite
+   * a portion of a stylesheet using a temporary string, and switch to parsing
+   * that rewritten section, and then resume parsing the original stylesheet.)
+   *
+   * aParser must have a non-null nsCSSScanner (which we'll be temporarily
+   * replacing) and ErrorReporter (which this class will co-opt for the
+   * temporary parser). While we're in scope, we also suppress error reporting,
+   * so it doesn't really matter which reporter we use. We suppress reporting
+   * because this class is only used with CSS that is synthesized & didn't
+   * come directly from an author, and it would be confusing if we reported
+   * syntax errors for CSS that an author didn't provide.
+   *
+   * XXXdholbert we could also change this & report errors, if needed. Might
+   * want to customize the error reporting somehow though.
+   */
+  class MOZ_STACK_CLASS nsAutoScannerChanger {
+    public:
+      nsAutoScannerChanger(CSSParserImpl* aParser,
+                           const nsAString& aStringToScan
+                           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+        : mParser(aParser),
+          mOriginalScanner(aParser->mScanner),
+          mStringScanner(aStringToScan, 0),
+          mParserStateRestorer(aParser),
+          mErrorSuppresser(aParser)
+      {
+        MOZ_ASSERT(mOriginalScanner,
+                   "Shouldn't use nsAutoScannerChanger unless we already "
+                   "have a scanner");
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+        // Set & setup the new scanner:
+        mParser->mScanner = &mStringScanner;
+        mStringScanner.SetErrorReporter(mParser->mReporter);
+
+        // We might've had push-back on our original scanner (and if we did,
+        // that fact is saved via mParserStateRestorer).  But we don't have
+        // push-back in mStringScanner, so clear that flag.
+        mParser->mHavePushBack = false;
+      }
+
+      ~nsAutoScannerChanger()
+      {
+        // Restore original scanner. All other cleanup is done by RAII members.
+        mParser->mScanner = mOriginalScanner;
+      }
+
+    private:
+      MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+      CSSParserImpl* mParser;
+      nsCSSScanner *mOriginalScanner;
+      nsCSSScanner mStringScanner;
+      nsAutoCSSParserInputStateRestorer mParserStateRestorer;
+      nsAutoSuppressErrors mErrorSuppresser;
+  };
+
+
   bool IsSVGMode() const {
     return mScanner->IsSVGMode();
   }
 
   /**
    * Saves the current input state, which includes any currently pushed
    * back token, and the current position of the scanner.
    */
@@ -600,32 +699,42 @@ protected:
   // If aStopChar is non-zero, the selector list is done when we hit
   // aStopChar.  Otherwise, it's done when we hit EOF.
   bool ParseSelectorList(nsCSSSelectorList*& aListHead,
                            char16_t aStopChar);
   bool ParseSelectorGroup(nsCSSSelectorList*& aListHead);
   bool ParseSelector(nsCSSSelectorList* aList, char16_t aPrevCombinator);
 
   enum {
-    eParseDeclaration_InBraces       = 1 << 0,
-    eParseDeclaration_AllowImportant = 1 << 1
+    eParseDeclaration_InBraces           = 1 << 0,
+    eParseDeclaration_AllowImportant     = 1 << 1,
+    // The declaration we're parsing was generated by the CSSUnprefixingService:
+    eParseDeclaration_FromUnprefixingSvc = 1 << 2
   };
   enum nsCSSContextType {
     eCSSContext_General,
     eCSSContext_Page
   };
 
   css::Declaration* ParseDeclarationBlock(uint32_t aFlags,
                                           nsCSSContextType aContext = eCSSContext_General);
   bool ParseDeclaration(css::Declaration* aDeclaration,
                         uint32_t aFlags,
                         bool aMustCallValueAppended,
                         bool* aChanged,
                         nsCSSContextType aContext = eCSSContext_General);
 
+  bool ShouldUseUnprefixingService();
+  bool ParsePropertyWithUnprefixingService(const nsAString& aPropertyName,
+                                           css::Declaration* aDeclaration,
+                                           uint32_t aFlags,
+                                           bool aMustCallValueAppended,
+                                           bool* aChanged,
+                                           nsCSSContextType aContext);
+
   bool ParseProperty(nsCSSProperty aPropID);
   bool ParsePropertyByFunction(nsCSSProperty aPropID);
   bool ParseSingleValueProperty(nsCSSValue& aValue,
                                   nsCSSProperty aPropID);
 
   enum PriorityParsingStatus {
     ePriority_None,
     ePriority_Important,
@@ -6436,16 +6545,83 @@ CSSParserImpl::ParseTreePseudoElement(ns
     }
   }
   *aPseudoElementArgs = fakeSelector.mClassList;
   fakeSelector.mClassList = nullptr;
   return true;
 }
 #endif
 
+bool
+CSSParserImpl::ShouldUseUnprefixingService()
+{
+  if (!sUnprefixingServiceEnabled) {
+    return false;
+  }
+
+  // XXXdholbert Bug 1132743: Check if stylesheet URI is on fixlist here.
+  return true;
+}
+
+bool
+CSSParserImpl::ParsePropertyWithUnprefixingService(
+  const nsAString& aPropertyName,
+  css::Declaration* aDeclaration,
+  uint32_t aFlags,
+  bool aMustCallValueAppended,
+  bool* aChanged,
+  nsCSSContextType aContext)
+{
+  MOZ_ASSERT(ShouldUseUnprefixingService(),
+             "Caller should've checked ShouldUseUnprefixingService()");
+
+  nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
+    do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(unprefixingSvc, false);
+
+  // Save the state so we can jump back to this spot if our unprefixing fails
+  // (so we can behave as if we didn't even try to unprefix).
+  nsAutoCSSParserInputStateRestorer parserStateBeforeTryingToUnprefix(this);
+
+  // Caller has already parsed the first half of the declaration --
+  // aPropertyName and the ":".  Now, we record the rest of the CSS declaration
+  // (the part after ':') into rightHalfOfDecl.  (This is the property value,
+  // plus anything else up to the end of the declaration -- maybe "!important",
+  // maybe trailing junk characters, maybe a semicolon, maybe a trailing "}".)
+  bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
+  nsAutoString rightHalfOfDecl;
+  mScanner->StartRecording();
+  SkipDeclaration(checkForBraces);
+  mScanner->StopRecording(rightHalfOfDecl);
+
+  // Try to unprefix:
+  bool success;
+  nsAutoString unprefixedDecl;
+  nsresult rv =
+    unprefixingSvc->GenerateUnprefixedDeclaration(aPropertyName,
+                                                  rightHalfOfDecl,
+                                                  unprefixedDecl, &success);
+  if (NS_FAILED(rv) || !success) {
+    return false;
+  }
+
+  // Attempt to parse the unprefixed declaration:
+  nsAutoScannerChanger scannerChanger(this, unprefixedDecl);
+  success = ParseDeclaration(aDeclaration,
+                             aFlags | eParseDeclaration_FromUnprefixingSvc,
+                             aMustCallValueAppended, aChanged, aContext);
+  if (success) {
+    // We succeeded, so we'll leave the parser pointing at the end of
+    // the declaration; don't restore it to the pre-recording position.
+    parserStateBeforeTryingToUnprefix.DoNotRestore();
+  }
+
+  return success;
+}
+
 //----------------------------------------------------------------------
 
 bool
 CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration,
                                 uint32_t aFlags,
                                 bool aMustCallValueAppended,
                                 bool* aChanged,
                                 nsCSSContextType aContext)
@@ -6525,17 +6701,29 @@ CSSParserImpl::ParseDeclaration(css::Dec
   } else {
     // Map property name to its ID.
     propID = LookupEnabledProperty(propertyName);
     if (eCSSProperty_UNKNOWN == propID ||
         eCSSPropertyExtra_variable == propID ||
         (aContext == eCSSContext_Page &&
          !nsCSSProps::PropHasFlags(propID,
                                    CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
-      if (!NonMozillaVendorIdentifier(propertyName)) {
+      if (NonMozillaVendorIdentifier(propertyName)) {
+        if (!mInSupportsCondition &&
+            aContext == eCSSContext_General &&
+            !(aFlags & eParseDeclaration_FromUnprefixingSvc) && // no recursion
+            ShouldUseUnprefixingService()) {
+          if (ParsePropertyWithUnprefixingService(propertyName,
+                                                  aDeclaration, aFlags,
+                                                  aMustCallValueAppended,
+                                                  aChanged, aContext)) {
+            return true;
+          }
+        }
+      } else {
         REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
         REPORT_UNEXPECTED(PEDeclDropped);
         OUTPUT_ERROR();
       }
       return false;
     }
     // Then parse the property.
     if (!ParseProperty(propID)) {
@@ -15055,16 +15243,18 @@ CSSParserImpl::IsValueValidForProperty(c
 
 static CSSParserImpl* gFreeList = nullptr;
 
 /* static */ void
 nsCSSParser::Startup()
 {
   Preferences::AddBoolVarCache(&sOpentypeSVGEnabled,
                                "gfx.font_rendering.opentype_svg.enabled");
+  Preferences::AddBoolVarCache(&sUnprefixingServiceEnabled,
+                               "layout.css.unprefixing-service.enabled");
 }
 
 nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,
                          CSSStyleSheet* aSheet)
 {
   CSSParserImpl *impl = gFreeList;
   if (impl) {
     gFreeList = impl->mNextFree;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2149,16 +2149,20 @@ pref("layout.css.image-orientation.enabl
 // Are sets of prefixed properties supported?
 pref("layout.css.prefixes.border-image", true);
 pref("layout.css.prefixes.transforms", true);
 pref("layout.css.prefixes.transitions", true);
 pref("layout.css.prefixes.animations", true);
 pref("layout.css.prefixes.box-sizing", true);
 pref("layout.css.prefixes.font-features", true);
 
+// Is the CSS Unprefixing Service enabled? (This service emulates support
+// for certain vendor-prefixed properties & values, for sites on a "fixlist".)
+pref("layout.css.unprefixing-service.enabled", false);
+
 // Is support for the :scope selector enabled?
 pref("layout.css.scope-pseudo.enabled", true);
 
 // Is support for background-blend-mode enabled?
 pref("layout.css.background-blend-mode.enabled", true);
 
 // Is support for CSS vertical text enabled?
 pref("layout.css.vertical-text.enabled", false);