Bug 1301078 - correctly disable pasted commented-out properties; r?pbro draft
authorTom Tromey <tom@tromey.com>
Wed, 07 Sep 2016 11:08:12 -0600
changeset 412642 d9ec564cc17386c042d7b05f80177223b15dffe8
parent 411232 80dccdd8c94ae0f47a9c037bc56e4c4ed79ebfbb
child 531036 7af9a985881b79919a1958c19a35738e54ab5589
push id29224
push userbmo:ttromey@mozilla.com
push dateMon, 12 Sep 2016 15:07:23 +0000
reviewerspbro
bugs1301078
milestone51.0a1
Bug 1301078 - correctly disable pasted commented-out properties; r?pbro MozReview-Commit-ID: 97ueaqvYVVN
devtools/client/inspector/rules/models/rule.js
devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js
devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js
devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js
devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js
devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js
devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
devtools/client/shared/test/unit/test_rewriteDeclarations.js
devtools/client/styleeditor/test/browser.ini
devtools/client/styleeditor/test/browser_styleeditor_syncAddProperty.js
devtools/shared/css-parsing-utils.js
devtools/shared/fronts/styles.js
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -184,17 +184,17 @@ Rule.prototype = {
       ind = this.textProps.indexOf(siblingProp) + 1;
       this.textProps.splice(ind, 0, prop);
     } else {
       ind = this.textProps.length;
       this.textProps.push(prop);
     }
 
     this.applyProperties((modifications) => {
-      modifications.createProperty(ind, name, value, priority);
+      modifications.createProperty(ind, name, value, priority, enabled);
       // Now that the rule has been updated, the server might have given us data
       // that changes the state of the property. Update it now.
       prop.updateEditor();
     });
 
     return prop;
   },
 
--- a/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-and-reselect.js
@@ -24,17 +24,17 @@ add_task(function* () {
   checkPropertyOnAllRules(view);
 });
 
 function* setPropertyOnAllRules(view) {
   // Wait for the properties to be properly created on the backend and for the
   // view to be updated.
   let onRefreshed = view.once("ruleview-refreshed");
   for (let rule of view._elementStyle.rules) {
-    rule.editor.addProperty("font-weight", "bold", "");
+    rule.editor.addProperty("font-weight", "bold", "", true);
   }
   yield onRefreshed;
 }
 
 function checkPropertyOnAllRules(view) {
   for (let rule of view._elementStyle.rules) {
     let lastRule = rule.textProps[rule.textProps.length - 1];
 
--- a/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-and-property.js
@@ -15,16 +15,16 @@ add_task(function* () {
 
   info("Adding a new rule for this node and blurring the new selector field");
   yield addNewRuleAndDismissEditor(inspector, view, "#testid", 1);
 
   info("Adding a new property for this rule");
   let ruleEditor = getRuleViewRuleEditor(view, 1);
 
   let onRuleViewChanged = view.once("ruleview-changed");
-  ruleEditor.addProperty("font-weight", "bold", "");
+  ruleEditor.addProperty("font-weight", "bold", "", true);
   yield onRuleViewChanged;
 
   let textProps = ruleEditor.rule.textProps;
   let prop = textProps[textProps.length - 1];
   is(prop.name, "font-weight", "The last property name is font-weight");
   is(prop.value, "bold", "The last property value is bold");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-iframes.js
@@ -42,16 +42,16 @@ add_task(function* () {
  * @param {String} value
  *        The value of the new property.
  */
 function* addNewProperty(view, index, name, value) {
   let idRuleEditor = getRuleViewRuleEditor(view, index);
   info(`Adding new property "${name}: ${value};"`);
 
   let onRuleViewChanged = view.once("ruleview-changed");
-  idRuleEditor.addProperty(name, value, "");
+  idRuleEditor.addProperty(name, value, "", true);
   yield onRuleViewChanged;
 
   let textProps = idRuleEditor.rule.textProps;
   let lastProperty = textProps[textProps.length - 1];
   is(lastProperty.name, name, "Last property has the expected name");
   is(lastProperty.value, value, "Last property has the expected value");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-then-property-edit-selector.js
@@ -34,17 +34,17 @@ add_task(function* () {
   yield selectNode("span", inspector);
 
   info("Check new rule and property exist in the modified element");
   yield checkModifiedElement(view, "span", 1);
 });
 
 function* testAddingProperty(view, index) {
   let ruleEditor = getRuleViewRuleEditor(view, index);
-  ruleEditor.addProperty("font-weight", "bold", "");
+  ruleEditor.addProperty("font-weight", "bold", "", true);
   let textProps = ruleEditor.rule.textProps;
   let lastRule = textProps[textProps.length - 1];
   is(lastRule.name, "font-weight", "Last rule name is font-weight");
   is(lastRule.value, "bold", "Last rule value is bold");
 }
 
 function* testEditSelector(view, name) {
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
--- a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js
@@ -31,17 +31,17 @@ function* testPacman(inspector, view) {
   // Dynamic changes test disabled because of Bug 1050940
   // If this part of the test is ever enabled again, it should be changed to
   // use addProperty (in head.js) and stop using _applyingModifications
 
   // info("Test dynamic changes to keyframe rule for #pacman");
 
   // let defaultView = element.ownerDocument.defaultView;
   // let ruleEditor = view.element.children[5].childNodes[0]._ruleEditor;
-  // ruleEditor.addProperty("opacity", "0");
+  // ruleEditor.addProperty("opacity", "0", true);
 
   // yield ruleEditor._applyingModifications;
   // yield once(element, "animationend");
 
   // is
   // (
   //   convertTextPropsToString(rules.keyframeRules[1].textProps),
   //   "left: 750px; opacity: 0",
--- a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
@@ -65,22 +65,22 @@ function* testTopLeft(inspector, view) {
     })[0]._ruleEditor;
 
   is(convertTextPropsToString(elementFirstLineRule.textProps),
      "color: orange",
      "TopLeft firstLine properties are correct");
 
   let onAdded = view.once("ruleview-changed");
   let firstProp = elementFirstLineRuleView.addProperty("background-color",
-    "rgb(0, 255, 0)", "");
+    "rgb(0, 255, 0)", "", true);
   yield onAdded;
 
   onAdded = view.once("ruleview-changed");
   let secondProp = elementFirstLineRuleView.addProperty("font-style",
-    "italic", "");
+    "italic", "", true);
   yield onAdded;
 
   is(firstProp,
      elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 2],
      "First added property is on back of array");
   is(secondProp,
      elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 1],
      "Second added property is on back of array");
@@ -103,17 +103,17 @@ function* testTopLeft(inspector, view) {
 
   is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
      "rgb(0, 255, 0)", "Added property should have been used.");
   is((yield getComputedStyleProperty(id, null, "text-decoration")),
      "none", "Added property should not apply to element");
 
   onAdded = view.once("ruleview-changed");
   firstProp = elementRuleView.addProperty("background-color",
-                                          "rgb(0, 0, 255)", "");
+                                          "rgb(0, 0, 255)", "", true);
   yield onAdded;
 
   is((yield getComputedStyleProperty(id, null, "background-color")),
      "rgb(0, 0, 255)", "Added property should have been used.");
   is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
      "rgb(0, 255, 0)", "Added prop does not apply to pseudo");
 }
 
--- a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
@@ -27,17 +27,17 @@ add_task(function* () {
   yield testPropertyChange5(inspector, view, "#testid", testActor);
   yield testPropertyChange6(inspector, view, "#testid", testActor);
 });
 
 function* testPropertyChanges(inspector, ruleView) {
   info("Adding a second margin-top value in the element selector");
   let ruleEditor = ruleView._elementStyle.rules[0].editor;
   let onRefreshed = inspector.once("rule-view-refreshed");
-  ruleEditor.addProperty("margin-top", "5px", "");
+  ruleEditor.addProperty("margin-top", "5px", "", true);
   yield onRefreshed;
 
   let rule = ruleView._elementStyle.rules[0];
   validateTextProp(rule.textProps[0], false, "margin-top", "1px",
     "Original margin property active");
 }
 
 function* testPropertyChange0(inspector, ruleView, selector, testActor) {
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -59,33 +59,33 @@ const TEST_DATA = [
     input: "p:v;",
     instruction: {type: "rename", name: "p", newName: "a b", index: 0},
     expected: "a\\ b:v;"
   },
   {
     desc: "simple create",
     input: "",
     instruction: {type: "create", name: "p", value: "v", priority: "important",
-                  index: 0},
+                  index: 0, enabled: true},
     expected: "p: v !important;"
   },
   {
     desc: "create between two properties",
     input: "a:b; e: f;",
     instruction: {type: "create", name: "c", value: "d", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "a:b; c: d;e: f;"
   },
   // "create" is passed the name that the user entered, and must do
   // any escaping necessary to ensure that this is an identifier.
   {
     desc: "create requiring escape",
     input: "",
     instruction: {type: "create", name: "a b", value: "d", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "a\\ b: d;"
   },
   {
     desc: "simple disable",
     input: "p:v;",
     instruction: {type: "enable", name: "p", value: false, index: 0},
     expected: "/*! p:v; */"
   },
@@ -132,95 +132,95 @@ const TEST_DATA = [
     instruction: {type: "enable", name: "color", value: true, index: 0},
     expected: "color:red; color: blue;"
   },
   {
     desc: "create requiring semicolon insertion",
     // Note the lack of a trailing semicolon.
     input: "color: red",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "color: red;a: b;"
   },
 
   // Newline insertion.
   {
     desc: "simple newline insertion",
     input: "\ncolor: red;\n",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "\ncolor: red;\na: b;\n"
   },
   // Newline insertion.
   {
     desc: "semicolon insertion before newline",
     // Note the lack of a trailing semicolon.
     input: "\ncolor: red\n",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "\ncolor: red;\na: b;\n"
   },
   // Newline insertion.
   {
     desc: "newline and semicolon insertion",
     // Note the lack of a trailing semicolon and newline.
     input: "\ncolor: red",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "\ncolor: red;\na: b;\n"
   },
 
   // Newline insertion and indentation.
   {
     desc: "indentation with create",
     input: "\n  color: red;\n",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "\n  color: red;\n  a: b;\n"
   },
   // Newline insertion and indentation.
   {
     desc: "indentation plus semicolon insertion before newline",
     // Note the lack of a trailing semicolon.
     input: "\n  color: red\n",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "\n  color: red;\n  a: b;\n"
   },
   {
     desc: "indentation inserted before trailing whitespace",
     // Note the trailing whitespace.  This could come from a rule
     // like:
     // @supports (mumble) {
     //   body {
     //     color: red;
     //   }
     // }
     // Here if we create a rule we don't want it to follow
     // the indentation of the "}".
     input: "\n    color: red;\n  ",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "\n    color: red;\n    a: b;\n  "
   },
   // Newline insertion and indentation.
   {
     desc: "indentation comes from preceding comment",
     // Note how the comment comes before the declaration.
     input: "\n  /* comment */ color: red\n",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "\n  /* comment */ color: red;\n  a: b;\n"
   },
   // Default indentation.
   {
     desc: "use of default indentation",
     input: "\n",
     instruction: {type: "create", name: "a", value: "b", priority: "",
-                  index: 0},
+                  index: 0, enabled: true},
     expected: "\n\ta: b;\n"
   },
 
   // Deletion handles newlines properly.
   {
     desc: "deletion removes newline",
     input: "a:b;\nc:d;\ne:f;",
     instruction: {type: "remove", name: "c", index: 1},
@@ -270,17 +270,17 @@ const TEST_DATA = [
     expected: "content: 'hi'; color: red;",
     changed: {0: "'hi'"}
   },
   // Termination insertion corner case.
   {
     desc: "create single quote termination",
     input: "content: 'hi",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "content: 'hi';color: red;",
     changed: {0: "'hi'"}
   },
 
   // Termination insertion corner case.
   {
     desc: "enable double quote termination",
     input: "/* content: \"hi */ color: red;",
@@ -288,17 +288,17 @@ const TEST_DATA = [
     expected: "content: \"hi\"; color: red;",
     changed: {0: "\"hi\""}
   },
   // Termination insertion corner case.
   {
     desc: "create double quote termination",
     input: "content: \"hi",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "content: \"hi\";color: red;",
     changed: {0: "\"hi\""}
   },
 
   // Termination insertion corner case.
   {
     desc: "enable url termination",
     input: "/* background-image: url(something.jpg */ color: red;",
@@ -307,17 +307,17 @@ const TEST_DATA = [
     expected: "background-image: url(something.jpg); color: red;",
     changed: {0: "url(something.jpg)"}
   },
   // Termination insertion corner case.
   {
     desc: "create url termination",
     input: "background-image: url(something.jpg",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "background-image: url(something.jpg);color: red;",
     changed: {0: "url(something.jpg)"}
   },
 
   // Termination insertion corner case.
   {
     desc: "enable url single quote termination",
     input: "/* background-image: url('something.jpg */ color: red;",
@@ -326,17 +326,17 @@ const TEST_DATA = [
     expected: "background-image: url('something.jpg'); color: red;",
     changed: {0: "url('something.jpg')"}
   },
   // Termination insertion corner case.
   {
     desc: "create url single quote termination",
     input: "background-image: url('something.jpg",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "background-image: url('something.jpg');color: red;",
     changed: {0: "url('something.jpg')"}
   },
 
   // Termination insertion corner case.
   {
     desc: "create url double quote termination",
     input: "/* background-image: url(\"something.jpg */ color: red;",
@@ -345,74 +345,74 @@ const TEST_DATA = [
     expected: "background-image: url(\"something.jpg\"); color: red;",
     changed: {0: "url(\"something.jpg\")"}
   },
   // Termination insertion corner case.
   {
     desc: "enable url double quote termination",
     input: "background-image: url(\"something.jpg",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "background-image: url(\"something.jpg\");color: red;",
     changed: {0: "url(\"something.jpg\")"}
   },
 
   // Termination insertion corner case.
   {
     desc: "create backslash termination",
     input: "something: \\",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "something: \\\\;color: red;",
     // The lexer rewrites the token before we see it.  However this is
     // so obscure as to be inconsequential.
     changed: {0: "\uFFFD\\"}
   },
 
   // Termination insertion corner case.
   {
     desc: "enable backslash single quote termination",
     input: "something: '\\",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "something: '\\\\';color: red;",
     changed: {0: "'\\\\'"}
   },
   {
     desc: "enable backslash double quote termination",
     input: "something: \"\\",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "something: \"\\\\\";color: red;",
     changed: {0: "\"\\\\\""}
   },
 
   // Termination insertion corner case.
   {
     desc: "enable comment termination",
     input: "something: blah /* comment ",
     instruction: {type: "create", name: "color", value: "red", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "something: blah /* comment*/; color: red;"
   },
 
   // Rewrite a "heuristic override" comment.
   {
     desc: "enable with heuristic override comment",
     input: "/*! walrus: zebra; */",
     instruction: {type: "enable", name: "walrus", value: true, index: 0},
     expected: "walrus: zebra;"
   },
 
   // Sanitize a bad value.
   {
     desc: "create sanitize unpaired brace",
     input: "",
     instruction: {type: "create", name: "p", value: "}", priority: "",
-                  index: 0},
+                  index: 0, enabled: true},
     expected: "p: \\};",
     changed: {0: "\\}"}
   },
   // Sanitize a bad value.
   {
     desc: "set sanitize unpaired brace",
     input: "walrus: zebra;",
     instruction: {type: "set", name: "walrus", value: "{{}}}", priority: "",
@@ -430,20 +430,35 @@ const TEST_DATA = [
   },
 
   // Creating a new declaration does not require an attempt to
   // terminate a previous commented declaration.
   {
     desc: "disabled declaration does not need semicolon insertion",
     input: "/*! no: semicolon */\n",
     instruction: {type: "create", name: "walrus", value: "zebra", priority: "",
-                  index: 1},
+                  index: 1, enabled: true},
     expected: "/*! no: semicolon */\nwalrus: zebra;\n",
     changed: {}
   },
+
+  {
+    desc: "create commented-out property",
+    input: "p: v",
+    instruction: {type: "create", name: "shoveler", value: "duck", priority: "",
+                  index: 1, enabled: false},
+    expected: "p: v;/*! shoveler: duck; */",
+  },
+  {
+    desc: "disabled create with comment ender in string",
+    input: "",
+    instruction: {type: "create", name: "content", value: "'*/'", priority: "",
+                  index: 0, enabled: false},
+    expected: "/*! content: '*\\/'; */"
+  },
 ];
 
 function rewriteDeclarations(inputString, instruction, defaultIndentation) {
   let rewriter = new RuleRewriter(isCssPropertyKnown, null, inputString);
   rewriter.defaultIndentation = defaultIndentation;
 
   switch (instruction.type) {
     case "rename":
@@ -453,17 +468,18 @@ function rewriteDeclarations(inputString
 
     case "enable":
       rewriter.setPropertyEnabled(instruction.index, instruction.name,
                                   instruction.value);
       break;
 
     case "create":
       rewriter.createProperty(instruction.index, instruction.name,
-                              instruction.value, instruction.priority);
+                              instruction.value, instruction.priority,
+                              instruction.enabled);
       break;
 
     case "set":
       rewriter.setProperty(instruction.index, instruction.name,
                            instruction.value, instruction.priority);
       break;
 
     case "remove":
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -90,14 +90,15 @@ skip-if = e10s && debug # Bug 1252201 - 
 [browser_styleeditor_sv_keynav.js]
 [browser_styleeditor_sv_resize.js]
 [browser_styleeditor_selectstylesheet.js]
 [browser_styleeditor_sourcemaps.js]
 [browser_styleeditor_sourcemaps_inline.js]
 [browser_styleeditor_sourcemap_large.js]
 [browser_styleeditor_sourcemap_watching.js]
 [browser_styleeditor_sync.js]
+[browser_styleeditor_syncAddProperty.js]
 [browser_styleeditor_syncAddRule.js]
 [browser_styleeditor_syncAlreadyOpen.js]
 [browser_styleeditor_syncEditSelector.js]
 [browser_styleeditor_syncIntoRuleView.js]
 [browser_styleeditor_transition_rule.js]
 [browser_styleeditor_xul.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/test/browser_styleeditor_syncAddProperty.js
@@ -0,0 +1,45 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that adding a new rule is synced to the style editor.
+
+const TESTCASE_URI = TEST_BASE_HTTP + "sync.html";
+
+const expectedText = `
+  body {
+    border-width: 15px;
+    color: red;
+  }
+
+  #testid {
+    font-size: 4em;
+    /*! background-color: yellow; */
+  }
+  `;
+
+add_task(function* () {
+  yield addTab(TESTCASE_URI);
+  let { inspector, view } = yield openRuleView();
+  yield selectNode("#testid", inspector);
+
+  info("Focusing a new property name in the rule-view");
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let editor = yield focusEditableField(view, ruleEditor.closeBrace);
+  is(inplaceEditor(ruleEditor.newPropSpan), editor,
+    "The new property editor has focus");
+
+  let input = editor.input;
+  input.value = "/* background-color: yellow; */";
+
+  info("Pressing return to commit and focus the new value field");
+  let onModifications = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onModifications;
+
+  let { ui } = yield openStyleEditor();
+  let sourceEditor = yield ui.editors[0].getSourceEditor();
+  let text = sourceEditor.sourceEditor.getText();
+  is(text, expectedText, "selector edits are synced");
+});
--- a/devtools/shared/css-parsing-utils.js
+++ b/devtools/shared/css-parsing-utils.js
@@ -795,20 +795,22 @@ RuleRewriter.prototype = {
    * An internal function to create a new declaration.  This does all
    * the work of |createProperty|.
    *
    * @param {Number} index index of the property in the rule.
    * @param {String} name name of the new property
    * @param {String} value value of the new property
    * @param {String} priority priority of the new property; either
    *                          the empty string or "important"
+   * @param {Boolean} enabled True if the new property should be
+   *                          enabled, false if disabled
    * @return {Promise} a promise that is resolved when the edit has
    *                   completed
    */
-  internalCreateProperty: Task.async(function* (index, name, value, priority) {
+  internalCreateProperty: Task.async(function* (index, name, value, priority, enabled) {
     this.completeInitialization(index);
     let newIndentation = "";
     if (this.hasNewLine) {
       if (this.declarations.length > 0) {
         newIndentation = this.getIndentation(this.inputString,
                                              this.declarations[0].offsets[0]);
       } else if (this.defaultIndentation) {
         newIndentation = this.defaultIndentation;
@@ -828,23 +830,28 @@ RuleRewriter.prototype = {
       let wsOffset = this.skipWhitespaceBackward(this.result,
                                                  this.result.length);
       if (this.result[wsOffset] === "\r" || this.result[wsOffset] === "\n") {
         savedWhitespace = this.result.substring(wsOffset + 1);
         this.result = this.result.substring(0, wsOffset + 1);
       }
     }
 
-    this.result += newIndentation + CSS.escape(name) + ": " +
-      this.sanitizeText(value, index);
-
+    let newText = CSS.escape(name) + ": " + this.sanitizeText(value, index);
     if (priority === "important") {
-      this.result += " !important";
+      newText += " !important";
     }
-    this.result += ";";
+    newText += ";";
+
+    if (!enabled) {
+      newText = "/*" + COMMENT_PARSING_HEURISTIC_BYPASS_CHAR + " " +
+        escapeCSSComment(newText) + " */";
+    }
+
+    this.result += newIndentation + newText;
     if (this.hasNewLine) {
       this.result += "\n";
     }
     this.result += savedWhitespace;
 
     if (this.decl) {
       // Still want to copy in the declaration previously at this
       // index.
@@ -855,20 +862,22 @@ RuleRewriter.prototype = {
   /**
    * Create a new declaration.
    *
    * @param {Number} index index of the property in the rule.
    * @param {String} name name of the new property
    * @param {String} value value of the new property
    * @param {String} priority priority of the new property; either
    *                          the empty string or "important"
+   * @param {Boolean} enabled True if the new property should be
+   *                          enabled, false if disabled
    */
-  createProperty: function (index, name, value, priority) {
+  createProperty: function (index, name, value, priority, enabled) {
     this.editPromise = this.internalCreateProperty(index, name, value,
-                                                   priority);
+                                                   priority, enabled);
   },
 
   /**
    * Set a declaration's value.
    *
    * @param {Number} index index of the property in the rule.
    *                       This can be -1 in the case where
    *                       the rule does not support setRuleText;
@@ -879,17 +888,17 @@ RuleRewriter.prototype = {
    * @param {String} priority the property's priority, either the empty
    *                          string or "important"
    */
   setProperty: function (index, name, value, priority) {
     this.completeInitialization(index);
     // We might see a "set" on a previously non-existent property; in
     // that case, act like "create".
     if (!this.decl) {
-      this.createProperty(index, name, value, priority);
+      this.createProperty(index, name, value, priority, true);
       return;
     }
 
     // Note that this assumes that "set" never operates on disabled
     // properties.
     this.result += this.inputString.substring(this.decl.offsets[0],
                                               this.decl.colonOffsets[1]) +
       this.sanitizeText(value, index);
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -393,27 +393,29 @@ var RuleModificationList = Class({
       this.removeProperty(index, name);
     }
   },
 
   /**
    * Create a new property.  This implementation does nothing, because
    * |setRuleText| is not available.
    *
-   * These parameter are passed, but as they are not used in this
+   * These parameters are passed, but as they are not used in this
    * implementation, they are omitted.  They are documented here as
    * this code also defined the interface implemented by @see
    * RuleRewriter.
    *
    * @param {Number} index index of the property in the rule.
    *                       This can be -1 in the case where
    *                       the rule does not support setRuleText;
    *                       generally for setting properties
    *                       on an element's style.
    * @param {String} name name of the new property
    * @param {String} value value of the new property
    * @param {String} priority priority of the new property; either
    *                          the empty string or "important"
+   * @param {Boolean} enabled True if the new property should be
+   *                          enabled, false if disabled
    */
   createProperty: function () {
     // Nothing.
   },
 });