This commit is contained in:
Richard Newman 2012-10-04 07:34:14 -07:00
Родитель 6a401b1ee3 ab5493f19c
Коммит 6b64ac8884
33 изменённых файлов: 863 добавлений и 156 удалений

Просмотреть файл

@ -522,7 +522,7 @@
<menuitem id="webConsole" observes="devtoolsMenuBroadcaster_WebConsole" accesskey="&webConsoleCmd.accesskey;"/>
<menuitem id="menu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect" accesskey="&inspectMenu.accesskey;"/>
<menuitem id="menu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI" accesskey="&responsiveDesignTool.accesskey;"/>
<menuitem id="menu_debugger" observes="devtoolsMenuBroadcaster_Debugger"/>
<menuitem id="menu_debugger" observes="devtoolsMenuBroadcaster_Debugger" accesskey="&debuggerMenu.accesskey;"/>
<menuitem id="menu_remoteDebugger" observes="devtoolsMenuBroadcaster_RemoteDebugger"/>
<menuitem id="menu_chromeDebugger" observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
<menuitem id="menu_scratchpad" observes="devtoolsMenuBroadcaster_Scratchpad" accesskey="&scratchpad.accesskey;"/>

Просмотреть файл

@ -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

Просмотреть файл

@ -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();
});
});
});

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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

Просмотреть файл

@ -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,

Просмотреть файл

@ -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()

Просмотреть файл

@ -11,7 +11,7 @@
<link rel="stylesheet" href="chrome://browser/skin/devtools/markup-view.css" type="text/css"/>
<link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
</head>
<body role="application">
<body class="devtools-theme-background" role="application">
<div id="root"></div>
<div id="templates" style="display:none">
<ul>

Просмотреть файл

@ -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();

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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();
}
};
/**

Просмотреть файл

@ -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) {}
},
}

Просмотреть файл

@ -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() {

Просмотреть файл

@ -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() {

Просмотреть файл

@ -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();

Просмотреть файл

@ -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;

Просмотреть файл

@ -15,6 +15,10 @@
- application menu item that opens the debugger UI. -->
<!ENTITY debuggerMenu.label2 "Debugger">
<!-- LOCALIZATION NOTE (debuggerMenu.accesskey): This is accesskey for the
- Tools meny entry of Debugger that opens the debugger UI. -->
<!ENTITY debuggerMenu.accesskey "D">
<!-- LOCALIZATION NOTE (remoteDebuggerMenu.label): This is the label for the
- application menu item that opens the remote debugger UI. -->
<!ENTITY remoteDebuggerMenu.label "Remote Debugger">

Просмотреть файл

@ -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

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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

Просмотреть файл

Просмотреть файл

@ -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.
*

Просмотреть файл

@ -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.
*

Просмотреть файл

@ -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");

Просмотреть файл

@ -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);
});

Просмотреть файл

@ -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");

Просмотреть файл

@ -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,

Просмотреть файл

@ -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()');
}

Просмотреть файл

@ -64,4 +64,5 @@ tail =
[test_longstringactor.js]
[test_longstringgrips-01.js]
[test_longstringgrips-02.js]
[test_source-01.js]
[test_breakpointstore.js]