Bug 722506 - Show imported stylesheets in the style editor, r=harth
authorJ. Ryan Stinnett <jryans@gmail.com>
Sun, 17 Mar 2013 01:54:51 -0500
changeset 125732 d855dc68a2024b64c316cb646950363f898dea72
parent 125731 a4742ca5ca32fcec7abad7518274167e765e15fe
child 125733 7a852243f456045e64fb3d73f05e8bdfd95736a2
push id25087
push usereakhgari@mozilla.com
push dateThu, 21 Mar 2013 12:27:40 +0000
treeherdermozilla-inbound@be6da6dbf632 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersharth
bugs722506
milestone22.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 722506 - Show imported stylesheets in the style editor, r=harth
browser/devtools/styleeditor/StyleEditor.jsm
browser/devtools/styleeditor/StyleEditorChrome.jsm
browser/devtools/styleeditor/test/Makefile.in
browser/devtools/styleeditor/test/browser_styleeditor_import_rule.js
browser/devtools/styleeditor/test/import.css
browser/devtools/styleeditor/test/import.html
browser/devtools/styleeditor/test/import2.css
--- a/browser/devtools/styleeditor/StyleEditor.jsm
+++ b/browser/devtools/styleeditor/StyleEditor.jsm
@@ -117,29 +117,76 @@ StyleEditor.prototype = {
    */
   get styleSheet()
   {
     assert(this._styleSheet, "StyleSheet must be loaded first.");
     return this._styleSheet;
   },
 
   /**
+   * Recursively traverse imported stylesheets to find the index
+   *
+   * @param number aIndex
+   *        The index of the current sheet in the document.
+   * @param CSSStyleSheet aSheet
+   *        A stylesheet we're going to browse to look for all imported sheets.
+   */
+  _getImportedStyleSheetIndex: function SE__getImportedStyleSheetIndex(aIndex, aSheet)
+  {
+    let index = aIndex;
+    for (let j = 0; j < aSheet.cssRules.length; j++) {
+      let rule = aSheet.cssRules.item(j);
+      if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
+        // Associated styleSheet may be null if it has already been seen due to
+        // duplicate @imports for the same URL.
+        if (!rule.styleSheet) {
+          continue;
+        }
+
+        if (rule.styleSheet == this.styleSheet) {
+          this._styleSheetIndex = index;
+          return index;
+        }
+        index++;
+        index = this._getImportedStyleSheetIndex(index, rule.styleSheet);
+
+        if (this._styleSheetIndex != -1) {
+          return index;
+        }
+      } else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
+        // @import rules must precede all others except @charset
+        return index;
+      }
+    }
+    return index;
+  },
+
+  /**
    * Retrieve the index (order) of stylesheet in the document.
    *
    * @return number
    */
   get styleSheetIndex()
   {
     let document = this.contentDocument;
     if (this._styleSheetIndex == -1) {
-      for (let i = 0; i < document.styleSheets.length; i++) {
-        if (document.styleSheets[i] == this.styleSheet) {
-          this._styleSheetIndex = i;
+      let index = 0;
+      let sheetIndex = 0;
+      while (sheetIndex <= document.styleSheets.length) {
+        let sheet = document.styleSheets[sheetIndex];
+        if (sheet == this.styleSheet) {
+          this._styleSheetIndex = index;
           break;
         }
+        index++;
+        index = this._getImportedStyleSheetIndex(index, sheet);
+        if (this._styleSheetIndex != -1) {
+          break;
+        }
+        sheetIndex++;
       }
     }
     return this._styleSheetIndex;
   },
 
   /**
    * Retrieve the input element that handles display and input for this editor.
    * Can be null if the editor is detached/headless, which means that this
--- a/browser/devtools/styleeditor/StyleEditorChrome.jsm
+++ b/browser/devtools/styleeditor/StyleEditorChrome.jsm
@@ -263,32 +263,44 @@ StyleEditorChrome.prototype = {
       let handler = listener["on" + aName];
       if (handler) {
         handler.apply(listener, aArgs);
       }
     }
   },
 
   /**
+   * Create a new style editor, add to the list of editors, and bind this
+   * object as an action listener.
+   * @param DOMDocument aDocument
+   *        The document that the stylesheet is being referenced in.
+   * @param CSSStyleSheet aSheet
+   *        Optional stylesheet to edit from the document.
+   * @return StyleEditor
+   */
+  _createStyleEditor: function SEC__createStyleEditor(aDocument, aSheet) {
+    let editor = new StyleEditor(aDocument, aSheet);
+    this._editors.push(editor);
+    editor.addActionListener(this);
+    return editor;
+  },
+
+  /**
    * Set up the chrome UI. Install event listeners and so on.
    */
   _setupChrome: function SEC__setupChrome()
   {
     // wire up UI elements
     wire(this._view.rootElement, ".style-editor-newButton", function onNewButton() {
-      let editor = new StyleEditor(this.contentDocument);
-      this._editors.push(editor);
-      editor.addActionListener(this);
+      let editor = this._createStyleEditor(this.contentDocument);
       editor.load();
     }.bind(this));
 
     wire(this._view.rootElement, ".style-editor-importButton", function onImportButton() {
-      let editor = new StyleEditor(this.contentDocument);
-      this._editors.push(editor);
-      editor.addActionListener(this);
+      let editor = this._createStyleEditor(this.contentDocument);
       editor.importFromFile(this._mockImportFile || null, this._window);
     }.bind(this));
   },
 
   /**
    * Reset the chrome UI to an empty and ready state.
    */
   resetChrome: function SEC__resetChrome()
@@ -303,34 +315,62 @@ StyleEditorChrome.prototype = {
     // (re)enable UI
     let matches = this._root.querySelectorAll("toolbarbutton,input,select");
     for (let i = 0; i < matches.length; i++) {
       matches[i].removeAttribute("disabled");
     }
   },
 
   /**
+   * Add all imported stylesheets to chrome UI, recursively
+   *
+   * @param CSSStyleSheet aSheet
+   *        A stylesheet we're going to browse to look for all imported sheets.
+   */
+  _showImportedStyleSheets: function SEC__showImportedStyleSheets(aSheet)
+  {
+    let document = this.contentDocument;
+    for (let j = 0; j < aSheet.cssRules.length; j++) {
+      let rule = aSheet.cssRules.item(j);
+      if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
+        // Associated styleSheet may be null if it has already been seen due to
+        // duplicate @imports for the same URL.
+        if (!rule.styleSheet) {
+          continue;
+        }
+
+        this._createStyleEditor(document, rule.styleSheet);
+
+        this._showImportedStyleSheets(rule.styleSheet);
+      } else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
+        // @import rules must precede all others except @charset
+        return;
+      }
+    }
+  },
+
+  /**
    * Populate the chrome UI according to the content document.
    *
    * @see StyleEditor._setupShadowStyleSheet
    */
   _populateChrome: function SEC__populateChrome()
   {
     this.resetChrome();
 
     let document = this.contentDocument;
     this._document.title = _("chromeWindowTitle",
       document.title || document.location.href);
 
     for (let i = 0; i < document.styleSheets.length; i++) {
       let styleSheet = document.styleSheets[i];
 
-      let editor = new StyleEditor(document, styleSheet);
-      editor.addActionListener(this);
-      this._editors.push(editor);
+      this._createStyleEditor(document, styleSheet);
+
+      this._showImportedStyleSheets(styleSheet);
     }
 
     // Queue editors loading so that ContentAttach is consistently triggered
     // right after all editor instances are available (this.editors) but are
     // NOT loaded/ready yet. This also helps responsivity during loading when
     // there are many heavy stylesheets.
     this._editors.forEach(function (aEditor) {
       this._window.setTimeout(aEditor.load.bind(aEditor), 0);
--- a/browser/devtools/styleeditor/test/Makefile.in
+++ b/browser/devtools/styleeditor/test/Makefile.in
@@ -12,32 +12,36 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_TEST_FILES = \
                  browser_styleeditor_enabled.js \
                  browser_styleeditor_filesave.js \
                  browser_styleeditor_cmd_edit.js \
                  browser_styleeditor_cmd_edit.html \
                  browser_styleeditor_import.js \
+                 browser_styleeditor_import_rule.js \
                  browser_styleeditor_init.js \
                  browser_styleeditor_loading.js \
                  browser_styleeditor_new.js \
                  browser_styleeditor_passedinsheet.js \
                  browser_styleeditor_pretty.js \
                  browser_styleeditor_private_perwindowpb.js \
                  browser_styleeditor_readonly.js \
                  browser_styleeditor_reopen.js \
                  browser_styleeditor_sv_keynav.js \
                  browser_styleeditor_sv_resize.js \
                  browser_styleeditor_bug_826982_location_changed.js \
                  head.js \
                  helpers.js \
                  four.html \
                  head.js \
                  helpers.js \
+                 import.css \
+                 import.html \
+                 import2.css \
                  longload.html \
                  media.html \
                  media-small.css \
                  minified.html \
                  resources_inpage.jsi \
                  resources_inpage1.css \
                  resources_inpage2.css \
                  simple.css \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_import_rule.js
@@ -0,0 +1,34 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// http rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTP + "import.html";
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    run(aChrome);
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.editors.length, 3,
+    "there are 3 stylesheets after loading @imports");
+
+  is(aChrome.editors[0]._styleSheet.href, TEST_BASE_HTTP + "simple.css",
+    "stylesheet 1 is simple.css");
+
+  is(aChrome.editors[1]._styleSheet.href, TEST_BASE_HTTP + "import.css",
+    "stylesheet 2 is import.css");
+
+  is(aChrome.editors[2]._styleSheet.href, TEST_BASE_HTTP + "import2.css",
+    "stylesheet 3 is import2.css");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/import.css
@@ -0,0 +1,10 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+@import url(import2.css);
+
+body {
+  margin: 0;
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/import.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<head>
+  <title>import testcase</title>
+  <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/>
+  <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="import.css"/>
+</head>
+<body>
+  <div>import <span>testcase</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/import2.css
@@ -0,0 +1,10 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+@import url(import.css);
+
+p {
+  padding: 5px;
+}
+