Bug 930928 - Shader compilation errors should be displayed in the editor, r=rcampbell,anton
authorVictor Porof <vporof@mozilla.com>
Tue, 05 Nov 2013 14:07:37 +0200
changeset 153639 cdafebd0eb7575e2f4b811fee4929c0db18889bc
parent 153638 64e809f87e60868a26a362bd93f2c21a3fb8c957
child 153640 20fb98a374ce044f63546c63c47ced9c10bd0317
push id35853
push userryanvm@gmail.com
push dateTue, 05 Nov 2013 20:37:09 +0000
treeherdermozilla-inbound@e3f9ccba044e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell, anton
bugs930928
milestone28.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 930928 - Shader compilation errors should be displayed in the editor, r=rcampbell,anton
browser/devtools/scratchpad/scratchpad.js
browser/devtools/shadereditor/shadereditor.js
browser/devtools/shadereditor/test/browser.ini
browser/devtools/shadereditor/test/browser_se_editors-error-gutter.js
browser/devtools/shadereditor/test/browser_se_editors-error-tooltip.js
browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js
browser/devtools/shared/widgets/Tooltip.js
browser/devtools/sourceeditor/codemirror/mozilla.css
browser/devtools/sourceeditor/debugger.js
browser/devtools/sourceeditor/editor.js
browser/devtools/styleeditor/StyleSheetEditor.jsm
browser/themes/linux/devtools/orion-error.png
browser/themes/linux/jar.mn
browser/themes/osx/devtools/orion-error.png
browser/themes/osx/jar.mn
browser/themes/shared/devtools/common.inc.css
browser/themes/windows/devtools/orion-error.png
browser/themes/windows/jar.mn
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -154,17 +154,17 @@ var Scratchpad = {
 
   /**
    * Sets the 'dirty' state of this Scratchpad.
    */
   set dirty(aValue)
   {
     this._dirty = aValue;
     if (!aValue && this.editor)
-      this.editor.markClean();
+      this.editor.setClean();
     this._updateTitle();
   },
 
   /**
    * Retrieve the xul:notificationbox DOM element. It notifies the user when
    * the current code execution context is SCRATCHPAD_CONTEXT_BROWSER.
    */
   get notificationBox()
--- a/browser/devtools/shadereditor/shadereditor.js
+++ b/browser/devtools/shadereditor/shadereditor.js
@@ -10,37 +10,41 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const promise = require("sdk/core/promise");
 const EventEmitter = require("devtools/shared/event-emitter");
+const {Tooltip} = require("devtools/shared/widgets/Tooltip");
 const Editor = require("devtools/sourceeditor/editor");
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
   // When new programs are received from the server.
   NEW_PROGRAM: "ShaderEditor:NewProgram",
   PROGRAMS_ADDED: "ShaderEditor:ProgramsAdded",
 
   // When the vertex and fragment sources were shown in the editor.
   SOURCES_SHOWN: "ShaderEditor:SourcesShown",
 
   // When a shader's source was edited and compiled via the editor.
   SHADER_COMPILED: "ShaderEditor:ShaderCompiled"
 };
 
 const STRINGS_URI = "chrome://browser/locale/devtools/shadereditor.properties"
-const HIGHLIGHT_COLOR = [1, 0, 0, 1];
-const TYPING_MAX_DELAY = 500;
+const HIGHLIGHT_COLOR = [1, 0, 0, 1]; // rgba
+const TYPING_MAX_DELAY = 500; // ms
 const SHADERS_AUTOGROW_ITEMS = 4;
+const GUTTER_ERROR_PANEL_OFFSET_X = 7; // px
+const GUTTER_ERROR_PANEL_DELAY = 100; // ms
 const DEFAULT_EDITOR_CONFIG = {
   mode: Editor.modes.text,
+  gutters: ["errors"],
   lineNumbers: true,
   showAnnotationRuler: true
 };
 
 /**
  * The current target and the WebGL Editor front, set by this tool's host.
  */
 let gToolbox, gTarget, gFront;
@@ -421,39 +425,146 @@ let ShadersEditorsView = {
   /**
    * The change listener for a source editor.
    *
    * @param string type
    *        The corresponding shader type for the focused editor (e.g. "vs").
    */
   _onChanged: function(type) {
     setNamedTimeout("gl-typed", TYPING_MAX_DELAY, () => this._doCompile(type));
+
+    // Remove all the gutter markers and line classes from the editor.
+    this._cleanEditor(type);
   },
 
   /**
    * Recompiles the source code for the shader being edited.
    * This function is fired at a certain delay after the user stops typing.
    *
    * @param string type
    *        The corresponding shader type for the focused editor (e.g. "vs").
    */
   _doCompile: function(type) {
     Task.spawn(function() {
       let editor = yield this._getEditor(type);
       let shaderActor = yield ShadersListView.selectedAttachment[type];
 
       try {
         yield shaderActor.compile(editor.getText());
-        window.emit(EVENTS.SHADER_COMPILED, null);
-        // TODO: remove error gutter markers, after bug 919709 lands.
-      } catch (error) {
-        window.emit(EVENTS.SHADER_COMPILED, error);
-        // TODO: add error gutter markers, after bug 919709 lands.
+        this._onSuccessfulCompilation();
+      } catch (e) {
+        this._onFailedCompilation(type, editor, e);
       }
     }.bind(this));
+  },
+
+  /**
+   * Called uppon a successful shader compilation.
+   */
+  _onSuccessfulCompilation: function() {
+    // Signal that the shader was compiled successfully.
+    window.emit(EVENTS.SHADER_COMPILED, null);
+  },
+
+  /**
+   * Called uppon an unsuccessful shader compilation.
+   */
+  _onFailedCompilation: function(type, editor, errors) {
+    let lineCount = editor.lineCount();
+    let currentLine = editor.getCursor().line;
+    let listeners = { mouseenter: this._onMarkerMouseEnter };
+
+    function matchLinesAndMessages(string) {
+      return {
+        // First number that is not equal to 0.
+        lineMatch: string.match(/\d{2,}|[1-9]/),
+        // The string after all the numbers, semicolons and spaces.
+        textMatch: string.match(/[^\s\d:][^\r\n|]*/)
+      };
+    }
+    function discardInvalidMatches(e) {
+      // Discard empty line and text matches.
+      return e.lineMatch && e.textMatch;
+    }
+    function sanitizeValidMatches(e) {
+      return {
+        // Drivers might yield retarded line numbers under some obscure
+        // circumstances. Don't throw the errors away in those cases,
+        // just display them on the currently edited line.
+        line: e.lineMatch[0] > lineCount ? currentLine : e.lineMatch[0] - 1,
+        // Trim whitespace from the beginning and the end of the message,
+        // and replace all other occurences of double spaces to a single space.
+        text: e.textMatch[0].trim().replace(/\s{2,}/g, " ")
+      };
+    }
+    function sortByLine(first, second) {
+      // Sort all the errors ascending by their corresponding line number.
+      return first.line > second.line ? 1 : -1;
+    }
+    function groupSameLineMessages(accumulator, current) {
+      // Group errors corresponding to the same line number to a single object.
+      let previous = accumulator[accumulator.length - 1];
+      if (!previous || previous.line != current.line) {
+        return [...accumulator, {
+          line: current.line,
+          messages: [current.text]
+        }];
+      } else {
+        previous.messages.push(current.text);
+        return accumulator;
+      }
+    }
+    function displayErrors({ line, messages }) {
+      // Add gutter markers and line classes for every error in the source.
+      editor.addMarker(line, "errors", "error");
+      editor.setMarkerListeners(line, "errors", "error", listeners, messages);
+      editor.addLineClass(line, "error-line");
+    }
+
+    (this._errors[type] = errors.link
+      .split("ERROR")
+      .map(matchLinesAndMessages)
+      .filter(discardInvalidMatches)
+      .map(sanitizeValidMatches)
+      .sort(sortByLine)
+      .reduce(groupSameLineMessages, []))
+      .forEach(displayErrors);
+
+    // Signal that the shader wasn't compiled successfully.
+    window.emit(EVENTS.SHADER_COMPILED, errors);
+  },
+
+  /**
+   * Event listener for the 'mouseenter' event on a marker in the editor gutter.
+   */
+  _onMarkerMouseEnter: function(line, node, messages) {
+    if (node._markerErrorsTooltip) {
+      return;
+    }
+
+    let tooltip = node._markerErrorsTooltip = new Tooltip(document);
+    tooltip.defaultOffsetX = GUTTER_ERROR_PANEL_OFFSET_X;
+    tooltip.setTextContent.apply(tooltip, messages);
+    tooltip.startTogglingOnHover(node, () => true, GUTTER_ERROR_PANEL_DELAY);
+  },
+
+  /**
+   * Removes all the gutter markers and line classes from the editor.
+   */
+  _cleanEditor: function(type) {
+    this._getEditor(type).then(editor => {
+      editor.removeAllMarkers("errors");
+      this._errors[type].forEach(e => editor.removeLineClass(e.line));
+      this._errors[type].length = 0;
+    });
+  },
+
+  _errors: {
+    vs: [],
+    fs: []
   }
 };
 
 /**
  * Localization convenience methods.
  */
 let L10N = new ViewHelpers.L10N(STRINGS_URI);
 
--- a/browser/devtools/shadereditor/test/browser.ini
+++ b/browser/devtools/shadereditor/test/browser.ini
@@ -4,16 +4,18 @@ support-files =
   doc_overlapping-geometry.html
   doc_shader-order.html
   doc_simple-canvas.html
   head.js
 
 [browser_se_aaa_run_first_leaktest.js]
 [browser_se_bfcache.js]
 [browser_se_editors-contents.js]
+[browser_se_editors-error-gutter.js]
+[browser_se_editors-error-tooltip.js]
 [browser_se_editors-lazy-init.js]
 [browser_se_first-run.js]
 [browser_se_navigation.js]
 [browser_se_programs-blackbox.js]
 [browser_se_programs-cache.js]
 [browser_se_programs-highlight.js]
 [browser_se_programs-list.js]
 [browser_se_shaders-edit-01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_editors-error-gutter.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if error indicators are shown in the editor's gutter and text area
+ * when there's a shader compilation error.
+ */
+
+function ifWebGLSupported() {
+  let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+  let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+  reload(target);
+  yield once(gFront, "program-linked");
+
+  let vsEditor = yield ShadersEditorsView._getEditor("vs");
+  let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+  vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
+  let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  checkHasVertFirstError(true, vertError);
+  checkHasVertSecondError(false, vertError);
+  info("Error marks added in the vertex shader editor.");
+
+  vsEditor.insertText(" ", { line: 1, ch: 0 });
+  is(vsEditor.getText(1), "       precision lowp float;", "Typed space.");
+  checkHasVertFirstError(false, vertError);
+  checkHasVertSecondError(false, vertError);
+  info("Error marks removed while typing in the vertex shader editor.");
+
+  let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  checkHasVertFirstError(true, vertError);
+  checkHasVertSecondError(false, vertError);
+  info("Error marks were re-added after recompiling the vertex shader.");
+
+  fsEditor.replaceText("vec4", { line: 2, ch: 14 }, { line: 2, ch: 18 });
+  let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  checkHasVertFirstError(true, vertError);
+  checkHasVertSecondError(false, vertError);
+  checkHasFragError(true, fragError);
+  info("Error marks added in the fragment shader editor.");
+
+  fsEditor.insertText(" ", { line: 1, ch: 0 });
+  is(fsEditor.getText(1), "       precision lowp float;", "Typed space.");
+  checkHasVertFirstError(true, vertError);
+  checkHasVertSecondError(false, vertError);
+  checkHasFragError(false, fragError);
+  info("Error marks removed while typing in the fragment shader editor.");
+
+  let fragError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  checkHasVertFirstError(true, vertError);
+  checkHasVertSecondError(false, vertError);
+  checkHasFragError(true, fragError);
+  info("Error marks were re-added after recompiling the fragment shader.");
+
+  vsEditor.replaceText("2", { line: 3, ch: 19 }, { line: 3, ch: 20 });
+  checkHasVertFirstError(false, vertError);
+  checkHasVertSecondError(false, vertError);
+  checkHasFragError(true, fragError);
+  info("Error marks removed while typing in the vertex shader editor again.");
+
+  let vertError = yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+  checkHasVertFirstError(true, vertError);
+  checkHasVertSecondError(true, vertError);
+  checkHasFragError(true, fragError);
+  info("Error marks were re-added after recompiling the fragment shader again.");
+
+  yield teardown(panel);
+  finish();
+
+  function checkHasVertFirstError(bool, error) {
+    ok(error, "Vertex shader compiled with errors.");
+    isnot(error.link, "", "The linkage status should not be empty.");
+
+    let line = 7;
+    info("Checking first vertex shader error on line " + line + "...");
+
+    is(vsEditor.hasMarker(line, "errors", "error"), bool,
+      "Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
+    is(vsEditor.hasLineClass(line, "error-line"), bool,
+      "Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
+
+    let parsed = ShadersEditorsView._errors.vs;
+    is(parsed.length >= 1, bool,
+      "There's " + (bool ? ">= 1" : "< 1") + " parsed vertex shader error(s).");
+
+    if (bool) {
+      is(parsed[0].line, line,
+        "The correct line was parsed.");
+      is(parsed[0].messages.length, 2,
+        "There are 2 parsed messages.");
+      ok(parsed[0].messages[0].contains("'constructor' : too many arguments"),
+        "The correct first message was parsed.");
+      ok(parsed[0].messages[1].contains("'assign' : cannot convert from"),
+        "The correct second message was parsed.");
+    }
+  }
+
+  function checkHasVertSecondError(bool, error) {
+    ok(error, "Vertex shader compiled with errors.");
+    isnot(error.link, "", "The linkage status should not be empty.");
+
+    let line = 8;
+    info("Checking second vertex shader error on line " + line + "...");
+
+    is(vsEditor.hasMarker(line, "errors", "error"), bool,
+      "Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
+    is(vsEditor.hasLineClass(line, "error-line"), bool,
+      "Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
+
+    let parsed = ShadersEditorsView._errors.vs;
+    is(parsed.length >= 2, bool,
+      "There's " + (bool ? ">= 2" : "< 2") + " parsed vertex shader error(s).");
+
+    if (bool) {
+      is(parsed[1].line, line,
+        "The correct line was parsed.");
+      is(parsed[1].messages.length, 1,
+        "There is 1 parsed message.");
+      ok(parsed[1].messages[0].contains("'assign' : cannot convert from"),
+        "The correct message was parsed.");
+    }
+  }
+
+  function checkHasFragError(bool, error) {
+    ok(error, "Fragment shader compiled with errors.");
+    isnot(error.link, "", "The linkage status should not be empty.");
+
+    let line = 5;
+    info("Checking first vertex shader error on line " + line + "...");
+
+    is(fsEditor.hasMarker(line, "errors", "error"), bool,
+      "Error is " + (bool ? "" : "not ") + "shown in the editor's gutter.");
+    is(fsEditor.hasLineClass(line, "error-line"), bool,
+      "Error style is " + (bool ? "" : "not ") + "applied to the faulty line.");
+
+    let parsed = ShadersEditorsView._errors.fs;
+    is(parsed.length >= 1, bool,
+      "There's " + (bool ? ">= 2" : "< 1") + " parsed fragment shader error(s).");
+
+    if (bool) {
+      is(parsed[0].line, line,
+        "The correct line was parsed.");
+      is(parsed[0].messages.length, 1,
+        "There is 1 parsed message.");
+      ok(parsed[0].messages[0].contains("'constructor' : too many arguments"),
+        "The correct message was parsed.");
+    }
+  }
+}
+
+function once(aTarget, aEvent) {
+  let deferred = promise.defer();
+  aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
+  return deferred.promise;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shadereditor/test/browser_se_editors-error-tooltip.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if error tooltips can be opened from the editor's gutter when there's
+ * a shader compilation error.
+ */
+
+function ifWebGLSupported() {
+  let [target, debuggee, panel] = yield initShaderEditor(SIMPLE_CANVAS_URL);
+  let { gFront, EVENTS, ShadersEditorsView } = panel.panelWin;
+
+  reload(target);
+  yield once(gFront, "program-linked");
+
+  let vsEditor = yield ShadersEditorsView._getEditor("vs");
+  let fsEditor = yield ShadersEditorsView._getEditor("fs");
+
+  vsEditor.replaceText("vec3", { line: 7, ch: 22 }, { line: 7, ch: 26 });
+  yield once(panel.panelWin, EVENTS.SHADER_COMPILED);
+
+  // Synthesizing 'mouseenter' events doesn't work, hack around this by
+  // manually calling the event listener with the expected arguments.
+  let editorDocument = vsEditor.container.contentDocument;
+  let marker = editorDocument.querySelector(".error");
+  let parsed = ShadersEditorsView._errors.vs[0].messages;
+  ShadersEditorsView._onMarkerMouseEnter(7, marker, parsed);
+
+  let tooltip = marker._markerErrorsTooltip;
+  ok(tooltip, "A tooltip was created successfully.");
+
+  let content = tooltip.content;
+  ok(tooltip.content,
+    "Some tooltip's content was set.");
+  is(tooltip.content.className, "devtools-tooltip-simple-text-container",
+    "The tooltip's content container was created correctly.");
+
+  let messages = content.childNodes;
+  is(messages.length, 2,
+    "There are two messages displayed in the tooltip.");
+  is(messages[0].className, "devtools-tooltip-simple-text",
+    "The first message was created correctly.");
+  is(messages[1].className, "devtools-tooltip-simple-text",
+    "The second message was created correctly.");
+
+  ok(messages[0].textContent.contains("'constructor' : too many arguments"),
+    "The first message contains the correct text.");
+  ok(messages[1].textContent.contains("'assign' : cannot convert"),
+    "The second message contains the correct text.");
+
+  yield teardown(panel);
+  finish();
+}
+
+function once(aTarget, aEvent) {
+  let deferred = promise.defer();
+  aTarget.once(aEvent, (aName, aData) => deferred.resolve(aData));
+  return deferred.promise;
+}
--- a/browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js
+++ b/browser/devtools/shadereditor/test/browser_se_shaders-edit-03.js
@@ -1,13 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Tests if editing a vertex and a fragment shader works properly.
+ * Tests if editing a vertex and a fragment shader would permanently store
+ * their new source on the backend and reshow it in the frontend when required.
  */
 
 function ifWebGLSupported() {
   let [target, debuggee, panel] = yield initShaderEditor(MULTIPLE_CONTEXTS_URL);
   let { gFront, EVENTS, ShadersListView, ShadersEditorsView } = panel.panelWin;
 
   reload(target);
   let firstProgramActor = yield once(gFront, "program-linked");
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -90,29 +90,36 @@ function Tooltip(doc) {
 
   // Used for namedTimeouts in the mouseover handling
   this.uid = "tooltip-" + Date.now();
 }
 
 module.exports.Tooltip = Tooltip;
 
 Tooltip.prototype = {
+  defaultPosition: "before_start",
+  defaultOffsetX: 0,
+  defaultOffsetY: 0,
+
   /**
    * Show the tooltip. It might be wise to append some content first if you
    * don't want the tooltip to be empty. You may access the content of the
    * tooltip by setting a XUL node to t.tooltip.content.
    * @param {node} anchor
    *        Which node should the tooltip be shown on
    * @param {string} position
    *        https://developer.mozilla.org/en-US/docs/XUL/PopupGuide/Positioning
    *        Defaults to before_start
    */
-  show: function(anchor, position="before_start") {
+  show: function(anchor,
+    position = this.defaultPosition,
+    x = this.defaultOffsetX,
+    y = this.defaultOffsetY) {
     this.panel.hidden = false;
-    this.panel.openPopup(anchor, position);
+    this.panel.openPopup(anchor, position, x, y);
   },
 
   /**
    * Hide the tooltip
    */
   hide: function() {
     this.panel.hidden = true;
     this.panel.hidePopup();
@@ -253,16 +260,38 @@ Tooltip.prototype = {
     }
   },
 
   get content() {
     return this.panel.firstChild;
   },
 
   /**
+   * Sets some text as the content of this tooltip.
+   *
+   * @param string[] messages
+   *        A list of text messages.
+   */
+  setTextContent: function(...messages) {
+    let vbox = this.doc.createElement("vbox");
+    vbox.className = "devtools-tooltip-simple-text-container";
+    vbox.setAttribute("flex", "1");
+
+    for (let text of messages) {
+      let description = this.doc.createElement("description");
+      description.setAttribute("flex", "1");
+      description.className = "devtools-tooltip-simple-text";
+      description.textContent = text;
+      vbox.appendChild(description);
+    }
+
+    this.content = vbox;
+  },
+
+  /**
    * Fill the tooltip with an image, displayed over a tiled background useful
    * for transparent images.
    * Also adds the image dimension as a label at the bottom.
    */
   setImageContent: function(imageUrl, maxDim=400) {
     // Main container
     let vbox = this.doc.createElement("vbox");
     vbox.setAttribute("align", "center")
--- a/browser/devtools/sourceeditor/codemirror/mozilla.css
+++ b/browser/devtools/sourceeditor/codemirror/mozilla.css
@@ -1,26 +1,40 @@
+/* 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/. */
+
+.errors,
 .breakpoints {
   width: 16px;
 }
 
-.breakpoint, .debugLocation, .breakpoint-debugLocation {
+.error, .breakpoint, .debugLocation, .breakpoint-debugLocation {
   display: inline-block;
   margin-left: 5px;
-  width: 14px;
-  height: 14px;
+  width: 12px;
+  height: 12px;
   background-repeat: no-repeat;
-  background-position: center center;
-  background-size: 12px;
+  background-position: center;
+  background-size: contain;
+}
+
+.error {
+  background-image: url("chrome://browser/skin/devtools/orion-error.png");
+  opacity: 0.75;
 }
 
 .breakpoint {
   background-image: url("chrome://browser/skin/devtools/orion-breakpoint.png");
 }
 
 .debugLocation {
   background-image: url("chrome://browser/skin/devtools/orion-debug-location.png");
 }
 
 .breakpoint.debugLocation {
   background-image: url("chrome://browser/skin/devtools/orion-debug-location.png"),
     url("chrome://browser/skin/devtools/orion-breakpoint.png");
-}
\ No newline at end of file
+}
+
+.error-line {
+  background: rgba(255,0,0,0.2);
+}
--- a/browser/devtools/sourceeditor/debugger.js
+++ b/browser/devtools/sourceeditor/debugger.js
@@ -1,48 +1,16 @@
 /* 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 dbginfo = new WeakMap();
 
-// Private functions
-
-/**
- * Adds a marker to the breakpoints gutter.
- * Type should be either a 'breakpoint' or a 'debugLocation'.
- */
-function addMarker(cm, line, type) {
-  let info = cm.lineInfo(line);
-
-  if (info.gutterMarkers)
-    return void info.gutterMarkers.breakpoints.classList.add(type);
-
-  let mark = cm.getWrapperElement().ownerDocument.createElement("div");
-  mark.className = type;
-  mark.innerHTML = "";
-
-  cm.setGutterMarker(info.line, "breakpoints", mark);
-}
-
-/**
- * Removes a marker from the breakpoints gutter.
- * Type should be either a 'breakpoint' or a 'debugLocation'.
- */
-function removeMarker(cm, line, type) {
-  let info = cm.lineInfo(line);
-
-  if (!info || !info.gutterMarkers)
-    return;
-
-  info.gutterMarkers.breakpoints.classList.remove(type);
-}
-
 // These functions implement search within the debugger. Since
 // search in the debugger is different from other components,
 // we can't use search.js CodeMirror addon. This is a slightly
 // modified version of that addon. Depends on searchcursor.js.
 
 function SearchState() {
   this.posFrom = this.posTo = this.query = null;
 }
@@ -150,17 +118,17 @@ function hasBreakpoint(ctx, line) {
 function addBreakpoint(ctx, line, cond) {
   if (hasBreakpoint(ctx, line))
     return;
 
   let { ed, cm } = ctx;
   let meta = dbginfo.get(ed);
   let info = cm.lineInfo(line);
 
-  addMarker(cm, line, "breakpoint");
+  ed.addMarker(line, "breakpoints", "breakpoint");
   meta.breakpoints[line] = { condition: cond };
 
   info.handle.on("delete", function onDelete() {
     info.handle.off("delete", onDelete);
     meta.breakpoints[info.line] = null;
   });
 
   ed.emit("breakpointAdded", line);
@@ -174,17 +142,17 @@ function removeBreakpoint(ctx, line) {
   if (!hasBreakpoint(ctx, line))
     return;
 
   let { ed, cm } = ctx;
   let meta = dbginfo.get(ed);
   let info = cm.lineInfo(line);
 
   meta.breakpoints[info.line] = null;
-  removeMarker(cm, info.line, "breakpoint");
+  ed.removeMarker(info.line, "breakpoints", "breakpoint");
   ed.emit("breakpointRemoved", line);
 }
 
 /**
  * Returns a list of all breakpoints in the current Editor.
  */
 function getBreakpoints(ctx) {
   let { ed } = ctx;
@@ -198,21 +166,21 @@ function getBreakpoints(ctx) {
 }
 
 /**
  * Saves a debug location information and adds a visual anchor to
  * the breakpoints gutter. This is used by the debugger UI to
  * display the line on which the Debugger is currently paused.
  */
 function setDebugLocation(ctx, line) {
-  let { ed, cm } = ctx;
+  let { ed } = ctx;
   let meta = dbginfo.get(ed);
 
   meta.debugLocation = line;
-  addMarker(cm, line, "debugLocation");
+  ed.addMarker(line, "breakpoints", "debugLocation");
 }
 
 /**
  * Returns a line number that corresponds to the current debug
  * location.
  */
 function getDebugLocation(ctx) {
   let { ed } = ctx;
@@ -221,21 +189,21 @@ function getDebugLocation(ctx) {
   return meta.debugLocation;
 }
 
 /**
  * Clears the debug location. Clearing the debug location
  * also removes a visual anchor from the breakpoints gutter.
  */
 function clearDebugLocation(ctx) {
-  let { ed, cm } = ctx;
+  let { ed } = ctx;
   let meta = dbginfo.get(ed);
 
   if (meta.debugLocation != null) {
-    removeMarker(cm, meta.debugLocation, "debugLocation");
+    ed.removeMarker(meta.debugLocation, "breakpoints", "debugLocation");
     meta.debugLocation = null;
   }
 }
 
 /**
  * Starts a new search.
  */
 function find(ctx, query) {
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -335,47 +335,56 @@ Editor.prototype = {
     let cm = editors.get(this);
     return cm.getOption("readOnly");
   },
 
   /**
    * Replaces contents of a text area within the from/to {line, ch}
    * range. If neither from nor to arguments are provided works
    * exactly like setText. If only from object is provided, inserts
-   * text at that point.
+   * text at that point, *overwriting* as many characters as needed.
    */
   replaceText: function (value, from, to) {
     let cm = editors.get(this);
 
     if (!from)
       return void this.setText(value);
 
     if (!to) {
       let text = cm.getRange({ line: 0, ch: 0 }, from);
       return void this.setText(text + value);
     }
 
     cm.replaceRange(value, from, to);
   },
 
   /**
+   * Inserts text at the specified {line, ch} position, shifting existing
+   * contents as necessary.
+   */
+  insertText: function (value, at) {
+    let cm = editors.get(this);
+    cm.replaceRange(value, at, at);
+  },
+
+  /**
    * Deselects contents of the text area.
    */
   dropSelection: function () {
     if (!this.somethingSelected())
       return;
 
     this.setCursor(this.getCursor());
   },
 
   /**
    * Marks the contents as clean and returns the current
    * version number.
    */
-  markClean: function () {
+  setClean: function () {
     let cm = editors.get(this);
     this.version = cm.changeGeneration();
     return this.version;
   },
 
   /**
    * Returns true if contents of the text area are
    * clean i.e. no changes were made since the last version.
@@ -514,16 +523,130 @@ Editor.prototype = {
       "top": Math.max(line - offset, 0)
     }[align || "top"] || offset;
 
     // Bringing down the topLine to total lines in the editor if exceeding.
     topLine = Math.min(topLine, this.lineCount());
     this.setFirstVisibleLine(topLine);
   },
 
+  /**
+   * Returns whether a marker of a specified class exists in a line's gutter.
+   */
+  hasMarker: function (line, gutterName, markerClass) {
+    let cm = editors.get(this);
+    let info = cm.lineInfo(line);
+    if (!info)
+      return false;
+
+    let gutterMarkers = info.gutterMarkers;
+    if (!gutterMarkers)
+      return false;
+
+    let marker = gutterMarkers[gutterName];
+    if (!marker)
+      return false;
+
+    return marker.classList.contains(markerClass);
+  },
+
+  /**
+   * Adds a marker with a specified class to a line's gutter. If another marker
+   * exists on that line, the new marker class is added to its class list.
+   */
+  addMarker: function (line, gutterName, markerClass) {
+    let cm = editors.get(this);
+    let info = cm.lineInfo(line);
+    if (!info)
+      return;
+
+    let gutterMarkers = info.gutterMarkers;
+    if (gutterMarkers) {
+      let marker = gutterMarkers[gutterName];
+      if (marker) {
+        marker.classList.add(markerClass);
+        return;
+      }
+    }
+
+    let marker = cm.getWrapperElement().ownerDocument.createElement("div");
+    marker.className = markerClass;
+    cm.setGutterMarker(info.line, gutterName, marker);
+  },
+
+  /**
+   * The reverse of addMarker. Removes a marker of a specified class from a
+   * line's gutter.
+   */
+  removeMarker: function (line, gutterName, markerClass) {
+    if (!this.hasMarker(line, gutterName, markerClass))
+      return;
+
+    let cm = editors.get(this);
+    cm.lineInfo(line).gutterMarkers[gutterName].classList.remove(markerClass);
+  },
+
+  /**
+   * Remove all gutter markers in the gutter with the given name.
+   */
+  removeAllMarkers: function (gutterName) {
+    let cm = editors.get(this);
+    cm.clearGutter(gutterName);
+  },
+
+  /**
+   * Handles attaching a set of events listeners on a marker. They should
+   * be passed as an object literal with keys as event names and values as
+   * function listeners. The line number, marker node and optional data
+   * will be passed as arguments to the function listener.
+   *
+   * You don't need to worry about removing these event listeners.
+   * They're automatically orphaned when clearing markers.
+   */
+  setMarkerListeners: function(line, gutterName, markerClass, events, data) {
+    if (!this.hasMarker(line, gutterName, markerClass))
+      return;
+
+    let cm = editors.get(this);
+    let marker = cm.lineInfo(line).gutterMarkers[gutterName];
+
+    for (let name in events) {
+      let listener = events[name].bind(this, line, marker, data);
+      marker.addEventListener(name, listener);
+    }
+  },
+
+  /**
+   * Returns whether a line is decorated using the specified class name.
+   */
+  hasLineClass: function (line, className) {
+    let cm = editors.get(this);
+    let info = cm.lineInfo(line);
+    if (!info)
+      return false;
+
+    return info.wrapClass == className;
+  },
+
+  /**
+   * Set a CSS class name for the given line, including the text and gutter.
+   */
+  addLineClass: function (line, className) {
+    let cm = editors.get(this);
+    cm.addLineClass(line, "wrap", className);
+  },
+
+  /**
+   * The reverse of addLineClass.
+   */
+  removeLineClass: function (line, className) {
+    let cm = editors.get(this);
+    cm.removeLineClass(line, "wrap", className);
+  },
+
   destroy: function () {
     this.container = null;
     this.config = null;
     this.version = null;
     this.emit("destroy");
   }
 };
 
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -371,17 +371,17 @@ StyleSheetEditor.prototype = {
         FileUtils.closeSafeFileOutputStream(ostream);
         // remember filename for next save if any
         this._friendlyName = null;
         this.savedFile = returnFile;
 
         if (callback) {
           callback(returnFile);
         }
-        this.sourceEditor.markClean();
+        this.sourceEditor.setClean();
       }.bind(this));
     };
 
     showFilePicker(file || this._styleSheetFilePath, true, this._window, onFile);
   },
 
   /**
     * Retrieve custom key bindings objects as expected by SourceEditor.
new file mode 100644
index 0000000000000000000000000000000000000000..39ef81e208bde4b4f1fcf8a8803db3d41357758f
GIT binary patch
literal 3794
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
IM6N<$f)4I2UjP6A
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -138,16 +138,17 @@ browser.jar:
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
   skin/classic/browser/devtools/commandline.css              (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css       (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/orion.css             (devtools/orion.css)
   skin/classic/browser/devtools/orion-container.css   (devtools/orion-container.css)
   skin/classic/browser/devtools/orion-task.png        (devtools/orion-task.png)
+  skin/classic/browser/devtools/orion-error.png       (devtools/orion-error.png)
   skin/classic/browser/devtools/orion-breakpoint.png  (devtools/orion-breakpoint.png)
   skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png             (devtools/breadcrumbs/ltr-end-selected.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end.png                      (devtools/breadcrumbs/ltr-end.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png           (devtools/breadcrumbs/ltr-middle-pressed.png)
new file mode 100644
index 0000000000000000000000000000000000000000..39ef81e208bde4b4f1fcf8a8803db3d41357758f
GIT binary patch
literal 3794
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
IM6N<$f)4I2UjP6A
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -227,16 +227,17 @@ browser.jar:
   skin/classic/browser/devtools/command-tilt.png            (devtools/command-tilt.png)
   skin/classic/browser/devtools/alerticon-warning.png       (devtools/alerticon-warning.png)
   skin/classic/browser/devtools/ruleview.css                (devtools/ruleview.css)
   skin/classic/browser/devtools/commandline.css             (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css             (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/orion.css                   (devtools/orion.css)
   skin/classic/browser/devtools/orion-container.css         (devtools/orion-container.css)
   skin/classic/browser/devtools/orion-task.png              (devtools/orion-task.png)
+  skin/classic/browser/devtools/orion-error.png             (devtools/orion-error.png)
   skin/classic/browser/devtools/orion-breakpoint.png        (devtools/orion-breakpoint.png)
   skin/classic/browser/devtools/orion-debug-location.png    (devtools/orion-debug-location.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
--- a/browser/themes/shared/devtools/common.inc.css
+++ b/browser/themes/shared/devtools/common.inc.css
@@ -116,20 +116,38 @@
 
 .devtools-tooltip.devtools-tooltip-tooltip {
   /* If the tooltip uses a <tooltip> XUL element */
   -moz-appearance: none;
   padding: 4px;
   background: #eee;
   border-radius: 3px;
 }
+
 .devtools-tooltip.devtools-tooltip-panel .panel-arrowcontent {
   /* If the tooltip uses a <panel> XUL element instead */
   padding: 4px;
 }
 
+.devtools-tooltip-simple-text {
+  background: linear-gradient(1deg, transparent 0%, rgba(94,136,176,0.1) 100%);
+  max-width: 400px;
+  margin: 0 -4px; /* Compensate for the .panel-arrowcontent padding. */
+  padding: 8px 12px;
+  text-shadow: 0 1px 0 #fff;
+  white-space: pre-wrap;
+}
+
+.devtools-tooltip-simple-text:first-child {
+  margin-top: -4px;
+}
+
+.devtools-tooltip-simple-text:last-child {
+  margin-bottom: -4px;
+}
+
 .devtools-tooltip-tiles {
   background-color: #eee;
   background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
     linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
   background-size: 20px 20px;
   background-position: 0 0, 10px 10px;
 }
new file mode 100644
index 0000000000000000000000000000000000000000..39ef81e208bde4b4f1fcf8a8803db3d41357758f
GIT binary patch
literal 3794
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
IM6N<$f)4I2UjP6A
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -162,16 +162,17 @@ browser.jar:
         skin/classic/browser/devtools/command-paintflashing.png     (devtools/command-paintflashing.png)
         skin/classic/browser/devtools/command-responsivemode.png    (devtools/command-responsivemode.png)
         skin/classic/browser/devtools/command-scratchpad.png        (devtools/command-scratchpad.png)
         skin/classic/browser/devtools/command-tilt.png              (devtools/command-tilt.png)
         skin/classic/browser/devtools/markup-view.css               (../shared/devtools/markup-view.css)
         skin/classic/browser/devtools/orion.css                     (devtools/orion.css)
         skin/classic/browser/devtools/orion-container.css           (devtools/orion-container.css)
         skin/classic/browser/devtools/orion-task.png                (devtools/orion-task.png)
+        skin/classic/browser/devtools/orion-error.png               (devtools/orion-error.png)
         skin/classic/browser/devtools/orion-breakpoint.png          (devtools/orion-breakpoint.png)
         skin/classic/browser/devtools/orion-debug-location.png      (devtools/orion-debug-location.png)
 *       skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
         skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
         skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
@@ -439,16 +440,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/command-tilt.png          (devtools/command-tilt.png)
         skin/classic/aero/browser/devtools/alerticon-warning.png     (devtools/alerticon-warning.png)
         skin/classic/aero/browser/devtools/ruleview.css              (devtools/ruleview.css)
         skin/classic/aero/browser/devtools/commandline.css           (devtools/commandline.css)
         skin/classic/aero/browser/devtools/markup-view.css           (../shared/devtools/markup-view.css)
         skin/classic/aero/browser/devtools/orion.css                 (devtools/orion.css)
         skin/classic/aero/browser/devtools/orion-container.css       (devtools/orion-container.css)
         skin/classic/aero/browser/devtools/orion-task.png            (devtools/orion-task.png)
+        skin/classic/aero/browser/devtools/orion-error.png           (devtools/orion-error.png)
         skin/classic/aero/browser/devtools/orion-breakpoint.png      (devtools/orion-breakpoint.png)
         skin/classic/aero/browser/devtools/orion-debug-location.png  (devtools/orion-debug-location.png)
 *       skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/aero/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
         skin/classic/aero/browser/devtools/webconsole.png                  (devtools/webconsole.png)
         skin/classic/aero/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)