Bug 1221297 - update line numbers for nested rules in rule view; r=pbrosset, a=ritu
authorTom Tromey <tromey@mozilla.com>
Fri, 06 Nov 2015 11:00:00 +0100
changeset 305488 0a67e975146984be6d0788d03d02471da9bf4b67
parent 305487 72fa0210971401197c1132a5f29a7c1a56c06bf7
child 305489 dd19fd9498a2bc8081113e400548cc3eeda69432
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspbrosset, ritu
bugs1221297
milestone44.0a2
Bug 1221297 - update line numbers for nested rules in rule view; r=pbrosset, a=ritu
devtools/client/styleinspector/test/browser.ini
devtools/client/styleinspector/test/browser_ruleview_keyframeLineNumbers.js
devtools/client/styleinspector/test/doc_keyframeLineNumbers.html
devtools/server/actors/styles.js
--- a/devtools/client/styleinspector/test/browser.ini
+++ b/devtools/client/styleinspector/test/browser.ini
@@ -11,16 +11,17 @@ support-files =
   doc_content_stylesheet_xul.css
   doc_copystyles.css
   doc_copystyles.html
   doc_custom.html
   doc_filter.html
   doc_frame_script.js
   doc_keyframeanimation.html
   doc_keyframeanimation.css
+  doc_keyframeLineNumbers.html
   doc_matched_selectors.html
   doc_media_queries.html
   doc_pseudoelement.html
   doc_ruleLineNumbers.html
   doc_sourcemaps.css
   doc_sourcemaps.css.map
   doc_sourcemaps.html
   doc_sourcemaps.scss
@@ -124,16 +125,17 @@ skip-if = e10s # Bug 1039528: "inspect e
 skip-if = (os == "win" && debug) || e10s # bug 963492: win. bug 1040653: e10s.
 [browser_ruleview_guessIndentation.js]
 [browser_ruleview_inherited-properties_01.js]
 [browser_ruleview_inherited-properties_02.js]
 [browser_ruleview_inherited-properties_03.js]
 [browser_ruleview_keybindings.js]
 [browser_ruleview_keyframes-rule_01.js]
 [browser_ruleview_keyframes-rule_02.js]
+[browser_ruleview_keyframeLineNumbers.js]
 [browser_ruleview_lineNumbers.js]
 [browser_ruleview_livepreview.js]
 [browser_ruleview_mark_overridden_01.js]
 [browser_ruleview_mark_overridden_02.js]
 [browser_ruleview_mark_overridden_03.js]
 [browser_ruleview_mark_overridden_04.js]
 [browser_ruleview_mark_overridden_05.js]
 [browser_ruleview_mark_overridden_06.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleinspector/test/browser_ruleview_keyframeLineNumbers.js
@@ -0,0 +1,28 @@
+/* vim: set ft=javascript 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 editing a rule will update the line numbers of subsequent
+// rules in the rule view.
+
+const TESTCASE_URI = TEST_URL_ROOT + "doc_keyframeLineNumbers.html";
+
+add_task(function*() {
+  yield addTab(TESTCASE_URI);
+  let { inspector, view } = yield openRuleView();
+  yield selectNode("#outer", inspector);
+
+  // Insert a new property, which will affect the line numbers.
+  let elementRuleEditor = getRuleViewRuleEditor(view, 1);
+  yield createNewRuleViewProperty(elementRuleEditor, "font-size: 72px");
+
+  let onRefresh = view.once("ruleview-refreshed");
+  yield selectNode("#inner", inspector);
+  yield onRefresh;
+
+  let value = getRuleViewLinkTextByIndex(view, 3);
+  // Note that this is relative to the <style>.
+  is(value.slice(-3), ":27", "rule line number is 27");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleinspector/test/doc_keyframeLineNumbers.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>keyframe line numbers test</title>
+  <style type="text/css">
+div {
+  animation-duration: 1s;
+  animation-iteration-count: infinite;
+  animation-direction: alternate;
+  animation-name: CC;
+}
+
+span {
+  animation-duration: 3s;
+  animation-iteration-count: infinite;
+  animation-direction: alternate;
+  animation-name: DD;
+}
+
+@keyframes CC {
+  from {
+    background: #ffffff;
+  }
+  to {
+    background: #f0c;
+  }
+}
+
+@keyframes DD {
+  from {
+    background: seagreen;
+  }
+  to {
+    background: chartreuse;
+  }
+}
+  </style>
+</head>
+<body>
+  <div id="outer">
+    <span id="inner">lizards</div>
+  </div>
+</body>
+</html>
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -208,16 +208,19 @@ var PageStyleActor = protocol.ActorClass
       }
     };
   },
 
   /**
    * Called when a style sheet is updated.
    */
   _styleApplied: function(kind, styleSheet) {
+    // No matter what kind of update is done, we need to invalidate
+    // the keyframe cache.
+    this.cssLogic.reset();
     if (kind === UPDATE_GENERAL) {
       events.emit(this, "stylesheet-updated", styleSheet);
     }
   },
 
   /**
    * Return or create a StyleRuleActor for the given item.
    * @param item Either a CSSStyleRule or a DOM element.
@@ -1103,18 +1106,18 @@ var StyleRuleActor = protocol.ActorClass
     this.pageStyle = pageStyle;
     this.rawStyle = item.style;
     this._parentSheet = null;
     this._onStyleApplied = this._onStyleApplied.bind(this);
 
     if (item instanceof (Ci.nsIDOMCSSRule)) {
       this.type = item.type;
       this.rawRule = item;
-      if ((this.rawRule instanceof Ci.nsIDOMCSSStyleRule ||
-           this.rawRule instanceof Ci.nsIDOMMozCSSKeyframeRule) &&
+      if ((this.type === Ci.nsIDOMCSSRule.STYLE_RULE ||
+           this.type === Ci.nsIDOMCSSRule.KEYFRAME_RULE) &&
           this.rawRule.parentStyleSheet) {
         this.line = DOMUtils.getRelativeRuleLine(this.rawRule);
         this.column = DOMUtils.getRuleColumn(this.rawRule);
         this._parentSheet = this.rawRule.parentStyleSheet;
         this._computeRuleIndex();
         this.sheetActor = this.pageStyle._sheetRef(this._parentSheet);
         this.sheetActor.on("style-applied", this._onStyleApplied);
       }
@@ -1267,47 +1270,90 @@ var StyleRuleActor = protocol.ActorClass
    * @param {Number} column the new column number
    */
   _notifyLocationChanged: function(line, column) {
     events.emit(this, "location-changed", line, column);
   },
 
   /**
    * Compute the index of this actor's raw rule in its parent style
-   * sheet.
+   * sheet.  The index is a vector where each element is the index of
+   * a given CSS rule in its parent.  A vector is used to support
+   * nested rules.
    */
   _computeRuleIndex: function() {
     let rule = this.rawRule;
-    let cssRules = this._parentSheet.cssRules;
-    this._ruleIndex = -1;
-    for (let i = 0; i < cssRules.length; i++) {
-      if (rule === cssRules.item(i)) {
-        this._ruleIndex = i;
-        break;
+    let result = [];
+
+    while (rule) {
+      let cssRules;
+      if (rule.parentRule) {
+        cssRules = rule.parentRule.cssRules;
+      } else {
+        cssRules = rule.parentStyleSheet.cssRules;
+      }
+
+      let found = false;
+      for (let i = 0; i < cssRules.length; i++) {
+        if (rule === cssRules.item(i)) {
+          found = true;
+          result.unshift(i);
+          break;
+        }
+      }
+
+      if (!found) {
+        this._ruleIndex = null;
+        return;
+      }
+
+      rule = rule.parentRule;
+    }
+
+    this._ruleIndex = result;
+  },
+
+  /**
+   * Get the rule corresponding to |this._ruleIndex| from the given
+   * style sheet.
+   *
+   * @param  {DOMStyleSheet} sheet
+   *         The style sheet.
+   * @return {CSSStyleRule} the rule corresponding to
+   * |this._ruleIndex|
+   */
+  _getRuleFromIndex: function(parentSheet) {
+    let currentRule = null;
+    for (let i of this._ruleIndex) {
+      if (currentRule === null) {
+        currentRule = parentSheet.cssRules[i];
+      } else {
+        currentRule = currentRule.cssRules.item(i);
       }
     }
+    return currentRule;
   },
 
   /**
    * This is attached to the parent style sheet actor's
    * "style-applied" event.
    */
   _onStyleApplied: function(kind) {
     if (kind === UPDATE_GENERAL) {
       // A general change means that the rule actors are invalidated,
       // so stop listening to events now.
       if (this.sheetActor) {
         this.sheetActor.off("style-applied", this._onStyleApplied);
       }
-    } else if (this._ruleIndex >= 0) {
+    } else if (this._ruleIndex) {
       // The sheet was updated by this actor, in a way that preserves
       // the rules.  Now, recompute our new rule from the style sheet,
       // so that we aren't left with a reference to a dangling rule.
       let oldRule = this.rawRule;
-      this.rawRule = this._parentSheet.cssRules[this._ruleIndex];
+      this.rawRule = this._getRuleFromIndex(this._parentSheet);
       // Also tell the page style so that future calls to _styleRef
       // return the same StyleRuleActor.
       this.pageStyle.updateStyleRef(oldRule, this.rawRule, this);
       let line = DOMUtils.getRelativeRuleLine(this.rawRule);
       let column = DOMUtils.getRuleColumn(this.rawRule);
       if (line !== this.line || column !== this.column) {
         this._notifyLocationChanged(line, column);
       }
@@ -1483,17 +1529,17 @@ var StyleRuleActor = protocol.ActorClass
           } catch(e) {
             // The selector could be invalid, or the rule could fail to insert.
             return null;
           }
         }
       }
     }
 
-    return parentStyleSheet.cssRules[this._ruleIndex];
+    return this._getRuleFromIndex(parentStyleSheet);
   }),
 
   /**
    * Modify the current rule's selector by inserting a new rule with the new
    * selector value and removing the current rule.
    *
    * Note this method was kept for backward compatibility, but unmatched rules
    * support was added in FF41.