Attachment #557396: Source map integration w/ the webconsole V3 for bug #670002

View | Details | Raw Unified | Return to bug 670002
Collapse All | Expand All

(-)a/browser/devtools/Makefile.in (-4 / +1 lines)
Line     Link Here 
 Lines 50-61    Link Here 
50
  webconsole \
50
  webconsole \
51
  scratchpad \
51
  scratchpad \
52
  sourceeditor \
52
  sourceeditor \
53
  sourcemap \
53
  $(NULL)
54
  $(NULL)
54
55
55
DIRS = \
56
	sourcemap \
57
	$(NULL)
58
59
ifdef ENABLE_TESTS
56
ifdef ENABLE_TESTS
60
# DIRS += test # no tests yet
57
# DIRS += test # no tests yet
61
endif
58
endif
(-)a/browser/devtools/sourcemap/Makefile.in (-2 / +4 lines)
Line     Link Here 
 Lines 44-51    Link Here 
44
44
45
include $(DEPTH)/config/autoconf.mk
45
include $(DEPTH)/config/autoconf.mk
46
46
47
EXTRA_JS_MODULES = SourceMapConsumer.jsm \
47
EXTRA_JS_MODULES = \
48
		$(NULL)
48
  SourceMapConsumer.jsm \
49
  SourceMapUtils.jsm \
50
  $(NULL)
49
51
50
ifdef ENABLE_TESTS
52
ifdef ENABLE_TESTS
51
# No tests right now
53
# No tests right now
(-)a/browser/devtools/sourcemap/SourceMapUtils.jsm (+203 lines)
Line     Link Here 
Line 0    Link Here 
1
/* -*- Mode: js; js-indent-level: 2; -*- */
2
/* ***** BEGIN LICENSE BLOCK *****
3
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4
 *
5
 * The contents of this file are subject to the Mozilla Public License Version
6
 * 1.1 (the "License"); you may not use this file except in compliance with
7
 * the License. You may obtain a copy of the License at
8
 * http://www.mozilla.org/MPL/
9
 *
10
 * Software distributed under the License is distributed on an "AS IS" basis,
11
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12
 * for the specific language governing rights and limitations under the
13
 * License.
14
 *
15
 * The Original Code is Mozilla Source Map.
16
 *
17
 * The Initial Developer of the Original Code is
18
 * The Mozilla Foundation
19
 * Portions created by the Initial Developer are Copyright (C) 2011
20
 * the Initial Developer. All Rights Reserved.
21
 *
22
 * Contributor(s):
23
 *   Nick Fitzgerald <nfitzgerald@mozilla.com> (original author)
24
 *
25
 * Alternatively, the contents of this file may be used under the terms of
26
 * either the GNU General Public License Version 2 or later (the "GPL"), or
27
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
 * in which case the provisions of the GPL or the LGPL are applicable instead
29
 * of those above. If you wish to allow use of your version of this file only
30
 * under the terms of either the GPL or the LGPL, and not to allow others to
31
 * use your version of this file under the terms of the MPL, indicate your
32
 * decision by deleting the provisions above and replace them with the notice
33
 * and other provisions required by the GPL or the LGPL. If you do not delete
34
 * the provisions above, a recipient may use your version of this file under
35
 * the terms of any one of the MPL, the GPL or the LGPL.
36
 *
37
 * ***** END LICENSE BLOCK ***** */
38
39
const Cc = Components.classes;
40
const Ci = Components.interfaces;
41
const Cu = Components.utils;
42
43
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
44
45
XPCOMUtils.defineLazyGetter(this, "Debugger", function () {
46
  let debuggerService = Cc["@mozilla.org/jsdebugger;1"].getService(Ci.IJSDebugger);
47
  debuggerService.addClass();
48
  return Debugger;
49
});
50
51
let EXPORTED_SYMBOLS = [ 'ERRORS', 'sourceMapURLForFilename', 'sourceMapForFilename' ];
52
53
function setTimeout(fn, ms) {
54
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
55
  timer.initWithCallback({ notify: fn }, ms, Ci.nsITimer.TYPE_ONE_SHOT);
56
}
57
58
function constantProperty(aValue) {
59
  return {
60
    value: aValue,
61
    writable: false,
62
    enumerable: true,
63
    configurable: false
64
  };
65
}
66
67
const ERRORS = Object.create(null, {
68
  NO_SOURCE_MAP: constantProperty(1),
69
  BAD_SOURCE_MAP: constantProperty(2),
70
  UNKNOWN_ERROR: constantProperty(3)
71
});
72
73
74
XPCOMUtils.defineLazyGetter(this, 'SourceMapConsumer', function () {
75
  let obj = {};
76
  Cu.import('resource:///modules/SourceMapConsumer.jsm', obj);
77
  return obj.SourceMapConsumer;
78
});
79
80
81
// The empty function
82
function noop () {}
83
84
function jsonGetOnReadyStateChange(aXhr, aUrl, aSuccessCallback,
85
                                   aErrorCallback, aEvt) {
86
  if (aXhr.readyState === 4) {
87
    if (aXhr.status === 200 || (aXhr.status === 0 && /^file:\/\//.test(aUrl))) {
88
      aSuccessCallback(aXhr);
89
    }
90
    else {
91
      aErrorCallback(aXhr);
92
    }
93
  }
94
}
95
96
/**
97
 * A utility for getting JSON which wraps XMLHttpRequest.
98
 */
99
function jsonGet(aUrl, aSuccessCallback, aErrorCallback) {
100
  let xhr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
101
    .createInstance(Ci.nsIXMLHttpRequest);
102
  xhr.overrideMimeType('text/json');
103
  xhr.onreadystatechange = jsonGetOnReadyStateChange.bind(this,
104
                                                          xhr,
105
                                                          aUrl,
106
                                                          aSuccessCallback,
107
                                                          aErrorCallback);
108
  xhr.open("GET", aUrl, true);
109
  xhr.send(null);
110
}
111
112
/**
113
 * Returns the URL of the source map associated for the given script filename,
114
 * otherwise returns null. This is where all the relative path resolution and
115
 * normalization should happen.
116
 *
117
 * @param aFilename The source of the script we are trying to get a source map
118
 *        URL for.
119
 * @param aWindow The window object for the script.
120
 * @param aCallback The function to call when we have the source map's URL.
121
 */
122
function sourceMapURLForFilename(aFilename, aWindow, aCallback) {
123
  // XXX: This setTimeout, along with aCallback, shouldn't be necessary. See bug
124
  // ######. "can't start debugging: a debuggee script is on the stack"
125
  setTimeout(function () {
126
    // try {
127
    //   let dbg = new Debugger(aWindow);
128
    //   let arr = dbg.getAllScripts();
129
    //   let len = arr.length;
130
    //   for (var i = 0; i < len; i++) {
131
    //     if (arr[i].filename == aFilename) {
132
    //       aCallback(arr[i].sourceMappingURL);
133
    //       return;
134
    //     }
135
    //   }
136
    // }
137
    // catch (e) {
138
    //   // dump('ERROR: ' + e.message + '\n');
139
    //   Cu.reportError(e);
140
    // }
141
    // aCallback(null);
142
    aCallback(aFilename + '.map');
143
  }, 4);
144
}
145
146
function sourceMapFound(aSuccessCallback, aErrorCallback, xhr) {
147
  let sourceMap;
148
  try {
149
    sourceMap = new SourceMapConsumer(JSON.parse(xhr.responseText));
150
  }
151
  catch (e) {
152
    aErrorCallback(ERRORS.BAD_SOURCE_MAP, e);
153
    return;
154
  }
155
  aSuccessCallback(sourceMap);
156
}
157
158
function sourceMapNotFound(aErrorCallback, xhr) {
159
  switch (xhr.status) {
160
  case 404:
161
    aErrorCallback(ERRORS.MISSING_SOURCE_MAP, new Error(xhr.statusText));
162
    break;
163
  default:
164
    aErrorCallback(ERRORS.UNKNOWN_ERROR, new Error(xhr.statusText));
165
  }
166
}
167
168
function fetchSourceMap(aSuccessCallback, aErrorCallback, aFilename,
169
                        aSourceMapURL) {
170
  if (aSourceMapURL) {
171
    try {
172
      jsonGet(aSourceMapURL,
173
              sourceMapFound.bind(this, aSuccessCallback, aErrorCallback),
174
              sourceMapNotFound.bind(this, aErrorCallback));
175
    }
176
    catch (e) {
177
      aErrorCallback(ERRORS.UNKNOWN_ERROR, e);
178
    }
179
  }
180
  else {
181
    aErrorCallback(ERRORS.NO_SOURCE_MAP,
182
                   new Error("No source map for " + aFilename));
183
  }
184
}
185
186
/**
187
 * Fetch the source map associated with the given filename.
188
 *
189
 * @param aFilename The js file you want to fetch the associated source map for.
190
 * @param aWindow The currently active window.
191
 * @param aSuccessCallback The function to call when we find a source map.
192
 * @param aErrorCallback The function to call when there is no source map.
193
 */
194
function sourceMapForFilename(aFilename, aWindow, aSuccessCallback,
195
                              aErrorCallback) {
196
  aErrorCallback = aErrorCallback || noop;
197
  sourceMapURLForFilename(aFilename,
198
                          aWindow,
199
                          fetchSourceMap.bind(this,
200
                                              aSuccessCallback,
201
                                              aErrorCallback,
202
                                              aFilename));
203
}
(-)a/browser/devtools/webconsole/HUDService.jsm (-5 / +201 lines)
Line     Link Here 
 Lines 104-109    Link Here 
104
  return obj.namesAndValuesOf;
104
  return obj.namesAndValuesOf;
105
});
105
});
106
106
107
XPCOMUtils.defineLazyGetter(this, "sourceMapUtils", function () {
108
  let smu = {};
109
  Cu.import("resource:///modules/SourceMapUtils.jsm", smu);
110
  return smu;
111
});
112
107
function LogFactory(aMessagePrefix)
113
function LogFactory(aMessagePrefix)
108
{
114
{
109
  function log(aMessage) {
115
  function log(aMessage) {
 Lines 1999-2004    Link Here 
1999
        return;
2005
        return;
2000
    }
2006
    }
2001
2007
2008
    let window = this.getWindowByWindowId(aMessage.ID)
2009
      || this.getWindowByWindowId(aMessage.innerID)
2010
      || this.currentContext()
2011
      || null;
2002
    let node = ConsoleUtils.createMessageNode(hud.outputNode.ownerDocument,
2012
    let node = ConsoleUtils.createMessageNode(hud.outputNode.ownerDocument,
2003
                                              CATEGORY_WEBDEV,
2013
                                              CATEGORY_WEBDEV,
2004
                                              LEVELS[level],
2014
                                              LEVELS[level],
 Lines 2006-2012    Link Here 
2006
                                              sourceURL,
2016
                                              sourceURL,
2007
                                              sourceLine,
2017
                                              sourceLine,
2008
                                              clipboardText,
2018
                                              clipboardText,
2009
                                              level);
2019
                                              level,
2020
                                              window);
2010
2021
2011
    // Make the node bring up the property panel, to allow the user to inspect
2022
    // Make the node bring up the property panel, to allow the user to inspect
2012
    // the stack trace.
2023
    // the stack trace.
 Lines 2106-2112    Link Here 
2106
                                                  severity,
2117
                                                  severity,
2107
                                                  aScriptError.errorMessage,
2118
                                                  aScriptError.errorMessage,
2108
                                                  aScriptError.sourceName,
2119
                                                  aScriptError.sourceName,
2109
                                                  aScriptError.lineNumber);
2120
                                                  aScriptError.lineNumber,
2121
                                                  null,
2122
                                                  null,
2123
                                                  window,
2124
                                                  null);
2110
2125
2111
        ConsoleUtils.outputMessageNode(node, hudId);
2126
        ConsoleUtils.outputMessageNode(node, hudId);
2112
      }
2127
      }
 Lines 5503-5508    Link Here 
5503
   *        a string, then the clipboard text must be supplied.
5518
   *        a string, then the clipboard text must be supplied.
5504
   * @param number aLevel [optional]
5519
   * @param number aLevel [optional]
5505
   *        The level of the console API message.
5520
   *        The level of the console API message.
5521
   * @param nsIDOMWindow aWindow
5522
   *        The window from which the message originates. This allows us to get
5523
   *        the source map associated with the script that created this message
5524
   *        and display the proper originating source file and line numbers.
5506
   * @return nsIDOMNode
5525
   * @return nsIDOMNode
5507
   *         The message node: a XUL richlistitem ready to be inserted into
5526
   *         The message node: a XUL richlistitem ready to be inserted into
5508
   *         the Web Console output node.
5527
   *         the Web Console output node.
 Lines 5510-5516    Link Here 
5510
  createMessageNode:
5529
  createMessageNode:
5511
  function ConsoleUtils_createMessageNode(aDocument, aCategory, aSeverity,
5530
  function ConsoleUtils_createMessageNode(aDocument, aCategory, aSeverity,
5512
                                          aBody, aSourceURL, aSourceLine,
5531
                                          aBody, aSourceURL, aSourceLine,
5513
                                          aClipboardText, aLevel) {
5532
                                          aClipboardText, aLevel, aWindow,
5533
                                          aScript) {
5514
    if (aBody instanceof Ci.nsIDOMNode && aClipboardText == null) {
5534
    if (aBody instanceof Ci.nsIDOMNode && aClipboardText == null) {
5515
      throw new Error("HUDService.createMessageNode(): DOM node supplied " +
5535
      throw new Error("HUDService.createMessageNode(): DOM node supplied " +
5516
                      "without any clipboard text");
5536
                      "without any clipboard text");
 Lines 5544-5551    Link Here 
5544
    // If a string was supplied for the body, turn it into a DOM node and an
5564
    // If a string was supplied for the body, turn it into a DOM node and an
5545
    // associated clipboard string now.
5565
    // associated clipboard string now.
5546
    aClipboardText = aClipboardText ||
5566
    aClipboardText = aClipboardText ||
5547
                     (aBody + (aSourceURL ? " @ " + aSourceURL : "") +
5567
                     this.makeClipboardText(aBody, aSourceURL, aSourceLine);
5548
                              (aSourceLine ? ":" + aSourceLine : ""));
5549
    aBody = aBody instanceof Ci.nsIDOMNode && !(aLevel == "dir") ?
5568
    aBody = aBody instanceof Ci.nsIDOMNode && !(aLevel == "dir") ?
5550
            aBody : aDocument.createTextNode(aBody);
5569
            aBody : aDocument.createTextNode(aBody);
5551
5570
 Lines 5626-5635    Link Here 
5626
5645
5627
    node.setAttribute("id", "console-msg-" + HUDService.sequenceId());
5646
    node.setAttribute("id", "console-msg-" + HUDService.sequenceId());
5628
5647
5648
    if (aSourceURL && aWindow) {
5649
      this.updateLocationInfoWithSourceMap(node, locationNode, aSourceURL,
5650
                                           aSourceLine, aWindow, aDocument,
5651
                                           aBody);
5652
    }
5653
5629
    return node;
5654
    return node;
5630
  },
5655
  },
5631
5656
5632
  /**
5657
  /**
5658
   * The panel which holds the original and generated location info for the
5659
   * currently focused console message. It is created the first time there is a
5660
   * console message which has source mapping info.
5661
   */
5662
  locationContainerPanel: null,
5663
5664
  /**
5665
   * What is the offset of a node? It is its offset from its offsetParent plus
5666
   * the offset of its parent. The offset of the null node is defined as 0 from
5667
   * the top and 0 from the left.
5668
   *
5669
   * @param aNode the node you would like the offset of.
5670
   * @returns an object with the left and top offsets of the given node.
5671
   */
5672
  elementOffset:
5673
  function ConsoleUtils_elementOffset(aNode) {
5674
    // This is the base case: no more offsetParents.
5675
    if (!aNode) {
5676
      return {
5677
        left: 0,
5678
        top: 0
5679
      };
5680
    }
5681
5682
    let { left, top } = aNode.getBoundingClientRect();
5683
    let offsetOfParent = ConsoleUtils_elementOffset(aNode.offsetParent);
5684
5685
    return {
5686
      left: left + offsetOfParent.left,
5687
      top: top + offsetOfParent.top
5688
    };
5689
  },
5690
5691
  /**
5692
   * Handles the logic for whether or not the mouseout handler for the
5693
   * locationContainerPanel should be called.
5694
   *
5695
   * @param aTarget The target element of a mouseout event.
5696
   * @returns boolean based on whether or not the mouseout handler for the
5697
   *          locationContainerPanel should fire.
5698
   */
5699
  mouseoutShouldFire:
5700
  function ConsoleUtils_mouseoutShouldFire(aTarget) {
5701
    if (aTarget === this.locationContainerPanel) {
5702
      return true;
5703
    }
5704
    let el = aTarget;
5705
    while (el) {
5706
      if (el === this.locationContainerPanel) {
5707
        return false;
5708
      }
5709
      el = el.parentNode;
5710
    }
5711
    return true;
5712
  },
5713
5714
  /**
5715
   * Asynchronously check if there is a source map for this message's
5716
   * script. If there is, update the locationNode and clipboardText.
5717
   *
5718
   * TODO: Use column numbers. See bug 679181
5719
   * TODO: Don't require aSourceURL and aWindow if aScript is supplied. See bug
5720
   *       679189.
5721
   *
5722
   * @param aParentNode The parent of the location node.
5723
   * @param aLocationNode The node which has the location info we would like to
5724
   *        update.
5725
   * @param aSourceURL The URL of the script which generated the message or null
5726
   *        if aScript is provided.
5727
   * @param aSourceLine The line number in the script where the message came
5728
   *        from.
5729
   * @param aWindow The window associated with aSourceURL from which the message
5730
   *        came, or null if aScript is provided.
5731
   * @param aDocument The document that contains aParentNode and aLocationNode.
5732
   * @param aBody The body of the message that was logged.
5733
   * @param aScript The script object that generated the message, or null if
5734
   *        aSourceURL and aWindow are provided.
5735
   */
5736
  updateLocationInfoWithSourceMap:
5737
  function ConsoleUtils_updateLocationInfoWithSourceMap (aParentNode, aLocationNode,
5738
                                                         aSourceURL, aSourceLine,
5739
                                                         aWindow, aDocument, aBody,
5740
                                                         aScript) {
5741
    sourceMapUtils.sourceMapForFilename(aSourceURL, aWindow, (function (aSourceMap) {
5742
      let { source, line } = aSourceMap.originalPositionFor({
5743
        line: aSourceLine,
5744
        column: 0
5745
      });
5746
5747
      if (source != null && line != null) {
5748
        // Resolve the original source url relative to the generated script.
5749
        try {
5750
          let url = Services.io.newURI(aSourceURL, null, null).QueryInterface(Ci.nsIURL);
5751
          source = url.resolve(source);
5752
        }
5753
        catch (e) {
5754
          Cu.reportError(e);
5755
          return;
5756
        }
5757
5758
        // Create the new elements we need.
5759
        if (!this.locationContainerPanel) {
5760
          this.locationContainerPanel = aDocument.createElementNS(XUL_NS, "panel");
5761
          this.locationContainerPanel.classList.add("webconsole-location-container");
5762
          aDocument.documentElement.appendChild(this.locationContainerPanel);
5763
        }
5764
        let sourceMappedLocationNode = this.createLocationNode(aDocument,
5765
                                                               source,
5766
                                                               line);
5767
5768
        // Replace the generated source and line with the original, mapped
5769
        // source and line.
5770
        aParentNode.replaceChild(sourceMappedLocationNode,
5771
                                 aLocationNode);
5772
5773
        // Create another location node with the original, mapped location info
5774
        // which will be put in the panel popup.
5775
        let sourceMappedLocationNodeForPanel = this.createLocationNode(aDocument,
5776
                                                                       source,
5777
                                                                       line);
5778
5779
        sourceMappedLocationNode.addEventListener("mouseover", (function (event) {
5780
          if (event.target === sourceMappedLocationNode) {
5781
            // Remove all the children from the panel, we are about to populate
5782
            // it with our own location infos.
5783
            this.locationContainerPanel.hidePopup();
5784
            while (this.locationContainerPanel.firstChild) {
5785
              this.locationContainerPanel
5786
                .removeChild(this.locationContainerPanel.firstChild);
5787
            }
5788
5789
            // Add the full original and generated location info to the panel.
5790
            this.locationContainerPanel.appendChild(sourceMappedLocationNodeForPanel);
5791
            this.locationContainerPanel.appendChild(aLocationNode);
5792
5793
            // Open the panel over the currently hovered over location node.
5794
            let { left, top } = this.elementOffset(sourceMappedLocationNode);
5795
            this.locationContainerPanel.openPopup(null, null, left, top,
5796
                                                  false, false, null);
5797
          }
5798
        }).bind(this), false);
5799
5800
        this.locationContainerPanel.addEventListener("mouseout", (function (event) {
5801
          if (this.mouseoutShouldFire(event.target)
5802
              || this.mouseoutShouldFire(event.relatedTarget)) {
5803
            this.locationContainerPanel.hidePopup();
5804
          }
5805
        }).bind(this), false);
5806
5807
        // Update the clipboard text to reflect the original source.
5808
        aParentNode.clipboardText = this.makeClipboardText(aBody, source, line);
5809
      }
5810
    }).bind(this), function (errno, error) {
5811
      if (errno != sourceMapUtils.ERRORS.NO_SOURCE_MAP) {
5812
        Cu.reportError(error);
5813
      }
5814
    });
5815
  },
5816
5817
  /**
5818
   * Creates the string that will be copied to the clipboard for individual
5819
   * console message nodes.
5820
   */
5821
  makeClipboardText:
5822
  function ConsoleUtils_makeClipboardText(aBody, aSourceURL, aSourceLine) {
5823
    return aBody
5824
            + (aSourceURL ? " @ " + aSourceURL : "")
5825
            + (aSourceLine ? ":" + aSourceLine : "");
5826
  },
5827
5828
  /**
5633
   * Adjusts the category and severity of the given message, clearing the old
5829
   * Adjusts the category and severity of the given message, clearing the old
5634
   * category and severity if present.
5830
   * category and severity if present.
5635
   *
5831
   *
(-)a/browser/devtools/webconsole/test/browser/Makefile.in (+5 lines)
Line     Link Here 
 Lines 62-67    Link Here 
62
	browser_webconsole_console_logging_api.js \
62
	browser_webconsole_console_logging_api.js \
63
	browser_webconsole_consoleonpage.js \
63
	browser_webconsole_consoleonpage.js \
64
	browser_webconsole_chrome.js \
64
	browser_webconsole_chrome.js \
65
	browser_webconsole_sourcemap.js \
65
	browser_webconsole_execution_scope.js \
66
	browser_webconsole_execution_scope.js \
66
	browser_webconsole_history.js \
67
	browser_webconsole_history.js \
67
	browser_webconsole_hud_getters.js \
68
	browser_webconsole_hud_getters.js \
 Lines 149-154    Link Here 
149
	$(NULL)
150
	$(NULL)
150
151
151
_BROWSER_TEST_PAGES = \
152
_BROWSER_TEST_PAGES = \
153
	test-sourcemap.html \
154
	test-sourcemap.js \
155
	test-sourcemap.js.map \
156
	test-sourcemap.coffee \
152
	test-console.html \
157
	test-console.html \
153
	test-network.html \
158
	test-network.html \
154
	test-network-request.html \
159
	test-network-request.html \
(-)a/browser/devtools/webconsole/test/browser/browser_webconsole_sourcemap.js (+105 lines)
Line     Link Here 
Line 0    Link Here 
1
/* ***** BEGIN LICENSE BLOCK *****
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3
 *
4
 * The contents of this file are subject to the Mozilla Public License Version
5
 * 1.1 (the "License"); you may not use this file except in compliance with
6
 * the License. You may obtain a copy of the License at
7
 * http://www.mozilla.org/MPL/
8
 *
9
 * Software distributed under the License is distributed on an "AS IS" basis,
10
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
 * for the specific language governing rights and limitations under the
12
 * License.
13
 *
14
 * The Original Code is DevTools test code.
15
 *
16
 * The Initial Developer of the Original Code is Mozilla Foundation.
17
 * Portions created by the Initial Developer are Copyright (C) 2010
18
 * the Initial Developer. All Rights Reserved.
19
 *
20
 * Contributor(s):
21
 *   Nick Fitzgerald <nfitzgerald@mozilla.com> (original author)
22
 *
23
 * Alternatively, the contents of this file may be used under the terms of
24
 * either the GNU General Public License Version 2 or later (the "GPL"), or
25
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26
 * in which case the provisions of the GPL or the LGPL are applicable instead
27
 * of those above. If you wish to allow use of your version of this file only
28
 * under the terms of either the GPL or the LGPL, and not to allow others to
29
 * use your version of this file under the terms of the MPL, indicate your
30
 * decision by deleting the provisions above and replace them with the notice
31
 * and other provisions required by the GPL or the LGPL. If you do not delete
32
 * the provisions above, a recipient may use your version of this file under
33
 * the terms of any one of the MPL, the GPL or the LGPL.
34
 *
35
 * ***** END LICENSE BLOCK ***** */
36
37
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/browser/test-sourcemap.html";
38
39
// The call to update the location info with data from a source map is
40
// async. How long should we wait? There is no event fired when the info is
41
// updated, so we can't ever know the exact time it happens.
42
const TIME_TO_WAIT_FOR_SOURCE_MAPS = 250;
43
44
function test() {
45
  addTab(TEST_URI);
46
47
  browser.addEventListener("DOMContentLoaded", testSourceMapIntegration, false);
48
}
49
50
function testSourceMapIntegration() {
51
  browser.removeEventListener("DOMContentLoaded", testSourceMapIntegration, false);
52
  openConsole();
53
  content.location = TEST_URI;
54
  browser.addEventListener("DOMContentLoaded", beginSourceMapIntegrationTests, false);
55
}
56
57
function beginSourceMapIntegrationTests() {
58
  browser.removeEventListener("DOMContentLoaded", beginSourceMapIntegrationTests, false);
59
60
  let hud = HUDService.getHudByWindow(content);
61
  ok(hud, "webconsole was opened");
62
63
  let outputNode = hud.outputNode;
64
  ok(outputNode, "the webconsole has an outpud node");
65
66
  let messages = [].slice.call(outputNode.querySelectorAll('richlistitem.hud-msg-node'));
67
  is(messages.length, 4, "4 messages: 3 console.logs, and 1 error");
68
69
  // Check the generated positions before the webconsole uses the source map
70
  // info to get the original source positions.
71
  messages.forEach(testSourceMapGeneratedPosition);
72
73
  // Check the original source mapped positions, after the console has had
74
  // time to fetch the source map and parse it, etc.
75
  content.setTimeout(testSourceMapOriginalPositions, TIME_TO_WAIT_FOR_SOURCE_MAPS, messages);
76
}
77
78
function testSourceMapGeneratedPosition(msgNode, idx) {
79
  is(msgNode.textContent.trim(), "This should be line " + (idx + 1) * 2,
80
     "message content is correct");
81
82
  let locationNode = msgNode.querySelector("label.webconsole-location");
83
  ok(locationNode, "each of these message nodes should have a location");
84
85
  is(locationNode.value,
86
     ConsoleUtils.abbreviateSourceURL("test-sourcemap.js") + ":" + (idx + 1),
87
     "generated position is correct");
88
}
89
90
function testSourceMapOriginalPosition(msgNode, idx) {
91
  is(msgNode.textContent.trim(), "This should be line " + (idx + 1) * 2,
92
     "message content is correct");
93
94
  let locationNode = msgNode.querySelector("label.webconsole-location");
95
  ok(locationNode, "each of these message nodes should have a location");
96
97
  is(locationNode.value,
98
     ConsoleUtils.abbreviateSourceURL("test-sourcemap.coffee") + ":" + (idx + 1) * 2,
99
     "original, source-mapped position is correct");
100
}
101
102
function testSourceMapOriginalPositions(messages) {
103
  messages.forEach(testSourceMapOriginalPosition);
104
  finishTest();
105
}
(-)a/browser/devtools/webconsole/test/browser/test-sourcemap.coffee (+8 lines)
Line     Link Here 
Line 0    Link Here 
1
# This is a comment
2
console.log "This should be line 2"
3
# This is another comment
4
console.log "This should be line 4"
5
# Wu-Tang Clan
6
console.log "This should be line 6"
7
# Bad news ahead...
8
throw new Error "This should be line 8"
(-)a/browser/devtools/webconsole/test/browser/test-sourcemap.html (+9 lines)
Line     Link Here 
Line 0    Link Here 
1
<!DOCTYPE HTML>
2
<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
3
    <title>Source Map Test</title>
4
  </head>
5
  <body>
6
    <h1 id="header">Testing the Source Map</h1>
7
    <script src="./test-sourcemap.js"></script>
8
  </body>
9
</html>
(-)a/browser/devtools/webconsole/test/browser/test-sourcemap.js (+5 lines)
Line     Link Here 
Line 0    Link Here 
1
console.log("This should be line 2");
2
console.log("This should be line 4");
3
console.log("This should be line 6");
4
throw new Error("This should be line 8");
5
//@sourceMappingURL=test-sourcemap.js.map
(-)a/browser/devtools/webconsole/test/browser/test-sourcemap.js.map (+7 lines)
Line     Link Here 
Line 0    Link Here 
1
{
2
  "version": 3,
3
  "file": "test-sourcemap.js",
4
  "sources": ["test-sourcemap.coffee"],
5
  "names": [],
6
  "mappings": "AAEA;AAEA;AAEA;AAEA"
7
}
(-)a/toolkit/themes/pinstripe/global/webConsole.css (+2 lines)
Line     Link Here 
 Lines 129-134    Link Here 
129
  -moz-margin-end: 6px;
129
  -moz-margin-end: 6px;
130
  width: 10em;
130
  width: 10em;
131
  text-align: end;
131
  text-align: end;
132
  display: block;
133
  font-family: monospace;
132
}
134
}
133
135
134
.hud-msg-node[selected="true"] > .webconsole-timestamp,
136
.hud-msg-node[selected="true"] > .webconsole-timestamp,

Return to bug 670002