diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc
index 4947fae8a8c9..fbbabf288d2d 100644
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -522,7 +522,7 @@
-
+
diff --git a/browser/devtools/commandline/CmdDbg.jsm b/browser/devtools/commandline/CmdDbg.jsm
index c74cbf9578e4..f30593598580 100644
--- a/browser/devtools/commandline/CmdDbg.jsm
+++ b/browser/devtools/commandline/CmdDbg.jsm
@@ -18,6 +18,47 @@ gcli.addCommand({
manual: gcli.lookup("dbgManual")
});
+/**
+ * 'dbg open' command
+ */
+gcli.addCommand({
+ name: "dbg open",
+ description: gcli.lookup("dbgOpen"),
+ params: [],
+ exec: function (args, context) {
+ let win = context.environment.chromeDocument.defaultView;
+ let tab = win.gBrowser.selectedTab;
+ let dbg = win.DebuggerUI.findDebugger();
+
+ if (dbg) {
+ if (dbg.ownerTab !== tab) {
+ win.DebuggerUI.toggleDebugger();
+ }
+
+ return;
+ }
+
+ win.DebuggerUI.toggleDebugger();
+ }
+});
+
+/**
+ * 'dbg close' command
+ */
+gcli.addCommand({
+ name: "dbg close",
+ description: gcli.lookup("dbgClose"),
+ params: [],
+ exec: function (args, context) {
+ let win = context.environment.chromeDocument.defaultView;
+ let tab = win.gBrowser.selectedTab;
+ let dbg = win.DebuggerUI.findDebugger();
+
+ if (dbg) {
+ dbg.close();
+ }
+ }
+});
/**
* 'dbg interrupt' command
diff --git a/browser/devtools/commandline/test/browser_dbg_cmd.js b/browser/devtools/commandline/test/browser_dbg_cmd.js
index eef119a5cb29..8011c8c9fa2f 100644
--- a/browser/devtools/commandline/test/browser_dbg_cmd.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd.js
@@ -8,8 +8,13 @@ function test() {
}
function testDbgCmd() {
- let pane = DebuggerUI.toggleDebugger();
- ok(pane, "toggleDebugger() should return a pane.");
+ DeveloperToolbarTest.exec({
+ typed: "dbg open",
+ blankOutput: true
+ });
+
+ let pane = DebuggerUI.findDebugger();
+ ok(pane, "Debugger was opened.");
let frame = pane._frame;
frame.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) {
@@ -41,9 +46,14 @@ function testDbgCmd() {
cmd("dbg continue", function() {
cmd("dbg continue", function() {
is(output.value, "dbg continue", "debugger continued");
- pane.contentWindow.gClient.close(function() {
- finish();
+ DeveloperToolbarTest.exec({
+ typed: "dbg close",
+ blankOutput: true
});
+
+ let dbg = DebuggerUI.findDebugger();
+ ok(!dbg, "Debugger was closed.");
+ finish();
});
});
});
diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js
index f7e5a545bb42..57d770deb6ae 100644
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -9,6 +9,7 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
+const NEW_SCRIPT_DISPLAY_DELAY = 100; // ms
const FRAME_STEP_CACHE_DURATION = 100; // ms
const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const SCRIPTS_URL_MAX_LENGTH = 64; // chars
@@ -929,7 +930,11 @@ SourceScripts.prototype = {
return;
}
- this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
+ this._addScript({
+ url: aPacket.url,
+ startLine: aPacket.startLine,
+ source: aPacket.source
+ }, true);
let preferredScriptUrl = DebuggerView.Scripts.preferredScriptUrl;
@@ -937,15 +942,21 @@ SourceScripts.prototype = {
if (aPacket.url === DebuggerView.Scripts.preferredScriptUrl) {
DebuggerView.Scripts.selectScript(aPacket.url);
}
- // ..or the first entry if there's not one selected yet.
- else if (!DebuggerView.Scripts.selected) {
- DebuggerView.Scripts.selectIndex(0);
- // Selecting a script would make it "preferred", which is a lie here,
- // because we're only displaying a script to make sure there's always
- // something available in the SourceEditor and the scripts menulist.
- // Hence the need revert back to the initial preferred script, just
- // in case it will be available soon.
- DebuggerView.Scripts.preferredScriptUrl = preferredScriptUrl;
+ // ..or the first entry if there's none selected yet after a while
+ else {
+ window.setTimeout(function() {
+ // If after a certain delay the preferred script still wasn't received,
+ // just give up on waiting and display the first entry.
+ if (!DebuggerView.Scripts.selected) {
+ DebuggerView.Scripts.selectIndex(0);
+ // Selecting a script would make it "preferred", which is a lie here,
+ // because we're only displaying a script to make sure there's always
+ // something available in the SourceEditor and the scripts menulist.
+ // Hence the need revert back to the initial preferred script, just
+ // in case it will be available soon.
+ DebuggerView.Scripts.preferredScriptUrl = preferredScriptUrl;
+ }
+ }, NEW_SCRIPT_DISPLAY_DELAY);
}
// If there are any stored breakpoints for this script, display them again,
@@ -1206,7 +1217,11 @@ SourceScripts.prototype = {
*/
showScript: function SS_showScript(aScript, aOptions = {}) {
if (aScript.loaded) {
- this._onShowScript(aScript, aOptions);
+ // Scripts may take a longer time to load than expected, therefore the
+ // required one may change at any time after a previous request was made.
+ if (aScript.url === DebuggerView.Scripts.selected) {
+ this._onShowScript(aScript, aOptions);
+ }
return;
}
@@ -1217,7 +1232,7 @@ SourceScripts.prototype = {
// Notify that we need to load a script file.
DebuggerController.dispatchEvent("Debugger:LoadSource", {
- url: aScript.url,
+ script: aScript,
options: aOptions
});
},
@@ -1256,84 +1271,22 @@ SourceScripts.prototype = {
},
/**
- * Handles notifications to load a source script from the cache or from a
- * local file.
- *
- * XXX: It may be better to use nsITraceableChannel to get to the sources
- * without relying on caching when we can (not for eval, etc.):
- * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
+ * Handles notifications to load a source script.
*/
_onLoadSource: function SS__onLoadSource(aEvent) {
- let url = aEvent.detail.url;
+ let script = aEvent.detail.script;
let options = aEvent.detail.options;
- let self = this;
- switch (Services.io.extractScheme(url)) {
- case "file":
- case "chrome":
- case "resource":
- try {
- NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
- if (!Components.isSuccessCode(aStatus)) {
- return self._logError(url, aStatus);
- }
- let source = NetUtil.readInputStreamToString(aStream, aStream.available());
- source = self._convertToUnicode(source);
- self._onLoadSourceFinished(url, source, null, options);
- aStream.close();
- });
- } catch (ex) {
- return self._logError(url, ex.name);
- }
- break;
+ let sourceClient = this.activeThread.source(script.source);
+ sourceClient.source(function (aResponse) {
+ if (aResponse.error) {
+ return this._logError(script.url, -1);
+ }
- default:
- let channel = Services.io.newChannel(url, null, null);
- let chunks = [];
- let streamListener = {
- onStartRequest: function(aRequest, aContext, aStatusCode) {
- if (!Components.isSuccessCode(aStatusCode)) {
- return self._logError(url, aStatusCode);
- }
- },
- onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
- chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
- },
- onStopRequest: function(aRequest, aContext, aStatusCode) {
- if (!Components.isSuccessCode(aStatusCode)) {
- return self._logError(url, aStatusCode);
- }
- let source = self._convertToUnicode(chunks.join(""), channel.contentCharset);
- self._onLoadSourceFinished(url, source, channel.contentType, options);
- }
- };
-
- channel.loadFlags = channel.LOAD_FROM_CACHE;
- channel.asyncOpen(streamListener, null);
- break;
- }
- },
-
- /**
- * Convert a given string, encoded in a given character set, to unicode.
- * @param string aString
- * A string.
- * @param string aCharset
- * A character set.
- * @return string
- * A unicode string.
- */
- _convertToUnicode: function SS__convertToUnicode(aString, aCharset) {
- // Decoding primitives.
- let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
-
- try {
- converter.charset = aCharset || "UTF-8";
- return converter.ConvertToUnicode(aString);
- } catch(e) {
- return aString;
- }
+ this._onLoadSourceFinished(script.url,
+ aResponse.source,
+ options);
+ }.bind(this));
},
/**
@@ -1344,14 +1297,12 @@ SourceScripts.prototype = {
* The URL of the source script.
* @param string aSourceText
* The text of the source script.
- * @param string aContentType
- * The content type of the source script.
* @param object aOptions [optional]
* Additional options for showing the script. Supported options:
* - targetLine: place the editor at the given line number.
*/
_onLoadSourceFinished:
- function SS__onLoadSourceFinished(aScriptUrl, aSourceText, aContentType, aOptions) {
+ function SS__onLoadSourceFinished(aScriptUrl, aSourceText, aOptions) {
let element = DebuggerView.Scripts.getScriptByLocation(aScriptUrl);
// Tab navigated before we got a chance to finish loading and displaying
@@ -1367,7 +1318,6 @@ SourceScripts.prototype = {
script.loaded = true;
script.text = aSourceText;
- script.contentType = aContentType;
element.setUserData("sourceScript", script, null);
if (aOptions.silent) {
diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js
index 79b254be1217..4e9a9613bb9e 100644
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -468,7 +468,7 @@ GlobalSearchView.prototype = {
continue;
}
DebuggerController.dispatchEvent("Debugger:LoadSource", {
- url: url,
+ script: DebuggerView.Scripts.getScriptByLocation(url).getUserData("sourceScript"),
options: {
silent: true,
callback: aFetchCallback
diff --git a/browser/devtools/debugger/test/browser_dbg_location-changes-new.js b/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
index 28040421b4b4..d5dbc9575aa0 100644
--- a/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
+++ b/browser/devtools/debugger/test/browser_dbg_location-changes-new.js
@@ -59,7 +59,7 @@ function testLocationChange()
ok(true, "tabNavigated event was fired.");
info("Still attached to the tab.");
- gDebugger.addEventListener("Debugger:AfterNewScript", function _onEvent(aEvent) {
+ gDebugger.addEventListener("Debugger:ScriptShown", function _onEvent(aEvent) {
gDebugger.removeEventListener(aEvent.type, _onEvent);
isnot(gDebugger.DebuggerView.Scripts.selected, null,
diff --git a/browser/devtools/debugger/test/browser_dbg_reload-same-script.js b/browser/devtools/debugger/test/browser_dbg_reload-same-script.js
index bc23e6ff0b15..5b4673d0b1d9 100644
--- a/browser/devtools/debugger/test/browser_dbg_reload-same-script.js
+++ b/browser/devtools/debugger/test/browser_dbg_reload-same-script.js
@@ -170,18 +170,24 @@ function test()
{
let scriptsView = gView.Scripts;
let scriptLocations = scriptsView.scriptLocations;
- info("Available scripts: " + scriptLocations);
- if (scriptLocations.length === 2) {
+ // Poll every few milliseconds until the scripts are retrieved.
+ let count = 0;
+ let intervalID = window.setInterval(function() {
+ dump("count: " + count + " ");
+ if (++count > 50) {
+ ok(false, "Timed out while polling for the scripts.");
+ closeDebuggerAndFinish();
+ }
+ if (scriptLocations.length !== 2) {
+ return;
+ }
+ info("Available scripts: " + scriptLocations);
+
// We got all the scripts, it's safe to switch.
+ window.clearInterval(intervalID);
scriptsView.selectScript(scriptLocations[index]);
- return;
- }
-
- window.addEventListener("Debugger:AfterNewScript", function _onEvent(aEvent) {
- window.removeEventListener(aEvent.type, _onEvent);
- switchScript(index);
- });
+ }, 100);
}
function reloadPage()
diff --git a/browser/devtools/markupview/markup-view.xhtml b/browser/devtools/markupview/markup-view.xhtml
index 3d0893add337..191655ec4c70 100644
--- a/browser/devtools/markupview/markup-view.xhtml
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -11,7 +11,7 @@
-
+
diff --git a/browser/devtools/responsivedesign/responsivedesign.jsm b/browser/devtools/responsivedesign/responsivedesign.jsm
index d6b0db95ae42..c43a071594e4 100644
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -9,6 +9,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/devtools/FloatingScrollbars.jsm");
var EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
@@ -159,6 +160,8 @@ function ResponsiveUI(aWindow, aTab)
this.rotate();
}
} catch(e) {}
+
+ switchToFloatingScrollbars(this.tab);
}
ResponsiveUI.prototype = {
@@ -179,6 +182,7 @@ ResponsiveUI.prototype = {
close: function RUI_unload() {
if (this.closing)
return;
+ switchToNativeScrollbars(this.tab);
this.closing = true;
this.unCheckMenus();
diff --git a/browser/devtools/responsivedesign/test/browser_responsiveui.js b/browser/devtools/responsivedesign/test/browser_responsiveui.js
index 54729bfcf548..071f32d34bb8 100644
--- a/browser/devtools/responsivedesign/test/browser_responsiveui.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveui.js
@@ -31,11 +31,27 @@ function test() {
instance = gBrowser.selectedTab.__responsiveUI;
ok(instance, "instance of the module is attached to the tab.");
+ ensureScrollbarsAreFloating();
+
instance.transitionsEnabled = false;
testPresets();
}
+ function ensureScrollbarsAreFloating() {
+ let body = gBrowser.contentDocument.body;
+ let html = gBrowser.contentDocument.documentElement;
+
+ let originalWidth = body.getBoundingClientRect().width;
+
+ html.style.overflowY = "scroll"; // Force scrollbars
+ // Flush. Should not be needed as getBoundingClientRect() should flush,
+ // but just in case.
+ gBrowser.contentWindow.getComputedStyle(html).overflowY;
+ let newWidth = body.getBoundingClientRect().width;
+ is(originalWidth, newWidth, "Floating scrollbars are presents");
+ }
+
function testPresets() {
function testOnePreset(c) {
if (c == 0) {
diff --git a/browser/devtools/shared/DeveloperToolbar.jsm b/browser/devtools/shared/DeveloperToolbar.jsm
index a4606f3bb02a..7873890a28ae 100644
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -765,7 +765,7 @@ OutputPanel.prototype.remove = function OP_remove()
this.canHide = true;
}
- if (this._panel) {
+ if (this._panel && this._panel.hidePopup) {
this._panel.hidePopup();
}
@@ -992,7 +992,9 @@ TooltipPanel.prototype.remove = function TP_remove()
if (isLinux) {
this.canHide = true;
}
- this._panel.hidePopup();
+ if (this._panel && this._panel.hidePopup) {
+ this._panel.hidePopup();
+ }
};
/**
diff --git a/browser/devtools/shared/FloatingScrollbars.jsm b/browser/devtools/shared/FloatingScrollbars.jsm
new file mode 100644
index 000000000000..3ed738c88f34
--- /dev/null
+++ b/browser/devtools/shared/FloatingScrollbars.jsm
@@ -0,0 +1,126 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const EXPORTED_SYMBOLS = [ "switchToFloatingScrollbars", "switchToNativeScrollbars" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+let URL = Services.io.newURI("chrome://browser/skin/devtools/floating-scrollbars.css", null, null);
+
+let trackedTabs = new WeakMap();
+
+/**
+ * Switch to floating scrollbars, à la mobile.
+ *
+ * @param aTab the targeted tab.
+ *
+ */
+function switchToFloatingScrollbars(aTab) {
+ let mgr = trackedTabs.get(aTab);
+ if (!mgr) {
+ mgr = new ScrollbarManager(aTab);
+ }
+ mgr.switchToFloating();
+}
+
+/**
+ * Switch to original native scrollbars.
+ *
+ * @param aTab the targeted tab.
+ *
+ */
+function switchToNativeScrollbars(aTab) {
+ let mgr = trackedTabs.get(aTab);
+ if (mgr) {
+ mgr.reset();
+ }
+}
+
+function ScrollbarManager(aTab) {
+ trackedTabs.set(aTab, this);
+
+ this.attachedTab = aTab;
+ this.attachedBrowser = aTab.linkedBrowser;
+
+ this.reset = this.reset.bind(this);
+ this.switchToFloating = this.switchToFloating.bind(this);
+
+ this.attachedTab.addEventListener("TabClose", this.reset, true);
+ this.attachedBrowser.addEventListener("DOMContentLoaded", this.switchToFloating, true);
+}
+
+ScrollbarManager.prototype = {
+ get win() {
+ return this.attachedBrowser.contentWindow;
+ },
+
+ /*
+ * Change the look of the scrollbars.
+ */
+ switchToFloating: function() {
+ let windows = this.getInnerWindows(this.win);
+ windows.forEach(this.injectStyleSheet);
+ this.forceStyle();
+ },
+
+
+ /*
+ * Reset the look of the scrollbars.
+ */
+ reset: function() {
+ let windows = this.getInnerWindows(this.win);
+ windows.forEach(this.removeStyleSheet);
+ this.forceStyle(this.attachedBrowser);
+ this.attachedBrowser.removeEventListener("DOMContentLoaded", this.switchToFloating, true);
+ this.attachedTab.removeEventListener("TabClose", this.reset, true);
+ trackedTabs.delete(this.attachedTab);
+ },
+
+ /*
+ * Toggle the display property of the window to force the style to be applied.
+ */
+ forceStyle: function() {
+ let parentWindow = this.attachedBrowser.ownerDocument.defaultView;
+ let display = parentWindow.getComputedStyle(this.attachedBrowser).display; // Save display value
+ this.attachedBrowser.style.display = "none";
+ parentWindow.getComputedStyle(this.attachedBrowser).display; // Flush
+ this.attachedBrowser.style.display = display; // Restore
+ },
+
+ /*
+ * return all the window objects present in the hiearchy of a window.
+ */
+ getInnerWindows: function(win) {
+ let iframes = win.document.querySelectorAll("iframe");
+ let innerWindows = [];
+ for (let iframe of iframes) {
+ innerWindows = innerWindows.concat(this.getInnerWindows(iframe.contentWindow));
+ }
+ return [win].concat(innerWindows);
+ },
+
+ /*
+ * Append the new scrollbar style.
+ */
+ injectStyleSheet: function(win) {
+ let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ try {
+ winUtils.loadSheet(URL, win.AGENT_SHEET);
+ }catch(e) {}
+ },
+
+ /*
+ * Remove the injected stylesheet.
+ */
+ removeStyleSheet: function(win) {
+ let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ try {
+ winUtils.removeSheet(URL, win.AGENT_SHEET);
+ }catch(e) {}
+ },
+}
diff --git a/browser/devtools/shared/test/browser_promise_basic.js b/browser/devtools/shared/test/browser_promise_basic.js
index 9e78c8426c0a..2466e4982810 100644
--- a/browser/devtools/shared/test/browser_promise_basic.js
+++ b/browser/devtools/shared/test/browser_promise_basic.js
@@ -4,7 +4,7 @@
// Tests that our Promise implementation works properly
let tempScope = {};
-Cu.import("resource:///modules/devtools/Promise.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/_Promise.jsm", tempScope);
let Promise = tempScope.Promise;
function test() {
diff --git a/browser/devtools/shared/test/browser_templater_basic.js b/browser/devtools/shared/test/browser_templater_basic.js
index 376daee2215b..6d83220f379d 100644
--- a/browser/devtools/shared/test/browser_templater_basic.js
+++ b/browser/devtools/shared/test/browser_templater_basic.js
@@ -11,7 +11,7 @@
var imports = {};
Cu.import("resource:///modules/devtools/Templater.jsm", imports);
-Cu.import("resource:///modules/devtools/Promise.jsm", imports);
+Cu.import("resource://gre/modules/devtools/_Promise.jsm", imports);
function test() {
addTab("http://example.com/browser/browser/devtools/shared/test/browser_templater_basic.html", function() {
diff --git a/browser/devtools/styleinspector/CssRuleView.jsm b/browser/devtools/styleinspector/CssRuleView.jsm
index 296837b267bf..df0a9139bb3a 100644
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -1629,13 +1629,6 @@ TextPropertyEditor.prototype = {
tabindex: "0",
});
- editableField({
- start: this._onStartEditing,
- element: this.valueSpan,
- done: this._onValueDone,
- advanceChars: ';'
- });
-
// Save the initial value as the last committed value,
// for restoring after pressing escape.
this.committed = { name: this.prop.name,
@@ -1655,6 +1648,15 @@ TextPropertyEditor.prototype = {
this.computed = createChild(this.element, "ul", {
class: "ruleview-computedlist",
});
+
+ editableField({
+ start: this._onStartEditing,
+ element: this.valueSpan,
+ done: this._onValueDone,
+ validate: this._validate.bind(this),
+ warning: this.warning,
+ advanceChars: ';'
+ });
},
/**
@@ -1841,17 +1843,30 @@ TextPropertyEditor.prototype = {
/**
* Validate this property.
*
+ * @param {String} [aValue]
+ * Override the actual property value used for validation without
+ * applying property values e.g. validate as you type.
+ *
* @returns {Boolean}
* True if the property value is valid, false otherwise.
*/
- _validate: function TextPropertyEditor_validate()
+ _validate: function TextPropertyEditor_validate(aValue)
{
let name = this.prop.name;
- let value = this.prop.value;
+ let value = typeof aValue == "undefined" ? this.prop.value : aValue;
+ let val = this._parseValue(value);
let style = this.doc.createElementNS(HTML_NS, "div").style;
+ let prefs = Services.prefs;
- style.setProperty(name, value, null);
+ // We toggle output of errors whilst the user is typing a property value.
+ let prefVal = Services.prefs.getBoolPref("layout.css.report_errors");
+ prefs.setBoolPref("layout.css.report_errors", false);
+ try {
+ style.setProperty(name, val.value, val.priority);
+ } finally {
+ prefs.setBoolPref("layout.css.report_errors", prefVal);
+ }
return !!style.getPropertyValue(name);
},
};
@@ -1971,6 +1986,7 @@ function InplaceEditor(aOptions, aEvent)
this._onBlur = this._onBlur.bind(this);
this._onKeyPress = this._onKeyPress.bind(this);
this._onInput = this._onInput.bind(this);
+ this._onKeyup = this._onKeyup.bind(this);
this._createInput();
this._autosize();
@@ -1998,6 +2014,13 @@ function InplaceEditor(aOptions, aEvent)
this.input.addEventListener("input", this._onInput, false);
this.input.addEventListener("mousedown", function(aEvt) { aEvt.stopPropagation(); }, false);
+ this.warning = aOptions.warning;
+ this.validate = aOptions.validate;
+
+ if (this.warning && this.validate) {
+ this.input.addEventListener("keyup", this._onKeyup, false);
+ }
+
if (aOptions.start) {
aOptions.start(this, aEvent);
}
@@ -2026,6 +2049,7 @@ InplaceEditor.prototype = {
this.input.removeEventListener("blur", this._onBlur, false);
this.input.removeEventListener("keypress", this._onKeyPress, false);
+ this.input.removeEventListener("keyup", this._onKeyup, false);
this.input.removeEventListener("oninput", this._onInput, false);
this._stopAutosize();
@@ -2128,10 +2152,12 @@ InplaceEditor.prototype = {
/**
* Handle loss of focus by calling done if it hasn't been called yet.
*/
- _onBlur: function InplaceEditor_onBlur(aEvent)
+ _onBlur: function InplaceEditor_onBlur(aEvent, aDoNotClear)
{
this._apply();
- this._clear();
+ if (!aDoNotClear) {
+ this._clear();
+ }
},
_onKeyPress: function InplaceEditor_onKeyPress(aEvent)
@@ -2193,11 +2219,26 @@ InplaceEditor.prototype = {
}
},
+ /**
+ * Handle the input field's keyup event.
+ */
+ _onKeyup: function(aEvent) {
+ // Validate the entered value.
+ this.warning.hidden = this.validate(this.input.value);
+ this._applied = false;
+ this._onBlur(null, true);
+ },
+
/**
* Handle changes the input text.
*/
_onInput: function InplaceEditor_onInput(aEvent)
{
+ // Validate the entered value.
+ if (this.warning && this.validate) {
+ this.warning.hidden = this.validate(this.input.value);
+ }
+
// Update size if we're autosizing.
if (this._measurement) {
this._updateSize();
diff --git a/browser/devtools/styleinspector/test/browser_ruleview_ui.js b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
index 89a8f09e56c6..f6e39d90eb13 100644
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -170,6 +170,8 @@ function testEditProperty()
for each (let ch in "red;") {
EventUtils.sendChar(ch, ruleDialog);
+ is(propEditor.warning.hidden, ch == "d" || ch == ";",
+ "warning triangle is hidden or shown as appropriate");
}
});
for each (let ch in "border-color:") {
@@ -201,7 +203,6 @@ function testDisableProperty()
function finishTest()
{
- ruleView.element.removeEventListener("CssRuleViewChanged", ruleViewChanged, false);
ruleView.clear();
ruleDialog.close();
ruleDialog = ruleView = null;
diff --git a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
index cda44218d6f0..d88efe3d0232 100644
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -15,6 +15,10 @@
- application menu item that opens the debugger UI. -->
+
+
+
diff --git a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
index 8983e4d2741e..18ec2afd9794 100644
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -380,6 +380,14 @@ dbgDesc=Manage debugger
# set of commands that control the debugger.
dbgManual=Commands to interrupt or resume the main thread, step in, out and over lines of code
+# LOCALIZATION NOTE (dbgOpen) A very short string used to describe the function
+# of the dbg open command.
+dbgOpen=Open the debugger
+
+# LOCALIZATION NOTE (dbgClose) A very short string used to describe the function
+# of the dbg close command.
+dbgClose=Close the debugger
+
# LOCALIZATION NOTE (dbgInterrupt) A very short string used to describe the
# function of the dbg interrupt command.
dbgInterrupt=Pauses the main thread
diff --git a/browser/themes/gnomestripe/devtools/floating-scrollbars.css b/browser/themes/gnomestripe/devtools/floating-scrollbars.css
new file mode 100644
index 000000000000..e27de04dedca
--- /dev/null
+++ b/browser/themes/gnomestripe/devtools/floating-scrollbars.css
@@ -0,0 +1,33 @@
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+scrollbar {
+ -moz-appearance: none !important;
+ position: relative;
+ background-color: transparent;
+ background-image: none;
+ z-index: 2147483647;
+ padding: 2px;
+}
+
+scrollbar[orient="vertical"] {
+ -moz-margin-start: -8px;
+ min-width: 8px;
+ max-width: 8px;
+}
+
+scrollbar[orient="horizontal"] {
+ margin-top: -8px;
+ min-height: 8px;
+ max-height: 8px;
+}
+
+scrollbar thumb {
+ -moz-appearance: none !important;
+ border-width: 0px !important;
+ background-color: rgba(0,0,0,0.2) !important;
+ border-radius: 3px !important;
+}
+
+scrollbar scrollbarbutton, scrollbar gripper {
+ display: none;
+}
diff --git a/browser/themes/gnomestripe/jar.mn b/browser/themes/gnomestripe/jar.mn
index 5402fd59e92a..df1daa3c2be5 100644
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -165,6 +165,7 @@ browser.jar:
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
+ skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-16-throbber.png
skin/classic/browser/sync-16.png
diff --git a/browser/themes/pinstripe/devtools/floating-scrollbars.css b/browser/themes/pinstripe/devtools/floating-scrollbars.css
new file mode 100644
index 000000000000..25e6d29de030
--- /dev/null
+++ b/browser/themes/pinstripe/devtools/floating-scrollbars.css
@@ -0,0 +1,30 @@
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+scrollbar {
+ -moz-appearance: none;
+ position: relative;
+ background-color: transparent;
+ background-image: none;
+ border: 0px solid transparent;
+ z-index: 2147483647;
+ -moz-box-align: start;
+ padding: 2px;
+}
+
+scrollbar[orient="vertical"] {
+ -moz-margin-start: -8px;
+ min-width: 8px;
+ max-width: 8px;
+}
+
+scrollbar[orient="horizontal"] {
+ margin-top: -8px;
+ min-height: 8px;
+ max-height: 8px;
+}
+
+thumb {
+ -moz-appearance: none !important;
+ background-color: rgba(0,0,0,0.2);
+ border-radius: 3px;
+}
diff --git a/browser/themes/pinstripe/jar.mn b/browser/themes/pinstripe/jar.mn
index aa30f1336d98..1fff34f6ee05 100644
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -235,6 +235,7 @@ browser.jar:
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
+ skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-throbber.png
skin/classic/browser/sync-16.png
diff --git a/browser/themes/winstripe/devtools/floating-scrollbars.css b/browser/themes/winstripe/devtools/floating-scrollbars.css
new file mode 100644
index 000000000000..e27de04dedca
--- /dev/null
+++ b/browser/themes/winstripe/devtools/floating-scrollbars.css
@@ -0,0 +1,33 @@
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+scrollbar {
+ -moz-appearance: none !important;
+ position: relative;
+ background-color: transparent;
+ background-image: none;
+ z-index: 2147483647;
+ padding: 2px;
+}
+
+scrollbar[orient="vertical"] {
+ -moz-margin-start: -8px;
+ min-width: 8px;
+ max-width: 8px;
+}
+
+scrollbar[orient="horizontal"] {
+ margin-top: -8px;
+ min-height: 8px;
+ max-height: 8px;
+}
+
+scrollbar thumb {
+ -moz-appearance: none !important;
+ border-width: 0px !important;
+ background-color: rgba(0,0,0,0.2) !important;
+ border-radius: 3px !important;
+}
+
+scrollbar scrollbarbutton, scrollbar gripper {
+ display: none;
+}
diff --git a/browser/themes/winstripe/jar.mn b/browser/themes/winstripe/jar.mn
index 603c039aeb47..a82b5cfae819 100644
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -192,6 +192,7 @@ browser.jar:
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
+ skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/browser/sync-throbber.png
skin/classic/browser/sync-16.png
@@ -395,6 +396,7 @@ browser.jar:
skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/aero/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/aero/browser/devtools/tools-icons-small.png (devtools/tools-icons-small.png)
+ skin/classic/aero/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
#ifdef MOZ_SERVICES_SYNC
skin/classic/aero/browser/sync-throbber.png
skin/classic/aero/browser/sync-16.png
diff --git a/browser/devtools/shared/Promise.jsm b/toolkit/devtools/_Promise.jsm
similarity index 100%
rename from browser/devtools/shared/Promise.jsm
rename to toolkit/devtools/_Promise.jsm
diff --git a/toolkit/devtools/debugger/dbg-client.jsm b/toolkit/devtools/debugger/dbg-client.jsm
index 8c5ab00308f4..86af464ae732 100644
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -487,6 +487,7 @@ function ThreadClient(aClient, aActor) {
this._frameCache = [];
this._scriptCache = {};
this._pauseGrips = {};
+ this._threadGrips = {};
}
ThreadClient.prototype = {
@@ -861,30 +862,74 @@ ThreadClient.prototype = {
},
/**
- * Return an instance of LongStringClient for the given long string grip.
+ * Get or create a long string client, checking the grip client cache if it
+ * already exists.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ * @param aGripCacheName String
+ * The property name of the grip client cache to check for existing
+ * clients in.
+ */
+ _longString: function TC__longString(aGrip, aGripCacheName) {
+ if (aGrip.actor in this[aGripCacheName]) {
+ return this[aGripCacheName][aGrip.actor];
+ }
+
+ let client = new LongStringClient(this._client, aGrip);
+ this[aGripCacheName][aGrip.actor] = client;
+ return client;
+ },
+
+ /**
+ * Return an instance of LongStringClient for the given long string grip that
+ * is scoped to the current pause.
*
* @param aGrip Object
* The long string grip returned by the protocol.
*/
- longString: function TC_longString(aGrip) {
- if (aGrip.actor in this._pauseGrips) {
- return this._pauseGrips[aGrip.actor];
- }
+ pauseLongString: function TC_pauseLongString(aGrip) {
+ return this._longString(aGrip, "_pauseGrips");
+ },
- let client = new LongStringClient(this._client, aGrip);
- this._pauseGrips[aGrip.actor] = client;
- return client;
+ /**
+ * Return an instance of LongStringClient for the given long string grip that
+ * is scoped to the thread lifetime.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ */
+ threadLongString: function TC_threadLongString(aGrip) {
+ return this._longString(aGrip, "_threadGrips");
+ },
+
+ /**
+ * Clear and invalidate all the grip clients from the given cache.
+ *
+ * @param aGripCacheName
+ * The property name of the grip cache we want to clear.
+ */
+ _clearGripClients: function TC_clearGrips(aGripCacheName) {
+ for each (let grip in this[aGripCacheName]) {
+ grip.valid = false;
+ }
+ this[aGripCacheName] = {};
},
/**
* Invalidate pause-lifetime grip clients and clear the list of
* current grip clients.
*/
- _clearPauseGrips: function TC_clearPauseGrips(aPacket) {
- for each (let grip in this._pauseGrips) {
- grip.valid = false;
- }
- this._pauseGrips = {};
+ _clearPauseGrips: function TC_clearPauseGrips() {
+ this._clearGripClients("_pauseGrips");
+ },
+
+ /**
+ * Invalidate pause-lifetime grip clients and clear the list of
+ * current grip clients.
+ */
+ _clearThreadGrips: function TC_clearPauseGrips() {
+ this._clearGripClients("_threadGrips");
},
/**
@@ -895,8 +940,17 @@ ThreadClient.prototype = {
this._state = ThreadStateTypes[aPacket.type];
this._clearFrames();
this._clearPauseGrips();
+ aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
this._client._eventsEnabled && this.notify(aPacket.type, aPacket);
},
+
+ /**
+ * Return an instance of SourceClient for the given actor.
+ */
+ source: function TC_source(aActor) {
+ return new SourceClient(this._client, aActor);
+ }
+
};
eventSource(ThreadClient.prototype);
@@ -1039,6 +1093,55 @@ LongStringClient.prototype = {
}
};
+/**
+ * A SourceClient provides a way to access the source text of a script.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aActor String
+ * The name of the source actor.
+ */
+function SourceClient(aClient, aActor) {
+ this._actor = aActor;
+ this._client = aClient;
+}
+
+SourceClient.prototype = {
+ /**
+ * Get a long string grip for this SourceClient's source.
+ */
+ source: function SC_source(aCallback) {
+ let packet = {
+ to: this._actor,
+ type: "source"
+ };
+ this._client.request(packet, function (aResponse) {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return;
+ }
+
+ if (typeof aResponse.source === "string") {
+ aCallback(aResponse);
+ return;
+ }
+
+ let longString = this._client.activeThread.threadLongString(
+ aResponse.source);
+ longString.substring(0, longString.length, function (aResponse) {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return;
+ }
+
+ aCallback({
+ source: aResponse.substring
+ });
+ });
+ }.bind(this));
+ }
+};
+
/**
* Breakpoint clients are used to remove breakpoints that are no longer used.
*
diff --git a/toolkit/devtools/debugger/server/dbg-script-actors.js b/toolkit/devtools/debugger/server/dbg-script-actors.js
index e8a8598c8c24..16a047db2f63 100644
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -546,10 +546,12 @@ ThreadActor.prototype = {
if (!this._scripts[url][i]) {
continue;
}
+
let script = {
url: url,
startLine: i,
- lineCount: this._scripts[url][i].lineCount
+ lineCount: this._scripts[url][i].lineCount,
+ source: this.sourceGrip(this._scripts[url][i], this)
};
scripts.push(script);
}
@@ -781,13 +783,16 @@ ThreadActor.prototype = {
/**
* Create a grip for the given debuggee value. If the value is an
- * object, will create a pause-lifetime actor.
+ * object, will create an actor with the given lifetime.
*/
- createValueGrip: function TA_createValueGrip(aValue) {
+ createValueGrip: function TA_createValueGrip(aValue, aPool=false) {
+ if (!aPool) {
+ aPool = this._pausePool;
+ }
let type = typeof(aValue);
if (type === "string" && this._stringIsLong(aValue)) {
- return this.longStringGrip(aValue);
+ return this.longStringGrip(aValue, aPool);
}
if (type === "boolean" || type === "string" || type === "number") {
@@ -803,7 +808,7 @@ ThreadActor.prototype = {
}
if (typeof(aValue) === "object") {
- return this.pauseObjectGrip(aValue);
+ return this.objectGrip(aValue, aPool);
}
dbg_assert(false, "Failed to provide a grip for: " + aValue);
@@ -881,26 +886,44 @@ ThreadActor.prototype = {
*
* @param aString String
* The string we are creating a grip for.
+ * @param aPool ActorPool
+ * The actor pool where the new actor will be added.
*/
- longStringGrip: function TA_longStringGrip(aString) {
- if (!this._pausePool) {
- throw new Error("LongString grip requested while not paused.");
+ longStringGrip: function TA_longStringGrip(aString, aPool) {
+ if (!aPool.longStringActors) {
+ aPool.longStringActors = {};
}
- if (!this._pausePool.longStringActors) {
- this._pausePool.longStringActors = {};
- }
-
- if (this._pausePool.longStringActors.hasOwnProperty(aString)) {
- return this._pausePool.longStringActors[aString].grip();
+ if (aPool.longStringActors.hasOwnProperty(aString)) {
+ return aPool.longStringActors[aString].grip();
}
let actor = new LongStringActor(aString, this);
- this._pausePool.addActor(actor);
- this._pausePool.longStringActors[aString] = actor;
+ aPool.addActor(actor);
+ aPool.longStringActors[aString] = actor;
return actor.grip();
},
+ /**
+ * Create a long string grip that is scoped to a pause.
+ *
+ * @param aString String
+ * The string we are creating a grip for.
+ */
+ pauseLongStringGrip: function TA_pauseLongStringGrip (aString) {
+ return this.longStringGrip(aString, this._pausePool);
+ },
+
+ /**
+ * Create a long string grip that is scoped to a thread.
+ *
+ * @param aString String
+ * The string we are creating a grip for.
+ */
+ threadLongStringGrip: function TA_pauseLongStringGrip (aString) {
+ return this.longStringGrip(aString, this._threadLifetimePool);
+ },
+
/**
* Returns true if the string is long enough to use a LongStringActor instead
* of passing the value directly over the protocol.
@@ -912,6 +935,26 @@ ThreadActor.prototype = {
return aString.length >= DebuggerServer.LONG_STRING_LENGTH;
},
+ /**
+ * Create a source grip for the given script.
+ */
+ sourceGrip: function TA_sourceGrip(aScript) {
+ // TODO: Once we have Debugger.Source, this should be replaced with a
+ // weakmap mapping Debugger.Source instances to SourceActor instances.
+ if (!this.threadLifetimePool.sourceActors) {
+ this.threadLifetimePool.sourceActors = {};
+ }
+
+ if (this.threadLifetimePool.sourceActors[aScript.url]) {
+ return this.threadLifetimePool.sourceActors[aScript.url].grip();
+ }
+
+ let actor = new SourceActor(aScript, this);
+ this.threadLifetimePool.addActor(actor);
+ this.threadLifetimePool.sourceActors[aScript.url] = actor;
+ return actor.grip();
+ },
+
// JS Debugger API hooks.
/**
@@ -981,7 +1024,8 @@ ThreadActor.prototype = {
type: "newScript",
url: aScript.url,
startLine: aScript.startLine,
- lineCount: aScript.lineCount
+ lineCount: aScript.lineCount,
+ source: this.sourceGrip(aScript, this)
});
}
},
@@ -1133,6 +1177,176 @@ function update(aTarget, aNewAttrs) {
}
+/**
+ * A SourceActor provides information about the source of a script.
+ *
+ * @param aScript Debugger.Script
+ * The script whose source we are representing.
+ * @param aThreadActor ThreadActor
+ * The current thread actor.
+ */
+function SourceActor(aScript, aThreadActor) {
+ this._threadActor = aThreadActor;
+ this._script = aScript;
+}
+
+SourceActor.prototype = {
+ constructor: SourceActor,
+ actorPrefix: "source",
+
+ get threadActor() { return this._threadActor; },
+
+ grip: function SA_grip() {
+ return this.actorID;
+ },
+
+ disconnect: function LSA_disconnect() {
+ if (this.registeredPool && this.registeredPool.sourceActors) {
+ delete this.registeredPool.sourceActors[this.actorID];
+ }
+ },
+
+ /**
+ * Handler for the "source" packet.
+ */
+ onSource: function SA_onSource(aRequest) {
+ this
+ ._loadSource()
+ .chainPromise(function(aSource) {
+ return this._threadActor.createValueGrip(
+ aSource, this.threadActor.threadLifetimePool);
+ }.bind(this))
+ .chainPromise(function (aSourceGrip) {
+ return {
+ from: this.actorID,
+ source: aSourceGrip
+ };
+ }.bind(this))
+ .trap(function (aError) {
+ return {
+ "from": this.actorID,
+ "error": "loadSourceError",
+ "message": "Could not load the source for " + this._script.url + "."
+ };
+ }.bind(this))
+ .chainPromise(function (aPacket) {
+ this.conn.send(aPacket);
+ }.bind(this));
+ },
+
+ /**
+ * Convert a given string, encoded in a given character set, to unicode.
+ * @param string aString
+ * A string.
+ * @param string aCharset
+ * A character set.
+ * @return string
+ * A unicode string.
+ */
+ _convertToUnicode: function SS__convertToUnicode(aString, aCharset) {
+ // Decoding primitives.
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+
+ try {
+ converter.charset = aCharset || "UTF-8";
+ return converter.ConvertToUnicode(aString);
+ } catch(e) {
+ return aString;
+ }
+ },
+
+ /**
+ * Performs a request to load the desired URL and returns a promise.
+ *
+ * @param aURL String
+ * The URL we will request.
+ * @returns Promise
+ *
+ * XXX: It may be better to use nsITraceableChannel to get to the sources
+ * without relying on caching when we can (not for eval, etc.):
+ * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
+ */
+ _loadSource: function SA__loadSource() {
+ let promise = new Promise();
+ let url = this._script.url;
+ let scheme;
+ try {
+ scheme = Services.io.extractScheme(url);
+ } catch (e) {
+ // In the xpcshell tests, the script url is the absolute path of the test
+ // file, which will make a malformed URI error be thrown. Add the file
+ // scheme prefix ourselves.
+ url = "file://" + url;
+ scheme = Services.io.extractScheme(url);
+ }
+
+ switch (scheme) {
+ case "file":
+ case "chrome":
+ case "resource":
+ try {
+ NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
+ if (!Components.isSuccessCode(aStatus)) {
+ promise.reject(new Error("Request failed"));
+ return;
+ }
+
+ let source = NetUtil.readInputStreamToString(aStream, aStream.available());
+ promise.resolve(this._convertToUnicode(source));
+ aStream.close();
+ }.bind(this));
+ } catch (ex) {
+ promise.reject(new Error("Request failed"));
+ }
+ break;
+
+ default:
+ let channel;
+ try {
+ channel = Services.io.newChannel(url, null, null);
+ } catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
+ // On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but
+ // newChannel won't be able to handle it.
+ url = "file:///" + url;
+ channel = Services.io.newChannel(url, null, null);
+ }
+ let chunks = [];
+ let streamListener = {
+ onStartRequest: function(aRequest, aContext, aStatusCode) {
+ if (!Components.isSuccessCode(aStatusCode)) {
+ promise.reject("Request failed");
+ }
+ },
+ onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
+ chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
+ },
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ if (!Components.isSuccessCode(aStatusCode)) {
+ promise.reject("Request failed");
+ return;
+ }
+
+ promise.resolve(this._convertToUnicode(chunks.join(""),
+ channel.contentCharset));
+ }.bind(this)
+ };
+
+ channel.loadFlags = channel.LOAD_FROM_CACHE;
+ channel.asyncOpen(streamListener, null);
+ break;
+ }
+
+ return promise;
+ }
+
+};
+
+SourceActor.prototype.requestTypes = {
+ "source": SourceActor.prototype.onSource
+};
+
+
/**
* Creates an actor for the specified object.
*
diff --git a/toolkit/devtools/debugger/server/dbg-server.js b/toolkit/devtools/debugger/server/dbg-server.js
index 12370ef66f8f..4ff6acda6cf0 100644
--- a/toolkit/devtools/debugger/server/dbg-server.js
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -24,6 +24,8 @@ let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
+Cu.import("resource://gre/modules/devtools/_Promise.jsm");
+
function dumpn(str) {
if (wantLogging) {
dump("DBG-SERVER: " + str + "\n");
diff --git a/toolkit/devtools/debugger/tests/unit/test_listscripts-01.js b/toolkit/devtools/debugger/tests/unit/test_listscripts-01.js
index d3e2ea402ae1..b18fa6f6a8aa 100644
--- a/toolkit/devtools/debugger/tests/unit/test_listscripts-01.js
+++ b/toolkit/devtools/debugger/tests/unit/test_listscripts-01.js
@@ -35,8 +35,9 @@ function test_simple_listscripts()
// Check the return value.
do_check_true(!!script);
do_check_eq(script.url, path);
- do_check_eq(script.startLine, 46);
+ do_check_eq(script.startLine, gDebuggee.line0);
do_check_eq(script.lineCount, 4);
+ do_check_true(!!script.source);
gThreadClient.resume(function () {
finishClient(gClient);
});
diff --git a/toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js
index bdebd8578bf1..1e93aad24e57 100644
--- a/toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js
+++ b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-01.js
@@ -48,7 +48,7 @@ function test_longstring_grip()
do_check_eq(grip.length, longString.length);
do_check_eq(grip.initial, longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH));
- let longStringClient = gThreadClient.longString(grip);
+ let longStringClient = gThreadClient.pauseLongString(grip);
longStringClient.substring(22, 28, function (aResponse) {
try {
do_check_eq(aResponse.substring, "monkey");
diff --git a/toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js
index 5c49ce059f3f..1bb7271406d6 100644
--- a/toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js
+++ b/toolkit/devtools/debugger/tests/unit/test_longstringgrips-02.js
@@ -36,7 +36,7 @@ function test_longstring_grip()
actor: "123fakeActor123",
initial: ""
};
- let longStringClient = gThreadClient.longString(fakeLongStringGrip);
+ let longStringClient = gThreadClient.pauseLongString(fakeLongStringGrip);
longStringClient.substring(22, 28, function (aResponse) {
try {
do_check_true(!!aResponse.error,
diff --git a/toolkit/devtools/debugger/tests/unit/test_source-01.js b/toolkit/devtools/debugger/tests/unit/test_source-01.js
new file mode 100644
index 000000000000..31582bf0af7c
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_source-01.js
@@ -0,0 +1,77 @@
+/* -*- Mode: javascript; js-indent-level: 2; -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+// This test ensures that we can create SourceActors and SourceClients properly,
+// and that they can communicate over the protocol to fetch the source text for
+// a given script.
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect(function() {
+ attachTestGlobalClientAndResume(gClient, "test-grips", function(aResponse, aThreadClient) {
+ gThreadClient = aThreadClient;
+ gThreadClient.addListener("unsolicitedPause", unsolicitedPauseListener);
+ test_source();
+ });
+ });
+ do_test_pending();
+}
+
+function unsolicitedPauseListener(aEvent, aPacket, aContinue) {
+ gContinue = aContinue;
+}
+
+function test_source()
+{
+ DebuggerServer.LONG_STRING_LENGTH = 200;
+
+ gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) {
+ gThreadClient.getScripts(function (aResponse) {
+ do_check_true(!!aResponse);
+ do_check_true(!!aResponse.scripts);
+
+ let script = aResponse.scripts.filter(function (s) {
+ return s.url.match(/test_source-01.js$/);
+ })[0];
+
+ do_check_true(!!script);
+ do_check_true(!!script.source);
+
+ let sourceClient = gThreadClient.source(script.source);
+ sourceClient.source(function (aResponse) {
+ do_check_true(!!aResponse);
+ do_check_true(!aResponse.error);
+ do_check_true(!!aResponse.source);
+
+ let f = do_get_file("test_source-01.js", false);
+ let s = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ s.init(f, -1, -1, false);
+
+ do_check_eq(NetUtil.readInputStreamToString(s, s.available()),
+ aResponse.source);
+
+ s.close();
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval('stopMe()');
+}
diff --git a/toolkit/devtools/debugger/tests/unit/xpcshell.ini b/toolkit/devtools/debugger/tests/unit/xpcshell.ini
index e1644c033792..c00e85e612d4 100644
--- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini
@@ -64,4 +64,5 @@ tail =
[test_longstringactor.js]
[test_longstringgrips-01.js]
[test_longstringgrips-02.js]
+[test_source-01.js]
[test_breakpointstore.js]