Bug 1265798 - Implement CSS database to query css properties r=pbro
authorGreg Tatum <tatum.creative@gmail.com>
Thu, 26 May 2016 08:48:00 -0700
changeset 299382 4a7a28982402ba18d512b8d5cefa52b504d4ad9c
parent 299381 f7bfb9a0184ae6110e3daa919d5081320418f57c
child 299383 08053d1e2cc1f3b6e6901e4aedaf122c4e1de27b
push id77547
push userkwierso@gmail.com
push dateFri, 27 May 2016 21:43:34 +0000
treeherdermozilla-inbound@5c73a69cb9eb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbro
bugs1265798
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1265798 - Implement CSS database to query css properties r=pbro MozReview-Commit-ID: CAUh2GyeA2o
devtools/client/inspector/inspector-panel.js
devtools/client/inspector/layout/layout.js
devtools/client/inspector/rules/models/rule.js
devtools/client/inspector/rules/models/text-property.js
devtools/client/inspector/rules/views/rule-editor.js
devtools/client/inspector/rules/views/text-property-editor.js
devtools/client/inspector/shared/utils.js
devtools/client/shared/css-properties-db.js
devtools/client/shared/moz.build
devtools/client/shared/test/unit/test_parseDeclarations.js
devtools/client/shared/test/unit/test_parseSingleValue.js
devtools/client/shared/test/unit/test_rewriteDeclarations.js
devtools/server/actors/css-properties.js
devtools/server/actors/moz.build
devtools/server/actors/styles.js
devtools/server/main.js
devtools/server/tests/mochitest/chrome.ini
devtools/server/tests/mochitest/inspector_css-properties.html
devtools/server/tests/mochitest/test_css-properties.html
devtools/server/tests/mochitest/test_styles-modify.html
devtools/shared/css-parsing-utils.js
devtools/shared/fronts/css-properties.js
devtools/shared/fronts/moz.build
devtools/shared/fronts/styles.js
devtools/shared/specs/css-properties.js
devtools/shared/specs/moz.build
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -12,16 +12,17 @@ const {Cc, Ci} = require("chrome");
 
 var Services = require("Services");
 var promise = require("promise");
 var EventEmitter = require("devtools/shared/event-emitter");
 var clipboard = require("sdk/clipboard");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 var {Task} = require("devtools/shared/task");
+const {initCssProperties} = require("devtools/shared/fronts/css-properties");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
 
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "ComputedViewTool", "devtools/client/inspector/computed/computed", true);
 loader.lazyRequireGetter(this, "FontInspector", "devtools/client/inspector/fonts/fonts", true);
 loader.lazyRequireGetter(this, "HTMLBreadcrumbs", "devtools/client/inspector/breadcrumbs", true);
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
@@ -103,25 +104,24 @@ function InspectorPanel(iframeWindow, to
 }
 
 exports.InspectorPanel = InspectorPanel;
 
 InspectorPanel.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
-  open: function () {
-    return this.target.makeRemote().then(() => {
-      return this._getPageStyle();
-    }).then(() => {
-      return this._getDefaultNodeForSelection();
-    }).then(defaultSelection => {
-      return this._deferredOpen(defaultSelection);
-    }).then(null, console.error);
-  },
+  open: Task.async(function* () {
+    this._cssPropertiesLoaded = initCssProperties(this.toolbox);
+    yield this._cssPropertiesLoaded;
+    yield this.target.makeRemote();
+    yield this._getPageStyle();
+    let defaultSelection = yield this._getDefaultNodeForSelection();
+    return yield this._deferredOpen(defaultSelection);
+  }),
 
   get toolbox() {
     return this._toolbox;
   },
 
   get inspector() {
     return this._toolbox.inspector;
   },
@@ -644,16 +644,22 @@ InspectorPanel.prototype = {
     if (this.fontInspector) {
       this.fontInspector.destroy();
     }
 
     if (this.layoutview) {
       this.layoutview.destroy();
     }
 
+    let cssPropertiesDestroyer = this._cssPropertiesLoaded.then(({front}) => {
+      if (front) {
+        front.destroy();
+      }
+    });
+
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
     this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
     this.breadcrumbs.destroy();
     this._paneToggleButton.removeEventListener("mousedown",
@@ -673,17 +679,18 @@ InspectorPanel.prototype = {
     this.nodemenu = null;
     this._toolbox = null;
     this.search.destroy();
     this.search = null;
     this.searchBox = null;
 
     this._panelDestroyer = promise.all([
       sidebarDestroyer,
-      markupDestroyer
+      markupDestroyer,
+      cssPropertiesDestroyer
     ]);
 
     return this._panelDestroyer;
   },
 
   /**
    * Show the node menu.
    */
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -7,35 +7,39 @@
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const {Task} = require("devtools/shared/task");
 const {InplaceEditor, editableItem} =
       require("devtools/client/shared/inplace-editor");
 const {ReflowFront} = require("devtools/server/actors/layout");
 const {LocalizationHelper} = require("devtools/client/shared/l10n");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 const STRINGS_URI = "chrome://devtools/locale/shared.properties";
 const SHARED_L10N = new LocalizationHelper(STRINGS_URI);
 const NUMERIC = /^-?[\d\.]+$/;
 const LONG_TEXT_ROTATE_LIMIT = 3;
 
 /**
  * An instance of EditingSession tracks changes that have been made during the
  * modification of box model values. All of these changes can be reverted by
- * calling revert.
+ * calling revert. The main parameter is the LayoutView that created it.
  *
- * @param doc    A DOM document that can be used to test style rules.
- * @param rules  An array of the style rules defined for the node being edited.
- *               These should be in order of priority, least important first.
+ * @param inspector The inspector panel.
+ * @param doc       A DOM document that can be used to test style rules.
+ * @param rules     An array of the style rules defined for the node being
+ *                  edited. These should be in order of priority, least
+ *                  important first.
  */
-function EditingSession(doc, rules) {
+function EditingSession({inspector, doc, elementRules}) {
   this._doc = doc;
-  this._rules = rules;
+  this._rules = elementRules;
   this._modifications = new Map();
+  this._cssProperties = getCssProperties(inspector.toolbox);
 }
 
 EditingSession.prototype = {
   /**
    * Gets the value of a single property from the CSS rule.
    *
    * @param {StyleRuleFront} rule The CSS rule.
    * @param {String} property The name of the property.
@@ -105,17 +109,18 @@ EditingSession.prototype = {
    * @return {Promise} Resolves when the modifications are complete.
    */
   setProperties: Task.async(function* (properties) {
     for (let property of properties) {
       // Get a RuleModificationList or RuleRewriter helper object from the
       // StyleRuleActor to make changes to CSS properties.
       // Note that RuleRewriter doesn't support modifying several properties at
       // once, so we do this in a sequence here.
-      let modifications = this._rules[0].startModifyingProperties();
+      let modifications = this._rules[0].startModifyingProperties(
+        this._cssProperties);
 
       // Remember the property so it can be reverted.
       if (!this._modifications.has(property.name)) {
         this._modifications.set(property.name,
           this.getPropertyFromRule(this._rules[0], property.name));
       }
 
       // Find the index of the property to be changed, or get the next index to
@@ -138,17 +143,18 @@ EditingSession.prototype = {
   /**
    * Reverts all of the property changes made by this instance.
    * @return {Promise} Resolves when all properties have been reverted.
    */
   revert: Task.async(function* () {
     // Revert each property that we modified previously, one by one. See
     // setProperties for information about why.
     for (let [property, value] of this._modifications) {
-      let modifications = this._rules[0].startModifyingProperties();
+      let modifications = this._rules[0].startModifyingProperties(
+        this._cssProperties);
 
       // Find the index of the property to be reverted.
       let index = this.getPropertyIndex(property);
 
       if (value != "") {
         // If the property doesn't exist anymore, insert at the beginning of the
         // rule.
         if (index === -1) {
@@ -353,17 +359,17 @@ LayoutView.prototype = {
     this.reflowFront.stop();
   },
 
   /**
    * Called when the user clicks on one of the editable values in the layoutview
    */
   initEditor: function (element, event, dimension) {
     let { property } = dimension;
-    let session = new EditingSession(this.doc, this.elementRules);
+    let session = new EditingSession(this);
     let initialValue = session.getProperty(property);
 
     let editor = new InplaceEditor({
       element: element,
       initial: initialValue,
       contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
       property: {
         name: dimension.property
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -9,16 +9,17 @@
 const {Cc, Ci, Cu} = require("chrome");
 const promise = require("promise");
 const {CssLogic} = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
 const {TextProperty} =
       require("devtools/client/inspector/rules/models/text-property");
 const {promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "osString", function () {
   return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
 });
 
 XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
@@ -54,16 +55,19 @@ function Rule(elementStyle, options) {
   this.inherited = options.inherited || null;
   this.keyframes = options.keyframes || null;
   this._modificationDepth = 0;
 
   if (this.domRule && this.domRule.mediaText) {
     this.mediaText = this.domRule.mediaText;
   }
 
+  const toolbox = this.elementStyle.ruleView.inspector.toolbox;
+  this.cssProperties = getCssProperties(toolbox);
+
   // Populate the text properties with the style's current authoredText
   // value, and add in any disabled properties from the store.
   this.textProps = this._getTextProperties();
   this.textProps = this.textProps.concat(this._getDisabledProperties());
 }
 
 Rule.prototype = {
   mediaText: "",
@@ -243,17 +247,18 @@ Rule.prototype = {
       disabled.delete(this.style);
     }
 
     return modifications.apply().then(() => {
       let cssProps = {};
       // Note that even though StyleRuleActors normally provide parsed
       // declarations already, _applyPropertiesNoAuthored is only used when
       // connected to older backend that do not provide them. So parse here.
-      for (let cssProp of parseDeclarations(this.style.authoredText)) {
+      for (let cssProp of parseDeclarations(this.cssProperties.isKnown,
+                                            this.style.authoredText)) {
         cssProps[cssProp.name] = cssProp;
       }
 
       for (let textProp of this.textProps) {
         if (!textProp.enabled) {
           continue;
         }
         let cssProp = cssProps[textProp.name];
@@ -307,17 +312,18 @@ Rule.prototype = {
    * @return {Promise} a promise which will resolve when the edit
    *        is complete
    */
   applyProperties: function (modifier) {
     // If there is already a pending modification, we have to wait
     // until it settles before applying the next modification.
     let resultPromise =
         promise.resolve(this._applyingModifications).then(() => {
-          let modifications = this.style.startModifyingProperties();
+          let modifications = this.style.startModifyingProperties(
+            this.cssProperties);
           modifier(modifications);
           if (this.style.canSetRuleText) {
             return this._applyPropertiesAuthored(modifications);
           }
           return this._applyPropertiesNoAuthored(modifications);
         }).then(() => {
           this.elementStyle.markOverriddenAll();
 
@@ -383,17 +389,17 @@ Rule.prototype = {
    * @param {TextProperty} property
    *        The property which value will be previewed
    * @param {String} value
    *        The value to be used for the preview
    * @param {String} priority
    *        The property's priority (either "important" or an empty string).
    */
   previewPropertyValue: function (property, value, priority) {
-    let modifications = this.style.startModifyingProperties();
+    let modifications = this.style.startModifyingProperties(this.cssProperties);
     modifications.setProperty(this.textProps.indexOf(property),
                               property.name, value, priority);
     modifications.apply().then(() => {
       // Ensure dispatching a ruleview-changed event
       // also for previews
       this.elementStyle._changed();
     });
   },
@@ -439,17 +445,18 @@ Rule.prototype = {
    */
   _getTextProperties: function () {
     let textProps = [];
     let store = this.elementStyle.store;
 
     // Starting with FF49, StyleRuleActors provide parsed declarations.
     let props = this.style.declarations;
     if (!props) {
-      props = parseDeclarations(this.style.authoredText, true);
+      props = parseDeclarations(this.cssProperties.isKnown,
+                                this.style.authoredText, true);
     }
 
     for (let prop of props) {
       let name = prop.name;
       // If the authored text has an invalid property, it will show up
       // as nameless.  Skip these as we don't currently have a good
       // way to display them.
       if (!name) {
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -3,16 +3,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 strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 const {escapeCSSComment} = require("devtools/shared/css-parsing-utils");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 /**
@@ -44,16 +45,19 @@ function TextProperty(rule, name, value,
                       invisible = false) {
   this.rule = rule;
   this.name = name;
   this.value = value;
   this.priority = priority;
   this.enabled = !!enabled;
   this.invisible = invisible;
   this.updateComputed();
+
+  const toolbox = this.rule.elementStyle.ruleView.inspector.toolbox;
+  this.cssProperties = getCssProperties(toolbox);
 }
 
 TextProperty.prototype = {
   /**
    * Update the editor associated with this text property,
    * if any.
    */
   updateEditor: function () {
@@ -181,25 +185,17 @@ TextProperty.prototype = {
   },
 
   /**
    * See whether this property's name is known.
    *
    * @return {Boolean} true if the property name is known, false otherwise.
    */
   isKnownProperty: function () {
-    try {
-      // If the property name is invalid, the cssPropertyIsShorthand
-      // will throw an exception.  But if it is valid, no exception will
-      // be thrown; so we just ignore the return value.
-      domUtils.cssPropertyIsShorthand(this.name);
-      return true;
-    } catch (e) {
-      return false;
-    }
+    return this.cssProperties.isKnown(this.name);
   },
 
   /**
    * Validate this property. Does it make sense for this value to be assigned
    * to this property name?
    *
    * @return {Boolean} true if the property value is valid, false otherwise.
    */
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -437,17 +437,17 @@ RuleEditor.prototype = {
       destroy: this._newPropertyDestroy,
       advanceChars: ":",
       contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
       popup: this.ruleView.popup
     });
 
     // Auto-close the input if multiple rules get pasted into new property.
     this.editor.input.addEventListener("paste",
-      blurOnMultipleProperties, false);
+      blurOnMultipleProperties(this.rule.cssProperties), false);
   },
 
   /**
    * Called when the new property input has been dismissed.
    *
    * @param {String} value
    *        The value in the editor.
    * @param {Boolean} commit
@@ -457,17 +457,18 @@ RuleEditor.prototype = {
     if (!value || !commit) {
       return;
     }
 
     // parseDeclarations allows for name-less declarations, but in the present
     // case, we're creating a new declaration, it doesn't make sense to accept
     // these entries
     this.multipleAddedProperties =
-      parseDeclarations(value, true).filter(d => d.name);
+      parseDeclarations(this.rule.cssProperties.isKnown, value, true)
+      .filter(d => d.name);
 
     // Blur the editor field now and deal with adding declarations later when
     // the field gets destroyed (see _newPropertyDestroy)
     this.editor.input.blur();
   },
 
   /**
    * Called when the new property editor is destroyed.
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -1,16 +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 strict";
 
 const {Ci} = require("chrome");
 const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 const {InplaceEditor, editableField} =
       require("devtools/client/shared/inplace-editor");
 const {
   createChild,
   appendText,
   advanceValidate,
   blurOnMultipleProperties,
   throttle
@@ -39,16 +40,19 @@ function TextPropertyEditor(ruleEditor, 
   this.ruleView = this.ruleEditor.ruleView;
   this.doc = this.ruleEditor.doc;
   this.popup = this.ruleView.popup;
   this.prop = property;
   this.prop.editor = this;
   this.browserWindow = this.doc.defaultView.top;
   this._populatedComputed = false;
 
+  const toolbox = this.ruleView.inspector.toolbox;
+  this.cssProperties = getCssProperties(toolbox);
+
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onValueDone = this._onValueDone.bind(this);
   this._onSwatchCommit = this._onSwatchCommit.bind(this);
   this._onSwatchPreview = this._onSwatchPreview.bind(this);
   this._onSwatchRevert = this._onSwatchRevert.bind(this);
@@ -194,17 +198,17 @@ TextPropertyEditor.prototype = {
         destroy: this.update,
         advanceChars: ":",
         contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
         popup: this.popup
       });
 
       // Auto blur name field on multiple CSS rules get pasted in.
       this.nameContainer.addEventListener("paste",
-        blurOnMultipleProperties, false);
+        blurOnMultipleProperties(this.cssProperties), false);
 
       this.valueContainer.addEventListener("click", (event) => {
         // Clicks within the value shouldn't propagate any further.
         event.stopPropagation();
 
         // Forward clicks on valueContainer to the editable valueSpan
         if (event.target === this.valueContainer) {
           this.valueSpan.click();
@@ -554,17 +558,17 @@ TextPropertyEditor.prototype = {
     if (!this.prop.value &&
         direction !== Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
       this.remove(direction);
       return;
     }
 
     // Adding multiple rules inside of name field overwrites the current
     // property with the first, then adds any more onto the property list.
-    let properties = parseDeclarations(value);
+    let properties = parseDeclarations(this.cssProperties.isKnown, value);
 
     if (properties.length) {
       this.prop.setName(properties[0].name);
       this.committed.name = this.prop.name;
 
       if (!this.prop.enabled) {
         this.prop.setEnabled(true);
       }
@@ -613,17 +617,18 @@ TextPropertyEditor.prototype = {
    *        The value contained in the editor.
    * @param {Boolean} commit
    *        True if the change should be applied.
    * @param {Number} direction
    *        The move focus direction number.
    */
   _onValueDone: function (value = "", commit, direction) {
     let parsedProperties = this._getValueAndExtraProperties(value);
-    let val = parseSingleValue(parsedProperties.firstValue);
+    let val = parseSingleValue(this.cssProperties.isKnown,
+                               parsedProperties.firstValue);
     let isValueUnchanged = (!commit && !this.ruleEditor.isEditing) ||
                            !parsedProperties.propertiesToAdd.length &&
                            this.committed.value === val.value &&
                            this.committed.priority === val.priority;
     // If the value is not empty and unchanged, revert the property back to
     // its original value and enabled or disabled state
     if (value.trim() && isValueUnchanged) {
       this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
@@ -701,17 +706,17 @@ TextPropertyEditor.prototype = {
   _getValueAndExtraProperties: function (value) {
     // The inplace editor will prevent manual typing of multiple properties,
     // but we need to deal with the case during a paste event.
     // Adding multiple properties inside of value editor sets value with the
     // first, then adds any more onto the property list (below this property).
     let firstValue = value;
     let propertiesToAdd = [];
 
-    let properties = parseDeclarations(value);
+    let properties = parseDeclarations(this.cssProperties.isKnown, value);
 
     // Check to see if the input string can be parsed as multiple properties
     if (properties.length) {
       // Get the first property value (if any), and any remaining
       // properties (if any)
       if (!properties[0].name && properties[0].value) {
         firstValue = properties[0].value;
         propertiesToAdd = properties.slice(1);
@@ -740,17 +745,17 @@ TextPropertyEditor.prototype = {
    */
   _previewValue: function (value, reverting = false) {
     // Since function call is throttled, we need to make sure we are still
     // editing, and any selector modifications have been completed
     if (!reverting && (!this.editing || this.ruleEditor.isEditing)) {
       return;
     }
 
-    let val = parseSingleValue(value);
+    let val = parseSingleValue(this.cssProperties.isKnown, value);
     this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
                                               val.priority);
   },
 
   /**
    * Validate this property. Does it make sense for this value to be assigned
    * to this property name? This does not apply the property value
    *
--- a/devtools/client/inspector/shared/utils.js
+++ b/devtools/client/inspector/shared/utils.js
@@ -127,23 +127,25 @@ function throttle(func, wait, scope) {
 }
 
 exports.throttle = throttle;
 
 /**
  * Event handler that causes a blur on the target if the input has
  * multiple CSS properties as the value.
  */
-function blurOnMultipleProperties(e) {
-  setTimeout(() => {
-    let props = parseDeclarations(e.target.value);
-    if (props.length > 1) {
-      e.target.blur();
-    }
-  }, 0);
+function blurOnMultipleProperties(cssProperties) {
+  return (e) => {
+    setTimeout(() => {
+      let props = parseDeclarations(cssProperties.isKnown, e.target.value);
+      if (props.length > 1) {
+        e.target.blur();
+      }
+    }, 0);
+  };
 }
 
 exports.blurOnMultipleProperties = blurOnMultipleProperties;
 
 /**
  * Log the provided error to the console and return a rejected Promise for
  * this error.
  *
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/css-properties-db.js
@@ -0,0 +1,422 @@
+/* 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 strict";
+
+/**
+ * This list is generated from the output of the CssPropertiesActor. If a server
+ * does not support the actor, this is loaded as a backup. This list does not
+ * guarantee that the server actually supports these CSS properties.
+ */
+exports.propertiesList = [
+  "align-content",
+  "align-items",
+  "align-self",
+  "animation-delay",
+  "animation-direction",
+  "animation-duration",
+  "animation-fill-mode",
+  "animation-iteration-count",
+  "animation-name",
+  "animation-play-state",
+  "animation-timing-function",
+  "-moz-appearance",
+  "backface-visibility",
+  "background-attachment",
+  "background-blend-mode",
+  "background-clip",
+  "background-color",
+  "background-image",
+  "background-origin",
+  "background-position-x",
+  "background-position-y",
+  "background-repeat",
+  "background-size",
+  "-moz-binding",
+  "block-size",
+  "border-block-end-color",
+  "border-block-end-style",
+  "border-block-end-width",
+  "border-block-start-color",
+  "border-block-start-style",
+  "border-block-start-width",
+  "border-bottom-color",
+  "-moz-border-bottom-colors",
+  "border-bottom-left-radius",
+  "border-bottom-right-radius",
+  "border-bottom-style",
+  "border-bottom-width",
+  "border-collapse",
+  "border-image-outset",
+  "border-image-repeat",
+  "border-image-slice",
+  "border-image-source",
+  "border-image-width",
+  "border-inline-end-color",
+  "border-inline-end-style",
+  "border-inline-end-width",
+  "border-inline-start-color",
+  "border-inline-start-style",
+  "border-inline-start-width",
+  "border-left-color",
+  "-moz-border-left-colors",
+  "border-left-style",
+  "border-left-width",
+  "border-right-color",
+  "-moz-border-right-colors",
+  "border-right-style",
+  "border-right-width",
+  "border-spacing",
+  "border-top-color",
+  "-moz-border-top-colors",
+  "border-top-left-radius",
+  "border-top-right-radius",
+  "border-top-style",
+  "border-top-width",
+  "bottom",
+  "-moz-box-align",
+  "box-decoration-break",
+  "-moz-box-direction",
+  "-moz-box-flex",
+  "-moz-box-ordinal-group",
+  "-moz-box-orient",
+  "-moz-box-pack",
+  "box-shadow",
+  "box-sizing",
+  "caption-side",
+  "clear",
+  "clip",
+  "clip-path",
+  "clip-rule",
+  "color",
+  "color-adjust",
+  "color-interpolation",
+  "color-interpolation-filters",
+  "-moz-column-count",
+  "-moz-column-fill",
+  "-moz-column-gap",
+  "-moz-column-rule-color",
+  "-moz-column-rule-style",
+  "-moz-column-rule-width",
+  "-moz-column-width",
+  "content",
+  "-moz-control-character-visibility",
+  "counter-increment",
+  "counter-reset",
+  "cursor",
+  "direction",
+  "display",
+  "dominant-baseline",
+  "empty-cells",
+  "fill",
+  "fill-opacity",
+  "fill-rule",
+  "filter",
+  "flex-basis",
+  "flex-direction",
+  "flex-grow",
+  "flex-shrink",
+  "flex-wrap",
+  "float",
+  "-moz-float-edge",
+  "flood-color",
+  "flood-opacity",
+  "font-family",
+  "font-feature-settings",
+  "font-kerning",
+  "font-language-override",
+  "font-size",
+  "font-size-adjust",
+  "font-stretch",
+  "font-style",
+  "font-synthesis",
+  "font-variant-alternates",
+  "font-variant-caps",
+  "font-variant-east-asian",
+  "font-variant-ligatures",
+  "font-variant-numeric",
+  "font-variant-position",
+  "font-weight",
+  "-moz-force-broken-image-icon",
+  "grid-auto-columns",
+  "grid-auto-flow",
+  "grid-auto-rows",
+  "grid-column-end",
+  "grid-column-gap",
+  "grid-column-start",
+  "grid-row-end",
+  "grid-row-gap",
+  "grid-row-start",
+  "grid-template-areas",
+  "grid-template-columns",
+  "grid-template-rows",
+  "height",
+  "hyphens",
+  "image-orientation",
+  "-moz-image-region",
+  "image-rendering",
+  "ime-mode",
+  "inline-size",
+  "isolation",
+  "justify-content",
+  "justify-items",
+  "justify-self",
+  "left",
+  "letter-spacing",
+  "lighting-color",
+  "line-height",
+  "list-style-image",
+  "list-style-position",
+  "list-style-type",
+  "margin-block-end",
+  "margin-block-start",
+  "margin-bottom",
+  "margin-inline-end",
+  "margin-inline-start",
+  "margin-left",
+  "margin-right",
+  "margin-top",
+  "marker-end",
+  "marker-mid",
+  "marker-offset",
+  "marker-start",
+  "mask",
+  "mask-type",
+  "max-block-size",
+  "max-height",
+  "max-inline-size",
+  "max-width",
+  "min-block-size",
+  "min-height",
+  "min-inline-size",
+  "min-width",
+  "mix-blend-mode",
+  "object-fit",
+  "object-position",
+  "offset-block-end",
+  "offset-block-start",
+  "offset-inline-end",
+  "offset-inline-start",
+  "opacity",
+  "order",
+  "-moz-orient",
+  "-moz-osx-font-smoothing",
+  "outline-color",
+  "outline-offset",
+  "-moz-outline-radius-bottomleft",
+  "-moz-outline-radius-bottomright",
+  "-moz-outline-radius-topleft",
+  "-moz-outline-radius-topright",
+  "outline-style",
+  "outline-width",
+  "overflow-x",
+  "overflow-y",
+  "padding-block-end",
+  "padding-block-start",
+  "padding-bottom",
+  "padding-inline-end",
+  "padding-inline-start",
+  "padding-left",
+  "padding-right",
+  "padding-top",
+  "page-break-after",
+  "page-break-before",
+  "page-break-inside",
+  "paint-order",
+  "perspective",
+  "perspective-origin",
+  "pointer-events",
+  "position",
+  "quotes",
+  "resize",
+  "right",
+  "ruby-align",
+  "ruby-position",
+  "scroll-behavior",
+  "scroll-snap-coordinate",
+  "scroll-snap-destination",
+  "scroll-snap-points-x",
+  "scroll-snap-points-y",
+  "scroll-snap-type-x",
+  "scroll-snap-type-y",
+  "shape-rendering",
+  "-moz-stack-sizing",
+  "stop-color",
+  "stop-opacity",
+  "stroke",
+  "stroke-dasharray",
+  "stroke-dashoffset",
+  "stroke-linecap",
+  "stroke-linejoin",
+  "stroke-miterlimit",
+  "stroke-opacity",
+  "stroke-width",
+  "-moz-tab-size",
+  "table-layout",
+  "text-align",
+  "-moz-text-align-last",
+  "text-anchor",
+  "text-combine-upright",
+  "text-decoration-color",
+  "text-decoration-line",
+  "text-decoration-style",
+  "text-emphasis-color",
+  "text-emphasis-position",
+  "text-emphasis-style",
+  "-webkit-text-fill-color",
+  "text-indent",
+  "text-orientation",
+  "text-overflow",
+  "text-rendering",
+  "text-shadow",
+  "-moz-text-size-adjust",
+  "-webkit-text-stroke-color",
+  "-webkit-text-stroke-width",
+  "text-transform",
+  "top",
+  "transform",
+  "transform-box",
+  "transform-origin",
+  "transform-style",
+  "transition-delay",
+  "transition-duration",
+  "transition-property",
+  "transition-timing-function",
+  "unicode-bidi",
+  "-moz-user-focus",
+  "-moz-user-input",
+  "-moz-user-modify",
+  "-moz-user-select",
+  "vector-effect",
+  "vertical-align",
+  "visibility",
+  "white-space",
+  "width",
+  "will-change",
+  "-moz-window-dragging",
+  "word-break",
+  "word-spacing",
+  "word-wrap",
+  "writing-mode",
+  "z-index",
+  "all",
+  "animation",
+  "background",
+  "background-position",
+  "border",
+  "border-block-end",
+  "border-block-start",
+  "border-bottom",
+  "border-color",
+  "border-image",
+  "border-inline-end",
+  "border-inline-start",
+  "border-left",
+  "border-radius",
+  "border-right",
+  "border-style",
+  "border-top",
+  "border-width",
+  "-moz-column-rule",
+  "-moz-columns",
+  "flex",
+  "flex-flow",
+  "font",
+  "font-variant",
+  "grid",
+  "grid-area",
+  "grid-column",
+  "grid-gap",
+  "grid-row",
+  "grid-template",
+  "list-style",
+  "margin",
+  "marker",
+  "outline",
+  "-moz-outline-radius",
+  "overflow",
+  "padding",
+  "scroll-snap-type",
+  "text-decoration",
+  "text-emphasis",
+  "-webkit-text-stroke",
+  "-moz-transform",
+  "transition",
+  "-moz-transform-origin",
+  "-moz-perspective-origin",
+  "-moz-perspective",
+  "-moz-transform-style",
+  "-moz-backface-visibility",
+  "-moz-border-image",
+  "-moz-transition",
+  "-moz-transition-delay",
+  "-moz-transition-duration",
+  "-moz-transition-property",
+  "-moz-transition-timing-function",
+  "-moz-animation",
+  "-moz-animation-delay",
+  "-moz-animation-direction",
+  "-moz-animation-duration",
+  "-moz-animation-fill-mode",
+  "-moz-animation-iteration-count",
+  "-moz-animation-name",
+  "-moz-animation-play-state",
+  "-moz-animation-timing-function",
+  "-moz-box-sizing",
+  "-moz-font-feature-settings",
+  "-moz-font-language-override",
+  "-moz-padding-end",
+  "-moz-padding-start",
+  "-moz-margin-end",
+  "-moz-margin-start",
+  "-moz-border-end",
+  "-moz-border-end-color",
+  "-moz-border-end-style",
+  "-moz-border-end-width",
+  "-moz-border-start",
+  "-moz-border-start-color",
+  "-moz-border-start-style",
+  "-moz-border-start-width",
+  "-moz-hyphens",
+  "-webkit-animation",
+  "-webkit-animation-delay",
+  "-webkit-animation-direction",
+  "-webkit-animation-duration",
+  "-webkit-animation-fill-mode",
+  "-webkit-animation-iteration-count",
+  "-webkit-animation-name",
+  "-webkit-animation-play-state",
+  "-webkit-animation-timing-function",
+  "-webkit-filter",
+  "-webkit-text-size-adjust",
+  "-webkit-transform",
+  "-webkit-transform-origin",
+  "-webkit-transform-style",
+  "-webkit-backface-visibility",
+  "-webkit-perspective",
+  "-webkit-perspective-origin",
+  "-webkit-transition",
+  "-webkit-transition-delay",
+  "-webkit-transition-duration",
+  "-webkit-transition-property",
+  "-webkit-transition-timing-function",
+  "-webkit-border-radius",
+  "-webkit-border-top-left-radius",
+  "-webkit-border-top-right-radius",
+  "-webkit-border-bottom-left-radius",
+  "-webkit-border-bottom-right-radius",
+  "-webkit-background-clip",
+  "-webkit-background-origin",
+  "-webkit-background-size",
+  "-webkit-border-image",
+  "-webkit-box-shadow",
+  "-webkit-box-sizing",
+  "-webkit-box-flex",
+  "-webkit-box-ordinal-group",
+  "-webkit-box-orient",
+  "-webkit-box-direction",
+  "-webkit-box-align",
+  "-webkit-box-pack",
+  "-webkit-user-select"
+];
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -16,16 +16,17 @@ DIRS += [
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
     'css-angle.js',
     'css-color-db.js',
     'css-color.js',
+    'css-properties-db.js',
     'css-reload.js',
     'Curl.jsm',
     'demangle.js',
     'developer-toolbar.js',
     'devices.js',
     'devtools-file-watcher.js',
     'DOMHelpers.jsm',
     'doorhanger.js',
--- a/devtools/client/shared/test/unit/test_parseDeclarations.js
+++ b/devtools/client/shared/test/unit/test_parseDeclarations.js
@@ -3,16 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {parseDeclarations, _parseCommentDeclarations} = require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   // Simple test
   {
     input: "p:v;",
     expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
   },
   // Simple test
@@ -354,17 +355,18 @@ function run_test() {
 }
 
 // Test parseDeclarations.
 function run_basic_tests() {
   for (let test of TEST_DATA) {
     do_print("Test input string " + test.input);
     let output;
     try {
-      output = parseDeclarations(test.input, test.parseComments);
+      output = parseDeclarations(isCssPropertyKnown, test.input,
+                                 test.parseComments);
     } catch (e) {
       do_print("parseDeclarations threw an exception with the given input " +
         "string");
       if (test.throws) {
         do_print("Exception expected");
         do_check_true(true);
       } else {
         do_print("Exception unexpected\n" + e);
@@ -389,17 +391,17 @@ const COMMENT_DATA = [
     expected: []
   },
 ];
 
 // Test parseCommentDeclarations.
 function run_comment_tests() {
   for (let test of COMMENT_DATA) {
     do_print("Test input string " + test.input);
-    let output = _parseCommentDeclarations(test.input, 0,
+    let output = _parseCommentDeclarations(isCssPropertyKnown, test.input, 0,
                                            test.input.length + 4);
     deepEqual(output, test.expected);
   }
 }
 
 function assertOutput(actual, expected) {
   if (actual.length === expected.length) {
     for (let i = 0; i < expected.length; i++) {
--- a/devtools/client/shared/test/unit/test_parseSingleValue.js
+++ b/devtools/client/shared/test/unit/test_parseSingleValue.js
@@ -3,16 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {parseSingleValue} = require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   {input: null, throws: true},
   {input: undefined, throws: true},
   {input: "", expected: {value: "", priority: ""}},
   {input: "  \t \t \n\n  ", expected: {value: "", priority: ""}},
   {input: "blue", expected: {value: "blue", priority: ""}},
   {input: "blue !important", expected: {value: "blue", priority: "important"}},
@@ -64,17 +65,17 @@ const TEST_DATA = [
     }
   }
 ];
 
 function run_test() {
   for (let test of TEST_DATA) {
     do_print("Test input value " + test.input);
     try {
-      let output = parseSingleValue(test.input);
+      let output = parseSingleValue(isCssPropertyKnown, test.input);
       assertOutput(output, test.expected);
     } catch (e) {
       do_print("parseSingleValue threw an exception with the given input " +
         "value");
       if (test.throws) {
         do_print("Exception expected");
         do_check_true(true);
       } else {
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -3,16 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 Cu.import("resource://devtools/shared/Loader.jsm");
 const {RuleRewriter} = devtools.require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 const TEST_DATA = [
   {
     desc: "simple set",
     input: "p:v;",
     instruction: {type: "set", name: "p", value: "N", priority: "",
                   index: 0},
     expected: "p:N;"
@@ -438,17 +439,17 @@ const TEST_DATA = [
     instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
                   index: 1},
     expected: "/*! no: semicolon */\nwalrus: zebra;\n",
     changed: {}
   },
 ];
 
 function rewriteDeclarations(inputString, instruction, defaultIndentation) {
-  let rewriter = new RuleRewriter(null, inputString);
+  let rewriter = new RuleRewriter(isCssPropertyKnown, null, inputString);
   rewriter.defaultIndentation = defaultIndentation;
 
   switch (instruction.type) {
     case "rename":
       rewriter.renameProperty(instruction.index, instruction.name,
                               instruction.newName);
       break;
 
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/css-properties.js
@@ -0,0 +1,53 @@
+/* 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 strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+
+loader.lazyGetter(this, "DOMUtils", () => {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
+
+const protocol = require("devtools/shared/protocol");
+const { ActorClassWithSpec, Actor } = protocol;
+const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
+
+var CssPropertiesActor = exports.CssPropertiesActor = ActorClassWithSpec(cssPropertiesSpec, {
+  typeName: "cssProperties",
+
+  initialize: function(conn, parent) {
+    Actor.prototype.initialize.call(this, conn);
+    this.parent = parent;
+  },
+
+  destroy: function() {
+    Actor.prototype.destroy.call(this);
+  },
+
+  getCSSDatabase: function() {
+    const propertiesList = DOMUtils.getCSSPropertyNames(DOMUtils.INCLUDE_ALIASES);
+    return { propertiesList };
+  }
+});
+
+/**
+ * Test if a CSS is property is known using server-code.
+ *
+ * @param {string} name
+ * @return {Boolean}
+ */
+function isCssPropertyKnown(name) {
+  try {
+    // If the property name is unknown, the cssPropertyIsShorthand
+    // will throw an exception.  But if it is known, no exception will
+    // be thrown; so we just ignore the return value.
+    DOMUtils.cssPropertyIsShorthand(name);
+    return true;
+  } catch (e) {
+    return false;
+  }
+}
+
+exports.isCssPropertyKnown = isCssPropertyKnown
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -16,16 +16,17 @@ DevToolsModules(
     'animation.js',
     'breakpoint.js',
     'call-watcher.js',
     'canvas.js',
     'child-process.js',
     'childtab.js',
     'chrome.js',
     'common.js',
+    'css-properties.js',
     'csscoverage.js',
     'device.js',
     'director-manager.js',
     'director-registry.js',
     'environment.js',
     'errordocs.js',
     'eventlooplag.js',
     'frame.js',
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -5,16 +5,17 @@
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const promise = require("promise");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {getDefinedGeometryProperties} = require("devtools/server/actors/highlighters/geometry-editor");
 const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 const {Task} = require("devtools/shared/task");
 const events = require("sdk/event/core");
 
 // This will also add the "stylesheet" actor type for protocol.js to recognize
 const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = require("devtools/server/actors/stylesheets");
 const {pageStyleSpec, styleRuleSpec} = require("devtools/shared/specs/styles");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
@@ -1088,18 +1089,19 @@ var StyleRuleActor = protocol.ActorClass
         form.keyText = this.rawRule.keyText || "";
         break;
     }
 
     // Parse the text into a list of declarations so the client doesn't have to
     // and so that we can safely determine if a declaration is valid rather than
     // have the client guess it.
     if (form.authoredText || form.cssText) {
-      let declarations = parseDeclarations(form.authoredText ||
-                                           form.cssText, true);
+      let declarations = parseDeclarations(isCssPropertyKnown,
+                                           form.authoredText || form.cssText,
+                                           true);
       form.declarations = declarations.map(decl => {
         decl.isValid = DOMUtils.cssPropertyIsValid(decl.name, decl.value);
         return decl;
       });
     }
 
     return form;
   },
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -520,16 +520,21 @@ var DebuggerServer = {
       constructor: "EventLoopLagActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/layout", {
       prefix: "reflow",
       constructor: "ReflowActor",
       type: { tab: true }
     });
+    this.registerModule("devtools/server/actors/css-properties", {
+      prefix: "cssProperties",
+      constructor: "CssPropertiesActor",
+      type: { tab: true }
+    });
     this.registerModule("devtools/server/actors/csscoverage", {
       prefix: "cssUsage",
       constructor: "CSSUsageActor",
       type: { tab: true }
     });
     this.registerModule("devtools/server/actors/monitor", {
       prefix: "monitor",
       constructor: "MonitorActor",
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -3,16 +3,17 @@ tags = devtools
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   animation-data.html
   Debugger.Source.prototype.element.js
   Debugger.Source.prototype.element-2.js
   Debugger.Source.prototype.element.html
   director-helpers.js
   hello-actor.js
+  inspector_css-properties.html
   inspector_getImageData.html
   inspector-delay-image-response.sjs
   inspector-helpers.js
   inspector-search-data.html
   inspector-styles-data.css
   inspector-styles-data.html
   inspector-traversal-data.html
   large-image.jpg
@@ -27,16 +28,17 @@ support-files =
 [test_connection-manager.html]
 skip-if = buildapp == 'mulet'
 [test_connectToChild.html]
 skip-if = buildapp == 'mulet'
 [test_css-logic.html]
 [test_css-logic-inheritance.html]
 [test_css-logic-media-queries.html]
 [test_css-logic-specificity.html]
+[test_css-properties.html]
 [test_Debugger.Source.prototype.introductionScript.html]
 [test_Debugger.Source.prototype.introductionType.html]
 [test_Debugger.Source.prototype.element.html]
 [test_Debugger.Script.prototype.global.html]
 [test_device.html]
 skip-if = buildapp == 'mulet'
 [test_director.html]
 [test_director_connectToChild.html]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector_css-properties.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<body>
+  <script type="text/javascript">
+    window.onload = function() {
+      window.opener.postMessage('ready', '*');
+    };
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-properties.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test CSS Properties Actor</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+window.onload = function() {
+  const { initCssProperties, getCssProperties } =
+    require("devtools/shared/fronts/css-properties");
+
+  function promiseAttachUrl (url) {
+    return new Promise((resolve, reject) => {
+      attachURL(url, function(err, client, tab, doc) {
+        if (err) {
+          return reject(err);
+        }
+        resolve({client, tab, doc});
+      });
+    })
+  }
+
+  const runCssPropertiesTests = Task.async(function* (url, useActor) {
+    info("Opening two tabs.");
+
+    let attachmentA = yield promiseAttachUrl(url);
+    let attachmentB = yield promiseAttachUrl(url);
+
+    const toolboxMockA = {
+      target: {
+        hasActor: () => useActor,
+        client: attachmentA.client,
+        form: attachmentA.tab
+      }
+    };
+    const toolboxMockB = {
+      target: {
+        hasActor: () => useActor,
+        client: attachmentB.client,
+        form: attachmentB.tab
+      }
+    };
+
+    yield initCssProperties(toolboxMockA);
+    yield initCssProperties(toolboxMockB);
+
+    const cssProperties = getCssProperties(toolboxMockA);
+    const cssPropertiesA = getCssProperties(toolboxMockA);
+    const cssPropertiesB = getCssProperties(toolboxMockB);
+
+    is(cssProperties, cssPropertiesA,
+       "Multiple calls with the same toolbox returns the same object.");
+    isnot(cssProperties, cssPropertiesB,
+       "Multiple calls with the different toolboxes return different "+
+       " objects.");
+
+    ok(cssProperties.isKnown("border"),
+      "The `border` shorthand property is known.");
+    ok(cssProperties.isKnown("display"),
+      "The `display` property is known.");
+    ok(!cssProperties.isKnown("foobar"),
+      "A fake property is not known.");
+    ok(cssProperties.isKnown("--foobar"),
+      "A CSS variable properly evaluates.");
+    ok(cssProperties.isKnown("--foob\\{ar"),
+      "A CSS variable with escaped character properly evaluates.");
+    ok(cssProperties.isKnown("--fübar"),
+      "A CSS variable unicode properly evaluates.");
+    ok(!cssProperties.isKnown("--foo bar"),
+      "A CSS variable with spaces fails");
+  });
+
+  addAsyncTest(function* setup() {
+    let url = document.getElementById("cssProperties").href;
+    yield runCssPropertiesTests(url, true);
+    yield runCssPropertiesTests(url, false);
+
+    runNextTest();
+  });
+
+  SimpleTest.waitForExplicitFinish();
+  runNextTest();
+}
+  </script>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265798">Mozilla Bug 1265798</a>
+  <a id="cssProperties" target="_blank" href="inspector_css-properties.html">Test Document</a>
+</body>
+</html>
--- a/devtools/server/tests/mochitest/test_styles-modify.html
+++ b/devtools/server/tests/mochitest/test_styles-modify.html
@@ -7,16 +7,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug </title>
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 const inspector = require("devtools/server/actors/inspector");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
 
 window.onload = function() {
   SimpleTest.waitForExplicitFinish();
   runNextTest();
 }
 
 var gWalker = null;
 var gStyles = null;
@@ -77,23 +78,23 @@ addAsyncTest(function* modifyProperties(
   is(elementStyle.cssText, "", "Should have expected cssText");
   is(elementStyle.cssText, localNode.style.cssText,
      "Local node and style front match.");
 
   runNextTest();
 });
 
 function* setProperty(rule, index, name, value) {
-  let changes = rule.startModifyingProperties();
+  let changes = rule.startModifyingProperties(isCssPropertyKnown);
   changes.setProperty(index, name, value);
   yield changes.apply();
 }
 
 function* removeProperty(rule, index, name) {
-  let changes = rule.startModifyingProperties();
+  let changes = rule.startModifyingProperties(isCssPropertyKnown);
   changes.removeProperty(index, name);
   yield changes.apply();
 }
 
 addTest(function cleanup() {
   delete gStyles;
   delete gWalker;
   delete gClient;
--- a/devtools/shared/css-parsing-utils.js
+++ b/devtools/shared/css-parsing-utils.js
@@ -9,24 +9,20 @@
 // parseDeclarations - parse a CSS rule into declarations
 // RuleRewriter - rewrite CSS rule text
 // parsePseudoClassesAndAttributes - parse selector and extract
 //     pseudo-classes
 // parseSingleValue - parse a single CSS property value
 
 "use strict";
 
-const {Cc, Ci} = require("chrome");
 loader.lazyRequireGetter(this, "CSS", "CSS");
 const promise = require("promise");
 const {getCSSLexer} = require("devtools/shared/css-lexer");
 const {Task} = require("devtools/shared/task");
-loader.lazyGetter(this, "DOMUtils", () => {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
 
 const SELECTOR_ATTRIBUTE = exports.SELECTOR_ATTRIBUTE = 1;
 const SELECTOR_ELEMENT = exports.SELECTOR_ELEMENT = 2;
 const SELECTOR_PSEUDO_CLASS = exports.SELECTOR_PSEUDO_CLASS = 3;
 
 // Used to test whether a newline appears anywhere in some text.
 const NEWLINE_RX = /[\r\n]/;
 // Used to test whether a bit of text starts an empty comment, either
@@ -146,52 +142,35 @@ function escapeCSSComment(inputString) {
  * @return {String} the un-escaped result
  */
 function unescapeCSSComment(inputString) {
   let result = inputString.replace(/\/\\(\\*)\*/g, "/$1*");
   return result.replace(/\*\\(\\*)\//g, "*$1/");
 }
 
 /**
- * A helper function for parseDeclarations that implements a heuristic
- * to decide whether a given bit of comment text should be parsed as a
- * declaration.
- *
- * @param {String} name the property name that has been parsed
- * @return {Boolean} true if the property should be parsed, false if
- *                        the remainder of the comment should be skipped
- */
-function shouldParsePropertyInComment(name) {
-  try {
-    // If the property name is invalid, the cssPropertyIsShorthand
-    // will throw an exception.  But if it is valid, no exception will
-    // be thrown; so we just ignore the return value.
-    DOMUtils.cssPropertyIsShorthand(name);
-    return true;
-  } catch (e) {
-    return false;
-  }
-}
-
-/**
  * A helper function for @see parseDeclarations that handles parsing
  * of comment text.  This wraps a recursive call to parseDeclarations
  * with the processing needed to ensure that offsets in the result
  * refer back to the original, unescaped, input string.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
  * @param {String} commentText The text of the comment, without the
  *                             delimiters.
  * @param {Number} startOffset The offset of the comment opener
  *                             in the original text.
  * @param {Number} endOffset The offset of the comment closer
  *                           in the original text.
  * @return {array} Array of declarations of the same form as returned
  *                 by parseDeclarations.
  */
-function parseCommentDeclarations(commentText, startOffset, endOffset) {
+function parseCommentDeclarations(isCssPropertyKnown, commentText, startOffset,
+                                  endOffset) {
   let commentOverride = false;
   if (commentText === "") {
     return [];
   } else if (commentText[0] === COMMENT_PARSING_HEURISTIC_BYPASS_CHAR) {
     // This is the special sign that the comment was written by
     // rewriteDeclarations and so we should bypass the usual
     // heuristic.
     commentOverride = true;
@@ -237,18 +216,18 @@ function parseCommentDeclarations(commen
       ++rewrites[i];
     }
   }
 
   // Note that we pass "false" for parseComments here.  It doesn't
   // seem worthwhile to support declarations in comments-in-comments
   // here, as there's no way to generate those using the tools, and
   // users would be crazy to write such things.
-  let newDecls = parseDeclarationsInternal(rewrittenText, false,
-                                           true, commentOverride);
+  let newDecls = parseDeclarationsInternal(isCssPropertyKnown, rewrittenText,
+                                           false, true, commentOverride);
   for (let decl of newDecls) {
     decl.offsets[0] = rewrites[decl.offsets[0]];
     decl.offsets[1] = rewrites[decl.offsets[1]];
     decl.colonOffsets[0] = rewrites[decl.colonOffsets[0]];
     decl.colonOffsets[1] = rewrites[decl.colonOffsets[1]];
     decl.commentOffsets = [startOffset, endOffset];
   }
   return newDecls;
@@ -271,29 +250,31 @@ function getEmptyDeclaration() {
 /**
  * A helper function that does all the parsing work for
  * parseDeclarations.  This is separate because it has some arguments
  * that don't make sense in isolation.
  *
  * The return value and arguments are like parseDeclarations, with
  * these additional arguments.
  *
+ * @param {Function} isCssPropertyKnown
+ *        Function to check if the CSS property is known.
  * @param {Boolean} inComment
  *        If true, assume that this call is parsing some text
  *        which came from a comment in another declaration.
  *        In this case some heuristics are used to avoid parsing
  *        text which isn't obviously a series of declarations.
  * @param {Boolean} commentOverride
  *        This only makes sense when inComment=true.
  *        When true, assume that the comment was generated by
  *        rewriteDeclarations, and skip the usual name-checking
  *        heuristic.
  */
-function parseDeclarationsInternal(inputString, parseComments,
-                                   inComment, commentOverride) {
+function parseDeclarationsInternal(isCssPropertyKnown, inputString,
+                                   parseComments, inComment, commentOverride) {
   if (inputString === null || inputString === undefined) {
     throw new Error("empty input string");
   }
 
   let lexer = getCSSLexer(inputString);
 
   let declarations = [getEmptyDeclaration()];
   let lastProp = declarations[0];
@@ -330,17 +311,17 @@ function parseDeclarationsInternal(input
         lastProp.name = current.trim();
         lastProp.colonOffsets = [token.startOffset, token.endOffset];
         current = "";
         hasBang = false;
 
         // When parsing a comment body, if the left-hand-side is not a
         // valid property name, then drop it and stop parsing.
         if (inComment && !commentOverride &&
-            !shouldParsePropertyInComment(lastProp.name)) {
+            !isCssPropertyKnown(lastProp.name)) {
           lastProp.name = null;
           break;
         }
       } else {
         // Otherwise, just append ':' to the current value (declaration value
         // with colons)
         current += ":";
       }
@@ -373,17 +354,18 @@ function parseDeclarationsInternal(input
     } else if (token.tokenType === "whitespace") {
       if (current !== "") {
         current += " ";
       }
     } else if (token.tokenType === "comment") {
       if (parseComments && !lastProp.name && !lastProp.value) {
         let commentText = inputString.substring(token.startOffset + 2,
                                                 token.endOffset - 2);
-        let newDecls = parseCommentDeclarations(commentText, token.startOffset,
+        let newDecls = parseCommentDeclarations(isCssPropertyKnown, commentText,
+                                                token.startOffset,
                                                 token.endOffset);
 
         // Insert the new declarations just before the final element.
         let lastDecl = declarations.pop();
         declarations = [...declarations, ...newDecls, lastDecl];
       }
     } else {
       current += inputString.substring(token.startOffset, token.endOffset);
@@ -415,23 +397,28 @@ function parseDeclarationsInternal(input
   // Remove declarations that have neither a name nor a value
   declarations = declarations.filter(prop => prop.name || prop.value);
 
   return declarations;
 }
 
 /**
  * Returns an array of CSS declarations given a string.
- * For example, parseDeclarations("width: 1px; height: 1px") would return
+ * For example, parseDeclarations(isCssPropertyKnown, "width: 1px; height: 1px")
+ * would return:
  * [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
  *
  * The input string is assumed to only contain declarations so { and }
  * characters will be treated as part of either the property or value,
  * depending where it's found.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server.
  * @param {String} inputString
  *        An input string of CSS
  * @param {Boolean} parseComments
  *        If true, try to parse the contents of comments as well.
  *        A comment will only be parsed if it occurs outside of
  *        the body of some other declaration.
  * @return {Array} an array of objects with the following signature:
  *         [{"name": string, "value": string, "priority": string,
@@ -445,60 +432,71 @@ function parseDeclarationsInternal(input
  *         usually "" to mean no additional termination is needed.
  *         "colonOffsets" holds the start and end locations of the
  *         ":" that separates the property name from the value.
  *         If the declaration appears in a comment, then there will
  *         be an additional {"commentOffsets": [start, end] property
  *         on the object, which will hold the offsets of the start
  *         and end of the enclosing comment.
  */
-function parseDeclarations(inputString, parseComments = false) {
-  return parseDeclarationsInternal(inputString, parseComments, false, false);
+function parseDeclarations(isCssPropertyKnown, inputString,
+                           parseComments = false) {
+  return parseDeclarationsInternal(isCssPropertyKnown, inputString,
+                                   parseComments, false, false);
 }
 
 /**
  * Return an object that can be used to rewrite declarations in some
  * source text.  The source text and parsing are handled in the same
  * way as @see parseDeclarations, with |parseComments| being true.
  * Rewriting is done by calling one of the modification functions like
  * setPropertyEnabled.  The returned object has the same interface
  * as @see RuleModificationList.
  *
  * An example showing how to disable the 3rd property in a rule:
  *
- *    let rewriter = new RuleRewriter(ruleActor, ruleActor.authoredText);
+ *    let rewriter = new RuleRewriter(isCssPropertyKnown, ruleActor,
+ *                                    ruleActor.authoredText);
  *    rewriter.setPropertyEnabled(3, "color", false);
  *    rewriter.apply().then(() => { ... the change is made ... });
  *
  * The exported rewriting methods are |renameProperty|, |setPropertyEnabled|,
  * |createProperty|, |setProperty|, and |removeProperty|.  The |apply|
  * method can be used to send the edited text to the StyleRuleActor;
  * |getDefaultIndentation| is useful for the methods requiring a
  * default indentation value; and |getResult| is useful for testing.
  *
  * Additionally, editing will set the |changedDeclarations| property
  * on this object.  This property has the same form as the |changed|
  * property of the object returned by |getResult|.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server. Note that if Bug 1222047
+ *        is completed then isCssPropertyKnown will not need to be passed in.
+ *        The CssProperty front will be able to obtained directly from the
+ *        RuleRewriter.
  * @param {StyleRuleFront} rule The style rule to use.  Note that this
  *        is only needed by the |apply| and |getDefaultIndentation| methods;
  *        and in particular for testing it can be |null|.
  * @param {String} inputString The CSS source text to parse and modify.
  * @return {Object} an object that can be used to rewrite the input text.
  */
-function RuleRewriter(rule, inputString) {
+function RuleRewriter(isCssPropertyKnown, rule, inputString) {
   this.rule = rule;
   this.inputString = inputString;
   // Whether there are any newlines in the input text.
   this.hasNewLine = /[\r\n]/.test(this.inputString);
   // Keep track of which any declarations we had to rewrite while
   // performing the requested action.
   this.changedDeclarations = {};
   // The declarations.
-  this.declarations = parseDeclarations(this.inputString, true);
+  this.declarations = parseDeclarations(isCssPropertyKnown, this.inputString,
+                                        true);
 
   this.decl = null;
   this.result = null;
   // If not null, a promise that must be wait upon before |apply| can
   // do its work.
   this.editPromise = null;
 
   // If the |defaultIndentation| property is set, then it is used;
@@ -1077,22 +1075,27 @@ function parsePseudoClassesAndAttributes
 
   return result;
 }
 
 /**
  * Expects a single CSS value to be passed as the input and parses the value
  * and priority.
  *
+ * @param {Function} isCssPropertyKnown
+ *        A function to check if the CSS property is known. This is either an
+ *        internal server function or from the CssPropertiesFront.
+ *        that are supported by the server.
  * @param {String} value
  *        The value from the text editor.
  * @return {Object} an object with 'value' and 'priority' properties.
  */
-function parseSingleValue(value) {
-  let declaration = parseDeclarations("a: " + value + ";")[0];
+function parseSingleValue(isCssPropertyKnown, value) {
+  let declaration = parseDeclarations(isCssPropertyKnown,
+                                      "a: " + value + ";")[0];
   return {
     value: declaration ? declaration.value : "",
     priority: declaration ? declaration.priority : ""
   };
 }
 
 exports.cssTokenizer = cssTokenizer;
 exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/css-properties.js
@@ -0,0 +1,128 @@
+/* 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 strict";
+
+const { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
+const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
+const { Task } = require("devtools/shared/task");
+
+/**
+ * Build up a regular expression that matches a CSS variable token. This is an
+ * ident token that starts with two dashes "--".
+ *
+ * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+ */
+var NON_ASCII = "[^\\x00-\\x7F]";
+var ESCAPE = "\\\\[^\n\r]";
+var FIRST_CHAR = ["[_a-z]", NON_ASCII, ESCAPE].join("|");
+var TRAILING_CHAR = ["[_a-z0-9-]", NON_ASCII, ESCAPE].join("|");
+var IS_VARIABLE_TOKEN = new RegExp(`^--(${FIRST_CHAR})(${TRAILING_CHAR})*$`,
+                                   "i");
+/**
+ * Check that this is a CSS variable.
+ *
+ * @param {String} input
+ * @return {Boolean}
+ */
+function isCssVariable(input) {
+  return !!input.match(IS_VARIABLE_TOKEN);
+}
+
+var cachedCssProperties = new WeakMap();
+
+/**
+ * The CssProperties front provides a mechanism to have a one-time asynchronous
+ * load of a CSS properties database. This is then fed into the CssProperties
+ * interface that provides synchronous methods for finding out what CSS
+ * properties the current server supports.
+ */
+const CssPropertiesFront = FrontClassWithSpec(cssPropertiesSpec, {
+  initialize: function (client, { cssPropertiesActor }) {
+    Front.prototype.initialize.call(this, client, {actor: cssPropertiesActor});
+    this.manage(this);
+  }
+});
+
+exports.CssPropertiesFront = CssPropertiesFront;
+
+/**
+ * Ask questions to a CSS database. This class does not care how the database
+ * gets loaded in, only the questions that you can ask to it.
+ *
+ * @param {array} properties
+ *                A list of all supported CSS properties.
+ */
+function CssProperties(properties) {
+  this.properties = properties;
+  // Bind isKnown so it can be passed around to helper functions.
+  this.isKnown = this.isKnown.bind(this);
+}
+
+CssProperties.prototype = {
+  /**
+   * Checks to see if the property is known by the browser. This function has
+   * `this` already bound so that it can be passed around by reference.
+   *
+   * @param {String}   property
+   *                   The property name to be checked.
+   * @return {Boolean}
+   */
+  isKnown(property) {
+    return this.properties.includes(property) || isCssVariable(property);
+  }
+};
+
+exports.CssProperties = CssProperties;
+
+/**
+ * Create a CssProperties object with a fully loaded CSS database. The
+ * CssProperties interface can be queried synchronously, but the initialization
+ * is potentially async and should be handled up-front when the tool is created.
+ *
+ * The front is returned only with this function so that it can be destroyed
+ * once the toolbox is destroyed.
+ *
+ * @param {Toolbox} The current toolbox.
+ * @returns {Promise} Resolves to {cssProperties, cssPropertiesFront}.
+ */
+exports.initCssProperties = Task.async(function* (toolbox) {
+  let client = toolbox.target.client;
+  if (cachedCssProperties.has(client)) {
+    return cachedCssProperties.get(client);
+  }
+
+  let propertiesList, front;
+
+  // Get the list dynamically if the cssProperties exists.
+  if (toolbox.target.hasActor("cssProperties")) {
+    front = CssPropertiesFront(client, toolbox.target.form);
+    const db = yield front.getCSSDatabase();
+    propertiesList = db.propertiesList;
+  } else {
+    // The target does not support this actor, so require a static list of
+    // supported properties.
+    const db = require("devtools/client/shared/css-properties-db");
+    propertiesList = db.propertiesList;
+  }
+  const cssProperties = new CssProperties(propertiesList);
+  cachedCssProperties.set(client, {cssProperties, front});
+  return {cssProperties, front};
+});
+
+/**
+ * Synchronously get a cached and initialized CssProperties.
+ *
+ * @param {Toolbox} The current toolbox.
+ * @returns {CssProperties}
+ */
+exports.getCssProperties = function (toolbox) {
+  if (!cachedCssProperties.has(toolbox.target.client)) {
+    throw new Error("The CSS database has not been initialized, please make " +
+                    "sure initCssDatabase was called once before for this " +
+                    "toolbox.");
+  }
+  return cachedCssProperties.get(toolbox.target.client).cssProperties;
+};
+
+exports.CssPropertiesFront = CssPropertiesFront;
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -2,14 +2,15 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'addons.js',
     'animation.js',
+    'css-properties.js',
     'highlighters.js',
     'inspector.js',
     'storage.js',
     'styles.js',
     'stylesheets.js'
 )
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -123,20 +123,24 @@ const StyleRuleFront = FrontClassWithSpe
     this._form.column = column;
   }),
 
   /**
    * Return a new RuleModificationList or RuleRewriter for this node.
    * A RuleRewriter will be returned when the rule's canSetRuleText
    * trait is true; otherwise a RuleModificationList will be
    * returned.
+   *
+   * @param {CssPropertiesFront} cssProperties
+   *                             This is needed by the RuleRewriter.
+   * @return {RuleModificationList}
    */
-  startModifyingProperties: function () {
+  startModifyingProperties: function (cssProperties) {
     if (this.canSetRuleText) {
-      return new RuleRewriter(this, this.authoredText);
+      return new RuleRewriter(cssProperties.isKnown, this, this.authoredText);
     }
     return new RuleModificationList(this);
   },
 
   get type() {
     return this._form.type;
   },
   get line() {
@@ -411,9 +415,8 @@ var RuleModificationList = Class({
    * @param {String} value value of the new property
    * @param {String} priority priority of the new property; either
    *                          the empty string or "important"
    */
   createProperty: function () {
     // Nothing.
   },
 });
-
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/css-properties.js
@@ -0,0 +1,19 @@
+/* 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 strict";
+
+const { RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const cssPropertiesSpec = generateActorSpec({
+  typeName: "cssProperties",
+
+  methods: {
+    getCSSDatabase: {
+      request: {},
+      response: RetVal("json"),
+    }
+  }
+});
+
+exports.cssPropertiesSpec = cssPropertiesSpec;
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -2,15 +2,16 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'addons.js',
     'animation.js',
+    'css-properties.js',
     'highlighters.js',
     'inspector.js',
     'storage.js',
     'styleeditor.js',
     'styles.js',
     'stylesheets.js'
 )