Bug 8552660728e7 - Wait a turn of the event loop if CodeMirror isn't settled by the time breakpoints are being set. r=vporof, a=sledru
authorPanos Astithas <past@mozilla.com>
Wed, 09 Jul 2014 18:49:34 +0300
changeset 209028 2a218b374b37ee265925a258e1559c35ef6a073f
parent 209027 10b0cd0433a6b1efaeb2f8a03719017f22a4e564
child 209029 0e13ae310139f84683aaefe091b22f4124372be7
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)
reviewersvporof, sledru
milestone32.0a2
Bug 8552660728e7 - Wait a turn of the event loop if CodeMirror isn't settled by the time breakpoints are being set. r=vporof, a=sledru
browser/devtools/debugger/debugger-controller.js
browser/devtools/sourceeditor/debugger.js
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted", "XStringBundle"];
 const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
-const EDITOR_BREAKPOINTS_UPDATE_DELAY = 200; // ms
 const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
 const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
 const FRAME_STEP_CLEAR_DELAY = 100; // ms
 const CALL_STACK_PAGE_SIZE = 25; // frames
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
   // When the debugger's source editor instance finishes loading or unloading.
@@ -1160,19 +1159,17 @@ SourceScripts.prototype = {
           DebuggerView.Sources.selectedIndex = 0;
         }
       });
     }
 
     // If there are any stored breakpoints for this source, display them again,
     // both in the editor and the breakpoints pane.
     DebuggerController.Breakpoints.updatePaneBreakpoints();
-    setNamedTimeout("update-editor-bp", EDITOR_BREAKPOINTS_UPDATE_DELAY, () => {
-      DebuggerController.Breakpoints.updateEditorBreakpoints();
-    });
+    DebuggerController.Breakpoints.updateEditorBreakpoints();
 
     // Make sure the events listeners are up to date.
     if (DebuggerView.instrumentsPaneTab == "events-tab") {
       DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
     }
 
     // Signal that a new source has been added.
     window.emit(EVENTS.NEW_SOURCE);
@@ -1849,17 +1846,17 @@ Breakpoints.prototype = {
   updateEditorBreakpoints: Task.async(function*() {
     for (let breakpointPromise of this._addedOrDisabled) {
       let breakpointClient = yield breakpointPromise;
       let currentSourceUrl = DebuggerView.Sources.selectedValue;
       let breakpointUrl = breakpointClient.location.url;
 
       // Update the view only if the breakpoint is in the currently shown source.
       if (currentSourceUrl == breakpointUrl) {
-        this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
+        yield this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
       }
     }
   }),
 
   /**
    * Update the breakpoints in the pane view. This function takes the list of
    * breakpoints in the debugger and adds them back into the breakpoints pane.
    * This is invoked when new sources are received via the _onNewSource and
@@ -1868,17 +1865,17 @@ Breakpoints.prototype = {
   updatePaneBreakpoints: Task.async(function*() {
     for (let breakpointPromise of this._addedOrDisabled) {
       let breakpointClient = yield breakpointPromise;
       let container = DebuggerView.Sources;
       let breakpointUrl = breakpointClient.location.url;
 
       // Update the view only if the breakpoint exists in a known source.
       if (container.containsValue(breakpointUrl)) {
-        this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
+        yield this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
       }
     }
   }),
 
   /**
    * Add a breakpoint.
    *
    * @param object aLocation
@@ -1961,17 +1958,17 @@ Breakpoints.prototype = {
       // Preserve information about the breakpoint's line text, to display it
       // in the sources pane without requiring fetching the source (for example,
       // after the target navigated). Note that this will get out of sync
       // if the source text contents change.
       let line = aBreakpointClient.location.line - 1;
       aBreakpointClient.text = DebuggerView.editor.getText(line).trim();
 
       // Show the breakpoint in the editor and breakpoints pane, and resolve.
-      this._showBreakpoint(aBreakpointClient, aOptions);
+      yield this._showBreakpoint(aBreakpointClient, aOptions);
 
       // Notify that we've added a breakpoint.
       window.emit(EVENTS.BREAKPOINT_ADDED, aBreakpointClient);
       deferred.resolve(aBreakpointClient);
     }.bind(this)));
 
     return deferred.promise;
   }),
@@ -2113,30 +2110,33 @@ Breakpoints.prototype = {
    *        This object must have the following properties:
    *          - location: the breakpoint's source location and line number
    *          - disabled: the breakpoint's disabled state, boolean
    *          - text: the breakpoint's line text to be displayed
    * @param object aOptions [optional]
    *        @see DebuggerController.Breakpoints.addBreakpoint
    */
   _showBreakpoint: function(aBreakpointData, aOptions = {}) {
+    let tasks = [];
     let currentSourceUrl = DebuggerView.Sources.selectedValue;
     let location = aBreakpointData.location;
 
     // Update the editor if required.
     if (!aOptions.noEditorUpdate && !aBreakpointData.disabled) {
       if (location.url == currentSourceUrl) {
-        DebuggerView.editor.addBreakpoint(location.line - 1);
+        tasks.push(DebuggerView.editor.addBreakpoint(location.line - 1));
       }
     }
 
     // Update the breakpoints pane if required.
     if (!aOptions.noPaneUpdate) {
       DebuggerView.Sources.addBreakpoint(aBreakpointData, aOptions);
     }
+
+    return promise.all(tasks);
   },
 
   /**
    * Update the editor and breakpoints pane to hide a specified breakpoint.
    *
    * @param object aLocation
    *        @see DebuggerController.Breakpoints.addBreakpoint
    * @param object aOptions [optional]
--- a/browser/devtools/sourceeditor/debugger.js
+++ b/browser/devtools/sourceeditor/debugger.js
@@ -1,14 +1,17 @@
 /* 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 {Cu} = require("chrome");
+const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const dbginfo = new WeakMap();
 
 // 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() {
@@ -99,46 +102,64 @@ function initialize(ctx) {
 }
 
 /**
  * True if editor has a visual breakpoint at that line, false
  * otherwise.
  */
 function hasBreakpoint(ctx, line) {
   let { cm } = ctx;
+  // In some rare occasions CodeMirror might not be properly initialized yet, so
+  // return an exceptional value in that case.
+  if (cm.lineInfo(line) === null) {
+    return null;
+  }
   let markers = cm.lineInfo(line).gutterMarkers;
 
   return markers != null &&
     markers.breakpoints.classList.contains("breakpoint");
 }
 
 /**
  * Adds a visual breakpoint for a specified line. Third
  * parameter 'cond' can hold any object.
  *
  * After adding a breakpoint, this function makes Editor to
  * emit a breakpointAdded event.
  */
 function addBreakpoint(ctx, line, cond) {
+  function _addBreakpoint(ctx, line, cond) {
+    let { ed, cm } = ctx;
+    let meta = dbginfo.get(ed);
+    let info = cm.lineInfo(line);
+
+    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);
+    deferred.resolve();
+  }
+
   if (hasBreakpoint(ctx, line))
     return;
 
-  let { ed, cm } = ctx;
-  let meta = dbginfo.get(ed);
-  let info = cm.lineInfo(line);
-
-  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);
+  let deferred = promise.defer();
+  // If lineInfo() returns null, wait a tick to give the editor a chance to
+  // initialize properly.
+  if (ctx.cm.lineInfo(line) === null) {
+    DevToolsUtils.executeSoon(() => _addBreakpoint(ctx, line, cond));
+  } else {
+    _addBreakpoint(ctx, line, cond);
+  }
+  return deferred.promise;
 }
 
 /**
  * Removes a visual breakpoint from a specified line and
  * makes Editor to emit a breakpointRemoved event.
  */
 function removeBreakpoint(ctx, line) {
   if (!hasBreakpoint(ctx, line))