Bug 1017248 - Show @media sidebar for original sources in style editor; r=bgrins
authorHeather Arthur <fayearthur@gmail.com>
Thu, 05 Jun 2014 23:02:23 -0700
changeset 207486 8ebe2f070df0a572edeab72c6148e1e93654febb
parent 207485 dc100e436c409c5542d531c11d18229dcc094c9f
child 207487 9e18c67d243e98810236d466548191cdc42f582c
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1017248
milestone32.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 1017248 - Show @media sidebar for original sources in style editor; r=bgrins
browser/devtools/styleeditor/StyleEditorUI.jsm
browser/devtools/styleeditor/StyleSheetEditor.jsm
browser/devtools/styleeditor/test/browser.ini
browser/devtools/styleeditor/test/browser_styleeditor_init.js
browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
browser/devtools/styleeditor/test/browser_styleeditor_reload.js
browser/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js
browser/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
browser/devtools/styleeditor/test/head.js
browser/devtools/styleeditor/test/media-rules-sourcemaps.html
browser/devtools/styleeditor/test/sourcemap-css/media-rules.css
browser/devtools/styleeditor/test/sourcemap-css/media-rules.css.map
browser/devtools/styleeditor/test/sourcemap-sass/media-rules.scss
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -449,36 +449,33 @@ StyleEditorUI.prototype = {
 
         summary.addEventListener("focus", function onSummaryFocus(event) {
           if (event.target == summary) {
             // autofocus the stylesheet name
             summary.querySelector(".stylesheet-name").focus();
           }
         }, false);
 
-        Task.spawn(function* () {
-          // autofocus if it's a new user-created stylesheet
-          if (editor.isNew) {
-            yield this._selectEditor(editor);
-          }
+        // autofocus if it's a new user-created stylesheet
+        if (editor.isNew) {
+          this._selectEditor(editor);
+        }
 
-          if (this._styleSheetToSelect
-              && this._styleSheetToSelect.stylesheet == editor.styleSheet.href) {
-            yield this.switchToSelectedSheet();
-          }
+        if (this._styleSheetToSelect
+            && this._styleSheetToSelect.stylesheet == editor.styleSheet.href) {
+          this.switchToSelectedSheet();
+        }
 
-          // If this is the first stylesheet and there is no pending request to
-          // select a particular style sheet, select this sheet.
-          if (!this.selectedEditor && !this._styleSheetBoundToSelect
-              && editor.styleSheet.styleSheetIndex == 0) {
-            yield this._selectEditor(editor);
-          }
-
-          this.emit("editor-added", editor);
-        }.bind(this)).then(null, Cu.reportError);
+        // If this is the first stylesheet and there is no pending request to
+        // select a particular style sheet, select this sheet.
+        if (!this.selectedEditor && !this._styleSheetBoundToSelect
+            && editor.styleSheet.styleSheetIndex == 0) {
+          this._selectEditor(editor);
+        }
+        this.emit("editor-added", editor);
       }.bind(this),
 
       onShow: function(summary, details, data) {
         let editor = data.editor;
         this.selectedEditor = editor;
 
         Task.spawn(function* () {
           if (!editor.sourceEditor) {
@@ -707,60 +704,84 @@ StyleEditorUI.prototype = {
    * Update the @media rules sidebar for an editor. Hide if there are no rules
    * Display a list of the @media rules in the editor's associated style sheet.
    * Emits a 'media-list-changed' event after updating the UI.
    *
    * @param  {StyleSheetEditor} editor
    *         Editor to update @media sidebar of
    */
   _updateMediaList: function(editor) {
-    this.getEditorDetails(editor).then((details) => {
+    Task.spawn(function* () {
+      let details = yield this.getEditorDetails(editor);
       let list = details.querySelector(".stylesheet-media-list");
 
       while (list.firstChild) {
         list.removeChild(list.firstChild);
       }
 
       let rules = editor.mediaRules;
       let showSidebar = Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR);
       let sidebar = details.querySelector(".stylesheet-sidebar");
-      sidebar.hidden = !showSidebar || !rules.length;
+
+      let inSource = false;
 
       for (let rule of rules) {
+        let {line, column, parentStyleSheet} = rule;
+
+        let location = {
+          line: line,
+          column: column,
+          source: editor.styleSheet.href,
+          styleSheet: parentStyleSheet
+        };
+        if (editor.styleSheet.isOriginalSource) {
+          location = yield editor.cssSheet.getOriginalLocation(line, column);
+        }
+
+        // this @media rule is from a different original source
+        if (location.source != editor.styleSheet.href) {
+          continue;
+        }
+        inSource = true;
+
         let div = this._panelDoc.createElement("div");
         div.className = "media-rule-label";
-        div.addEventListener("click", this._jumpToMediaRule.bind(this, rule));
+        div.addEventListener("click", this._jumpToLocation.bind(this, location));
 
         let cond = this._panelDoc.createElement("div");
         cond.textContent = rule.conditionText;
         cond.className = "media-rule-condition"
         if (!rule.matches) {
           cond.classList.add("media-condition-unmatched");
         }
         div.appendChild(cond);
 
-        let line = this._panelDoc.createElement("div");
-        line.className = "media-rule-line theme-link";
-        line.textContent = ":" + rule.line;
-        div.appendChild(line);
+        let link = this._panelDoc.createElement("div");
+        link.className = "media-rule-line theme-link";
+        link.textContent = ":" + location.line;
+        div.appendChild(link);
 
         list.appendChild(div);
       }
+
+      sidebar.hidden = !showSidebar || !inSource;
+
       this.emit("media-list-changed", editor);
-    });
+    }.bind(this)).then(null, Cu.reportError);
   },
 
   /**
    * Jump cursor to the editor for a stylesheet and line number for a rule.
    *
-   * @param  {MediaRuleFront} rule
-   *         Rule to jump to.
+   * @param  {object} location
+   *         Location object with 'line', 'column', and 'source' properties.
    */
-  _jumpToMediaRule: function(rule) {
-    this.selectStyleSheet(rule.parentStyleSheet, rule.line - 1, rule.column - 1);
+  _jumpToLocation: function(location) {
+    let source = location.styleSheet || location.source;
+    this.selectStyleSheet(source, location.line - 1, location.column - 1);
   },
 
   destroy: function() {
     this._clearStyleSheetEditors();
 
     this._optionsMenu.removeEventListener("popupshowing",
                                           this._updateOptionsMenu);
 
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -91,31 +91,24 @@ function StyleSheetEditor(styleSheet, wi
 
   this._onPropertyChange = this._onPropertyChange.bind(this);
   this._onError = this._onError.bind(this);
   this._onMediaRuleMatchesChange = this._onMediaRuleMatchesChange.bind(this);
   this._onMediaRulesChanged = this._onMediaRulesChanged.bind(this)
   this.checkLinkedFileForChanges = this.checkLinkedFileForChanges.bind(this);
   this.markLinkedFileBroken = this.markLinkedFileBroken.bind(this);
 
+  this._focusOnSourceEditorReady = false;
+  this.cssSheet.on("property-change", this._onPropertyChange);
+  this.styleSheet.on("error", this._onError);
   this.mediaRules = [];
-  if (this.styleSheet.getMediaRules) {
-    this.styleSheet.getMediaRules().then(this._onMediaRulesChanged);
+  if (this.cssSheet.getMediaRules) {
+    this.cssSheet.getMediaRules().then(this._onMediaRulesChanged);
   }
-  this.styleSheet.on("media-rules-changed", this._onMediaRulesChanged);
-
-  this._focusOnSourceEditorReady = false;
-
-  let relatedSheet = this.styleSheet.relatedStyleSheet;
-  if (relatedSheet) {
-    relatedSheet.on("property-change", this._onPropertyChange);
-  }
-  this.styleSheet.on("property-change", this._onPropertyChange);
-  this.styleSheet.on("error", this._onError);
-
+  this.cssSheet.on("media-rules-changed", this._onMediaRulesChanged);
   this.savedFile = file;
   this.linkCSSFile();
 }
 
 StyleSheetEditor.prototype = {
   /**
    * Whether there are unsaved changes in the editor
    */
@@ -126,16 +119,27 @@ StyleSheetEditor.prototype = {
   /**
    * Whether the editor is for a stylesheet created by the user
    * through the style editor UI.
    */
   get isNew() {
     return this._isNew;
   },
 
+  /**
+   * The style sheet or the generated style sheet for this source if it's an
+   * original source.
+   */
+  get cssSheet() {
+    if (this.styleSheet.isOriginalSource) {
+      return this.styleSheet.relatedStyleSheet;
+    }
+    return this.styleSheet;
+  },
+
   get savedFile() {
     return this._savedFile;
   },
 
   set savedFile(name) {
     this._savedFile = name;
 
     this.linkCSSFile();
@@ -525,17 +529,19 @@ StyleSheetEditor.prototype = {
 
   /**
    * Called when this source has been successfully saved to disk.
    */
   onFileSaved: function(returnFile) {
     this._friendlyName = null;
     this.savedFile = returnFile;
 
-    this.sourceEditor.setClean();
+    if (this.sourceEditor) {
+      this.sourceEditor.setClean();
+    }
 
     this.emit("property-change");
 
     // TODO: replace with file watching
     this._modCheckCount = 0;
     this._window.clearTimeout(this._timeout);
 
     if (this.linkedCSSFile && !this.linkedCSSFileError) {
@@ -623,18 +629,18 @@ StyleSheetEditor.prototype = {
 
   /**
    * Clean up for this editor.
    */
   destroy: function() {
     if (this.sourceEditor) {
       this.sourceEditor.destroy();
     }
-    this.styleSheet.off("media-rules-changed", this._onMediaRulesChanged);
-    this.styleSheet.off("property-change", this._onPropertyChange);
+    this.cssSheet.off("property-change", this._onPropertyChange);
+    this.cssSheet.off("media-rules-changed", this._onMediaRulesChanged);
     this.styleSheet.off("error", this._onError);
   }
 }
 
 
 const TAB_CHARS = "\t";
 
 const CURRENT_OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
--- a/browser/devtools/styleeditor/test/browser.ini
+++ b/browser/devtools/styleeditor/test/browser.ini
@@ -11,31 +11,35 @@ support-files =
   import2.css
   inline-1.html
   inline-2.html
   longload.html
   media-small.css
   media.html
   media-rules.html
   media-rules.css
+  media-rules-sourcemaps.html
   minified.html
   nostyle.html
   pretty.css
   resources_inpage.jsi
   resources_inpage1.css
   resources_inpage2.css
   simple.css
   simple.css.gz
   simple.css.gz^headers^
   simple.gz.html
   simple.html
   sourcemap-css/contained.css
   sourcemap-css/sourcemaps.css
   sourcemap-css/sourcemaps.css.map
+  sourcemap-css/media-rules.css
+  sourcemap-css/media-rules.css.map
   sourcemap-sass/sourcemaps.scss
+  sourcemap-sass/media-rules.scss
   sourcemaps.html
   test_private.css
   test_private.html
 
 [browser_styleeditor_autocomplete.js]
 [browser_styleeditor_bug_740541_iframes.js]
 skip-if = os == "linux" || "mac" # bug 949355
 [browser_styleeditor_bug_851132_middle_click.js]
@@ -44,16 +48,17 @@ skip-if = os == "linux" || "mac" # bug 9
 [browser_styleeditor_enabled.js]
 [browser_styleeditor_filesave.js]
 [browser_styleeditor_import.js]
 [browser_styleeditor_import_rule.js]
 [browser_styleeditor_init.js]
 [browser_styleeditor_inline_friendly_names.js]
 [browser_styleeditor_loading.js]
 [browser_styleeditor_media_sidebar.js]
+[browser_styleeditor_media_sidebar_sourcemaps.js]
 [browser_styleeditor_new.js]
 [browser_styleeditor_nostyle.js]
 [browser_styleeditor_pretty.js]
 [browser_styleeditor_private_perwindowpb.js]
 [browser_styleeditor_reload.js]
 [browser_styleeditor_sv_keynav.js]
 [browser_styleeditor_sv_resize.js]
 [browser_styleeditor_selectstylesheet.js]
--- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -15,17 +15,17 @@ function test()
   content.location = TESTCASE_URI;
 }
 
 let gEditorAddedCount = 0;
 function testEditorAdded(aEditor)
 {
   if (aEditor.styleSheet.styleSheetIndex == 0) {
     gEditorAddedCount++;
-    testFirstStyleSheetEditor(aEditor);
+    gUI.editors[0].getSourceEditor().then(testFirstStyleSheetEditor);
   }
   if (aEditor.styleSheet.styleSheetIndex == 1) {
     gEditorAddedCount++;
     testSecondStyleSheetEditor(aEditor);
   }
 
   if (gEditorAddedCount == 2) {
     gUI = null;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
@@ -0,0 +1,72 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules-sourcemaps.html";
+const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar";
+const MAP_PREF = "devtools.styleeditor.source-maps-enabled";
+
+const LABELS = ["screen and (max-width: 320px)",
+                "screen and (min-width: 1200px)"];
+const LINE_NOS = [4, 4];
+
+waitForExplicitFinish();
+
+let test = asyncTest(function*() {
+  Services.prefs.setBoolPref(MEDIA_PREF, true);
+  Services.prefs.setBoolPref(MAP_PREF, true);
+
+  let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+
+  yield listenForMediaChange(UI);
+
+  is(UI.editors.length, 1, "correct number of editors");
+
+  // Test editor with @media rules
+  let mediaEditor = UI.editors[0];
+  yield openEditor(mediaEditor);
+  testMediaEditor(mediaEditor);
+
+  Services.prefs.clearUserPref(MEDIA_PREF);
+  Services.prefs.clearUserPref(MAP_PREF);
+});
+
+function testMediaEditor(editor) {
+  let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+  is(sidebar.hidden, false, "sidebar is showing on editor with @media");
+
+  let entries = [...sidebar.querySelectorAll(".media-rule-label")];
+  is(entries.length, 2, "two @media rules displayed in sidebar");
+
+  testRule(entries[0], LABELS[0], LINE_NOS[0]);
+  testRule(entries[1], LABELS[1], LINE_NOS[1]);
+}
+
+function testRule(rule, text, lineno) {
+  let cond = rule.querySelector(".media-rule-condition");
+  is(cond.textContent, text, "media label is correct for " + text);
+
+  let line = rule.querySelector(".media-rule-line");
+  is(line.textContent, ":" + lineno, "correct line number shown");
+}
+
+/* Helpers */
+
+function openEditor(editor) {
+  getLinkFor(editor).click();
+
+  return editor.getSourceEditor();
+}
+
+function listenForMediaChange(UI) {
+  let deferred = promise.defer();
+  UI.once("media-list-changed", () => {
+    deferred.resolve();
+  })
+  return deferred.promise;
+}
+
+function getLinkFor(editor) {
+  return editor.summary.querySelector(".stylesheet-name");
+}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_reload.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_reload.js
@@ -22,31 +22,35 @@ function test()
   });
 
   content.location = TESTCASE_URI;
 }
 
 function runTests()
 {
   let count = 0;
-  gUI.once("editor-selected", (event, editor) => {
+  gUI.on("editor-selected", function editorSelected(event, editor) {
+    if (editor.styleSheet != gUI.editors[1].styleSheet) {
+      return;
+    }
+    gUI.off("editor-selected", editorSelected);
     editor.getSourceEditor().then(() => {
       info("selected second editor, about to reload page");
       reloadPage();
 
       gUI.on("editor-added", function editorAdded(event, editor) {
         if (++count == 2) {
           info("all editors added after reload");
           gUI.off("editor-added", editorAdded);
           gUI.editors[1].getSourceEditor().then(testRemembered);
         }
       })
     });
   });
-  gUI.selectStyleSheet(gUI.editors[1].styleSheet.href, LINE_NO, COL_NO);
+  gUI.selectStyleSheet(gUI.editors[1].styleSheet, LINE_NO, COL_NO);
 }
 
 function testRemembered()
 {
   is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
 
   let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
   is(line, LINE_NO, "correct line selected");
--- a/browser/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js
@@ -25,17 +25,22 @@ function test()
 }
 
 function runTests()
 {
   let count = 0;
 
   // Make sure Editor doesn't go into an infinite loop when
   // column isn't passed. See bug 941018.
-  gUI.once("editor-selected", (event, editor) => {
+  gUI.on("editor-selected", function editorSelected(event, editor) {
+    if (editor.styleSheet != gUI.editors[1].styleSheet) {
+      return;
+    }
+    gUI.off("editor-selected", editorSelected);
+
     editor.getSourceEditor().then(() => {
       is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
       let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
 
       is(line, LINE_NO, "correct line selected");
       is(ch, COL_NO, "correct column selected");
 
       gUI = null;
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
@@ -99,19 +99,23 @@ function togglePref(UI) {
   let deferred = promise.defer();
   let count = 0;
 
   UI.on("editor-added", (event, editor) => {
     if (++count == 3) {
       deferred.resolve();
     }
   })
+  let editorsPromise = deferred.promise;
+
+  let selectedPromise = UI.once("editor-selected");
 
   Services.prefs.setBoolPref(PREF, false);
-  return deferred.promise;
+
+  return promise.all([editorsPromise, selectedPromise]);
 }
 
 function openEditor(editor) {
   getLinkFor(editor).click();
 
   return editor.getSourceEditor();
 }
 
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -40,19 +40,20 @@ function cleanup()
     gBrowser.removeCurrentTab();
   }
 }
 
 function addTabAndOpenStyleEditors(count, callback, uri) {
   let deferred = promise.defer();
   let currentCount = 0;
   let panel;
-  addTabAndCheckOnStyleEditorAdded(p => panel = p, function () {
+  addTabAndCheckOnStyleEditorAdded(p => panel = p, function (editor) {
     currentCount++;
-    info(currentCount + " of " + count + " editors opened");
+    info(currentCount + " of " + count + " editors opened: "
+         + editor.styleSheet.href);
     if (currentCount == count) {
       if (callback) {
         callback(panel);
       }
       deferred.resolve(panel);
     }
   });
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/media-rules-sourcemaps.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <link rel="stylesheet" type="text/css" href="sourcemap-css/media-rules.css"
+</head>
+<body>
+  <div>
+    Testing style editor media sidebar with source maps
+  </div>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/sourcemap-css/media-rules.css
@@ -0,0 +1,8 @@
+@media screen and (max-width: 320px) {
+  div {
+    width: 100px; } }
+@media screen and (min-width: 1200px) {
+  div {
+    width: 400px; } }
+
+/*# sourceMappingURL=media-rules.css.map */
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/sourcemap-css/media-rules.css.map
@@ -0,0 +1,6 @@
+{
+"version": 3,
+"mappings": "AAIE,oCAA4C;EAD9C,GAAI;IAEA,KAAK,EAAE,KAAK;AAEd,qCAA4C;EAJ9C,GAAI;IAKA,KAAK,EAAE,KAAK",
+"sources": ["../sourcemap-sass/media-rules.scss"],
+"file": "media-rules.css"
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/sourcemap-sass/media-rules.scss
@@ -0,0 +1,11 @@
+$break-small: 320px;
+$break-large: 1200px;
+
+div {
+  @media screen and (max-width: $break-small) {
+    width: 100px;
+  }
+  @media screen and (min-width: $break-large) {
+    width: 400px;
+  }
+}