Bug 800857 - Implement debugger frontend for breaking on dom events, r=rcampbell

This commit is contained in:
Victor Porof 2013-10-04 10:33:08 +03:00
parent 934e18ae62
commit 98c03e2f04
15 changed files with 872 additions and 183 deletions

View File

@ -11,6 +11,7 @@ const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted", "XStringBundle"];
const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
const FRAME_STEP_CLEAR_DELAY = 100; // ms
const CALL_STACK_PAGE_SIZE = 25; // frames
@ -47,6 +48,10 @@ const EVENTS = {
CONDITIONAL_BREAKPOINT_POPUP_SHOWING: "Debugger:ConditionalBreakpointPopupShowing",
CONDITIONAL_BREAKPOINT_POPUP_HIDING: "Debugger:ConditionalBreakpointPopupHiding",
// When event listeners are fetched or event breakpoints are updated.
EVENT_LISTENERS_FETCHED: "Debugger:EventListenersFetched",
EVENT_BREAKPOINTS_UPDATED: "Debugger:EventBreakpointsUpdated",
// When a file search was performed.
FILE_SEARCH_MATCH_FOUND: "Debugger:FileSearch:MatchFound",
FILE_SEARCH_MATCH_NOT_FOUND: "Debugger:FileSearch:MatchNotFound",
@ -274,9 +279,13 @@ let DebuggerController = {
// Reset UI.
DebuggerView._handleTabNavigation();
// Discard all the old sources.
// Discard all the cached sources.
DebuggerController.Parser.clearCache();
SourceUtils.clearCache();
// Prevent performing any actions that were scheduled before navigation.
clearNamedTimeout("event-breakpoints-update");
clearNamedTimeout("event-listeners-fetch");
break;
}
case "navigate": {
@ -387,7 +396,7 @@ let DebuggerController = {
return;
}
// Reset UI, discard all the old sources and get them again.
// Reset the view and fetch all the sources again.
DebuggerView._handleTabNavigation();
this.SourceScripts._handleTabNavigation();
@ -1069,6 +1078,11 @@ SourceScripts.prototype = {
DebuggerController.Breakpoints.updateEditorBreakpoints();
DebuggerController.Breakpoints.updatePaneBreakpoints();
// Make sure the events listeners are up to date.
if (DebuggerView.instrumentsPaneTab == "events-tab") {
DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
}
// Signal that a new source has been added.
window.emit(EVENTS.NEW_SOURCE);
},
@ -1110,6 +1124,11 @@ SourceScripts.prototype = {
DebuggerController.Breakpoints.updateEditorBreakpoints();
DebuggerController.Breakpoints.updatePaneBreakpoints();
// Make sure the events listeners are up to date.
if (DebuggerView.instrumentsPaneTab == "events-tab") {
DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
}
// Signal that sources have been added.
window.emit(EVENTS.SOURCES_ADDED);
},
@ -1302,6 +1321,82 @@ SourceScripts.prototype = {
}
};
/**
* Handles breaking on event listeners in the currently debugged target.
*/
function EventListeners() {
this._onEventListeners = this._onEventListeners.bind(this);
}
EventListeners.prototype = {
/**
* A list of event names on which the debuggee will automatically pause
* when invoked.
*/
activeEventNames: [],
/**
* Updates the list of events types with listeners that, when invoked,
* will automatically pause the debuggee. The respective events are
* retrieved from the UI.
*/
scheduleEventBreakpointsUpdate: function() {
// Make sure we're not sending a batch of closely repeated requests.
// This can easily happen when toggling all events of a certain type.
setNamedTimeout("event-breakpoints-update", 0, () => {
this.activeEventNames = DebuggerView.EventListeners.getCheckedEvents();
gThreadClient.pauseOnDOMEvents(this.activeEventNames);
// Notify that event breakpoints were added/removed on the server.
window.emit(EVENTS.EVENT_BREAKPOINTS_UPDATED);
});
},
/**
* Fetches the currently attached event listeners from the debugee.
*/
scheduleEventListenersFetch: function() {
let getListeners = aCallback => gThreadClient.eventListeners(aResponse => {
this._onEventListeners(aResponse);
// Notify that event listeners were fetched and shown in the view,
// and callback to resume the active thread if necessary.
window.emit(EVENTS.EVENT_LISTENERS_FETCHED);
aCallback && aCallback();
});
// Make sure we're not sending a batch of closely repeated requests.
// This can easily happen whenever new sources are fetched.
setNamedTimeout("event-listeners-fetch", FETCH_EVENT_LISTENERS_DELAY, () => {
if (gThreadClient.state != "paused") {
gThreadClient.interrupt(() => getListeners(() => gThreadClient.resume()));
} else {
getListeners();
}
});
},
/**
* Callback for the debugger's active thread eventListeners() method.
*/
_onEventListeners: function(aResponse) {
if (aResponse.error) {
let msg = "Error getting event listeners: " + aResponse.message;
Cu.reportError(msg);
dumpn(msg);
return;
}
// Add all the listeners in the debugger view event linsteners container.
for (let listener of aResponse.listeners) {
DebuggerView.EventListeners.addListener(listener, { staged: true });
}
// Flushes all the prepared events into the event listeners container.
DebuggerView.EventListeners.commit();
}
};
/**
* Handles all the breakpoints in the current debugger.
*/
@ -1314,8 +1409,6 @@ function Breakpoints() {
}
Breakpoints.prototype = {
get activeThread() DebuggerController.activeThread,
/**
* A map of breakpoint promises as tracked by the debugger frontend.
* The keys consist of a string representation of the breakpoint location.
@ -1492,7 +1585,7 @@ Breakpoints.prototype = {
this._added.set(identifier, deferred.promise);
// Try adding the breakpoint.
this.activeThread.setBreakpoint(aLocation, (aResponse, aBreakpointClient) => {
gThreadClient.setBreakpoint(aLocation, (aResponse, aBreakpointClient) => {
// If the breakpoint response has an "actualLocation" attached, then
// the original requested placement for the breakpoint wasn't accepted.
if (aResponse.actualLocation) {
@ -1787,6 +1880,7 @@ DebuggerController.ThreadState = new ThreadState();
DebuggerController.StackFrames = new StackFrames();
DebuggerController.SourceScripts = new SourceScripts();
DebuggerController.Breakpoints = new Breakpoints();
DebuggerController.Breakpoints.DOM = new EventListeners();
/**
* Export some properties to the global scope for easier access.

View File

@ -38,7 +38,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
dumpn("Initializing the SourcesView");
this.widget = new SideMenuWidget(document.getElementById("sources"), {
showCheckboxes: true,
showItemCheckboxes: true,
showArrows: true
});
@ -932,10 +932,10 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
// Breakpoints can only be set while the debuggee is paused. To avoid
// an avalanche of pause/resume interrupts of the main thread, simply
// pause it beforehand if it's not already.
if (gThreadClient.state == "paused") {
enableOthers();
} else {
if (gThreadClient.state != "paused") {
gThreadClient.interrupt(() => enableOthers(() => gThreadClient.resume()));
} else {
enableOthers();
}
},
@ -1495,6 +1495,265 @@ WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
}
});
/**
* Functions handling the event listeners UI.
*/
function EventListenersView() {
dumpn("EventListenersView was instantiated");
this._onCheck = this._onCheck.bind(this);
this._onClick = this._onClick.bind(this);
}
EventListenersView.prototype = Heritage.extend(WidgetMethods, {
/**
* Initialization function, called when the debugger is started.
*/
initialize: function() {
dumpn("Initializing the EventListenersView");
this.widget = new SideMenuWidget(document.getElementById("event-listeners"), {
theme: "light",
showItemCheckboxes: true,
showGroupCheckboxes: true
});
this.emptyText = L10N.getStr("noEventListenersText");
this._eventCheckboxTooltip = L10N.getStr("eventCheckboxTooltip");
this._onSelectorString = " " + L10N.getStr("eventOnSelector") + " ";
this._inSourceString = " " + L10N.getStr("eventInSource") + " ";
this.widget.addEventListener("check", this._onCheck, false);
this.widget.addEventListener("click", this._onClick, false);
// Show an empty label by default.
this.empty();
},
/**
* Destruction function, called when the debugger is closed.
*/
destroy: function() {
dumpn("Destroying the EventListenersView");
this.widget.removeEventListener("check", this._onCheck, false);
this.widget.removeEventListener("click", this._onClick, false);
},
/**
* Adds an event to this event listeners container.
*
* @param object aListener
* The listener object coming from the active thread.
* @param object aOptions [optional]
* Additional options for adding the source. Supported options:
* - staged: true to stage the item to be appended later
*/
addListener: function(aListener, aOptions = {}) {
let { node: { selector }, function: { url }, type } = aListener;
// If an event item for this listener's url and type was already added,
// avoid polluting the view and simply increase the "targets" count.
let eventItem = this.getItemForPredicate(aItem =>
aItem.attachment.url == url &&
aItem.attachment.type == type);
if (eventItem) {
let { selectors, view: { targets } } = eventItem.attachment;
if (selectors.indexOf(selector) == -1) {
selectors.push(selector);
targets.setAttribute("value", L10N.getFormatStr("eventNodes", selectors.length));
}
return;
}
// There's no easy way of grouping event types into higher-level groups,
// so we need to do this by hand.
let is = (...args) => args.indexOf(type) != -1;
let has = str => type.contains(str);
let starts = str => type.startsWith(str);
let group;
if (starts("animation")) {
group = L10N.getStr("animationEvents");
} else if (starts("audio")) {
group = L10N.getStr("audioEvents");
} else if (is("levelchange")) {
group = L10N.getStr("batteryEvents");
} else if (is("cut", "copy", "paste")) {
group = L10N.getStr("clipboardEvents");
} else if (starts("composition")) {
group = L10N.getStr("compositionEvents");
} else if (starts("device")) {
group = L10N.getStr("deviceEvents");
} else if (is("fullscreenchange", "fullscreenerror", "orientationchange",
"overflow", "resize", "scroll", "underflow", "zoom")) {
group = L10N.getStr("displayEvents");
} else if (starts("drag") || starts("drop")) {
group = L10N.getStr("Drag and dropEvents");
} else if (starts("gamepad")) {
group = L10N.getStr("gamepadEvents");
} else if (is("canplay", "canplaythrough", "durationchange", "emptied",
"ended", "loadeddata", "loadedmetadata", "pause", "play", "playing",
"ratechange", "seeked", "seeking", "stalled", "suspend", "timeupdate",
"volumechange", "waiting")) {
group = L10N.getStr("mediaEvents");
} else if (is("blocked", "complete", "success", "upgradeneeded", "versionchange")) {
group = L10N.getStr("indexedDBEvents");
} else if (is("blur", "change", "focus", "focusin", "focusout", "invalid",
"reset", "select", "submit")) {
group = L10N.getStr("interactionEvents");
} else if (starts("key") || is("input")) {
group = L10N.getStr("keyboardEvents");
} else if (starts("mouse") || has("click") || is("contextmenu", "show", "wheel")) {
group = L10N.getStr("mouseEvents");
} else if (starts("DOM")) {
group = L10N.getStr("mutationEvents");
} else if (is("abort", "error", "hashchange", "load", "loadend", "loadstart",
"pagehide", "pageshow", "progress", "timeout", "unload", "uploadprogress",
"visibilitychange")) {
group = L10N.getStr("navigationEvents");
} else if (is("pointerlockchange", "pointerlockerror")) {
group = L10N.getStr("Pointer lockEvents");
} else if (is("compassneedscalibration", "userproximity")) {
group = L10N.getStr("sensorEvents");
} else if (starts("storage")) {
group = L10N.getStr("storageEvents");
} else if (is("beginEvent", "endEvent", "repeatEvent")) {
group = L10N.getStr("timeEvents");
} else if (starts("touch")) {
group = L10N.getStr("touchEvents");
} else {
group = L10N.getStr("otherEvents");
}
// Create the element node for the event listener item.
let itemView = this._createItemView(type, selector, url);
// Event breakpoints survive target navigations. Make sure the newly
// inserted event item is correctly checked.
let checkboxState =
DebuggerController.Breakpoints.DOM.activeEventNames.indexOf(type) != -1;
// Append an event listener item to this container.
this.push([itemView.container, url, group], {
staged: aOptions.staged, /* stage the item to be appended later? */
attachment: {
url: url,
type: type,
view: itemView,
selectors: [selector],
checkboxState: checkboxState,
checkboxTooltip: this._eventCheckboxTooltip
}
});
},
/**
* Gets all the event types known to this container.
*
* @return array
* List of event types, for example ["load", "click"...]
*/
getAllEvents: function() {
return this.attachments.map(e => e.type);
},
/**
* Gets the checked event types in this container.
*
* @return array
* List of event types, for example ["load", "click"...]
*/
getCheckedEvents: function() {
return this.attachments.filter(e => e.checkboxState).map(e => e.type);
},
/**
* Customization function for creating an item's UI.
*
* @param string aType
* The event type, for example "click".
* @param string aSelector
* The target element's selector.
* @param string url
* The source url in which the event listener is located.
* @return object
* An object containing the event listener view nodes.
*/
_createItemView: function(aType, aSelector, aUrl) {
let container = document.createElement("hbox");
container.className = "dbg-event-listener";
let eventType = document.createElement("label");
eventType.className = "plain dbg-event-listener-type";
eventType.setAttribute("value", aType);
container.appendChild(eventType);
let typeSeparator = document.createElement("label");
typeSeparator.className = "plain dbg-event-listener-separator";
typeSeparator.setAttribute("value", this._onSelectorString);
container.appendChild(typeSeparator);
let eventTargets = document.createElement("label");
eventTargets.className = "plain dbg-event-listener-targets";
eventTargets.setAttribute("value", aSelector);
container.appendChild(eventTargets);
let selectorSeparator = document.createElement("label");
selectorSeparator.className = "plain dbg-event-listener-separator";
selectorSeparator.setAttribute("value", this._inSourceString);
container.appendChild(selectorSeparator);
let eventLocation = document.createElement("label");
eventLocation.className = "plain dbg-event-listener-location";
eventLocation.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
eventLocation.setAttribute("flex", "1");
eventLocation.setAttribute("crop", "center");
container.appendChild(eventLocation);
return {
container: container,
type: eventType,
targets: eventTargets,
location: eventLocation
};
},
/**
* The check listener for the event listeners container.
*/
_onCheck: function({ detail: { description, checked }, target }) {
if (description == "item") {
this.getItemForElement(target).attachment.checkboxState = checked;
DebuggerController.Breakpoints.DOM.scheduleEventBreakpointsUpdate();
return;
}
// Check all the event items in this group.
this.items
.filter(e => e.description == description)
.forEach(e => this.callMethod("checkItem", e.target, checked));
},
/**
* The select listener for the event listeners container.
*/
_onClick: function({ target }) {
// Changing the checkbox state is handled by the _onCheck event. Avoid
// handling that again in this click event, so pass in "noSiblings"
// when retrieving the target's item, to ignore the checkbox.
let eventItem = this.getItemForElement(target, { noSiblings: true });
if (eventItem) {
let newState = eventItem.attachment.checkboxState ^= 1;
this.callMethod("checkItem", eventItem.target, newState);
}
},
_eventCheckboxTooltip: "",
_onSelectorString: "",
_inSourceString: ""
});
/**
* Functions handling the global search UI.
*/
@ -2228,4 +2487,5 @@ LineResults.size = function() {
*/
DebuggerView.Sources = new SourcesView();
DebuggerView.WatchExpressions = new WatchExpressionsView();
DebuggerView.EventListeners = new EventListenersView();
DebuggerView.GlobalSearch = new GlobalSearchView();

View File

@ -64,6 +64,7 @@ let DebuggerView = {
this.StackFrames.initialize();
this.Sources.initialize();
this.WatchExpressions.initialize();
this.EventListeners.initialize();
this.GlobalSearch.initialize();
this._initializeVariablesView();
this._initializeEditor(deferred.resolve);
@ -94,6 +95,7 @@ let DebuggerView = {
this.StackFrames.destroy();
this.Sources.destroy();
this.WatchExpressions.destroy();
this.EventListeners.destroy();
this.GlobalSearch.destroy();
this._destroyPanes();
this._destroyEditor(deferred.resolve);
@ -111,6 +113,9 @@ let DebuggerView = {
this._instrumentsPane = document.getElementById("instruments-pane");
this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
this._collapsePaneString = L10N.getStr("collapsePanes");
this._expandPaneString = L10N.getStr("expandPanes");
@ -128,6 +133,8 @@ let DebuggerView = {
Prefs.sourcesWidth = this._sourcesPane.getAttribute("width");
Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
this._instrumentsPane.tabpanels.removeEventListener("select", this._onTabSelect);
this._sourcesPane = null;
this._instrumentsPane = null;
this._instrumentsPaneToggleButton = null;
@ -406,6 +413,13 @@ let DebuggerView = {
get instrumentsPaneHidden()
this._instrumentsPane.hasAttribute("pane-collapsed"),
/**
* Gets the currently selected tab in the instruments pane.
* @return string
*/
get instrumentsPaneTab()
this._instrumentsPane.selectedTab.id,
/**
* Sets the instruments pane hidden or visible.
*
@ -415,8 +429,10 @@ let DebuggerView = {
* - animated: true to display an animation on toggle
* - delayed: true to wait a few cycles before toggle
* - callback: a function to invoke when the toggle finishes
* @param number aTabIndex [optional]
* The index of the intended selected tab in the details pane.
*/
toggleInstrumentsPane: function(aFlags) {
toggleInstrumentsPane: function(aFlags, aTabIndex) {
let pane = this._instrumentsPane;
let button = this._instrumentsPaneToggleButton;
@ -429,6 +445,10 @@ let DebuggerView = {
button.setAttribute("pane-collapsed", "");
button.setAttribute("tooltiptext", this._expandPaneString);
}
if (aTabIndex !== undefined) {
pane.selectedIndex = aTabIndex;
}
},
/**
@ -443,7 +463,16 @@ let DebuggerView = {
animated: true,
delayed: true,
callback: aCallback
});
}, 0);
},
/**
* Handles a tab selection event on the instruments pane.
*/
_onInstrumentsPaneTabSelect: function() {
if (this._instrumentsPane.selectedTab.id == "events-tab") {
DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
}
},
/**
@ -460,6 +489,7 @@ let DebuggerView = {
this.StackFrames.empty();
this.Sources.empty();
this.Variables.empty();
this.EventListeners.empty();
if (this.editor) {
this.editor.setMode(SourceEditor.MODES.TEXT);
@ -482,6 +512,7 @@ let DebuggerView = {
Sources: null,
Variables: null,
WatchExpressions: null,
EventListeners: null,
editor: null,
_editorSource: {},
_loadingText: "",

View File

@ -10,14 +10,18 @@
overflow: auto;
}
/* Watch expressions view */
/* Instruments pane view (watch expressions, variables, events...) */
#instruments-pane > tabpanels > tabpanel {
-moz-box-orient: vertical;
}
#expressions {
overflow-x: hidden;
overflow-y: auto;
}
/* Toolbar */
/* Toolbar controls */
.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
display: none;

View File

@ -333,7 +333,7 @@
</vbox>
<splitter class="devtools-side-splitter"/>
<deck id="editor-deck" flex="1" selectedIndex="0">
<vbox id="editor" />
<vbox id="editor"/>
<vbox id="black-boxed-message" align="center">
<label id="black-boxed-message-label">
&debuggerUI.blackBoxMessage.label;
@ -346,11 +346,24 @@
</vbox>
</deck>
<splitter class="devtools-side-splitter"/>
<vbox id="instruments-pane" hidden="true">
<vbox id="expressions"/>
<splitter class="devtools-horizontal-splitter"/>
<vbox id="variables" flex="1"/>
</vbox>
<tabbox id="instruments-pane"
class="devtools-sidebar-tabs"
hidden="true">
<tabs>
<tab id="variables-tab" label="&debuggerUI.tabs.variables;"/>
<tab id="events-tab" label="&debuggerUI.tabs.events;"/>
</tabs>
<tabpanels flex="1">
<tabpanel id="variables-tabpanel">
<vbox id="expressions"/>
<splitter class="devtools-horizontal-splitter"/>
<vbox id="variables" flex="1"/>
</tabpanel>
<tabpanel id="events-tabpanel">
<vbox id="event-listeners" flex="1"/>
</tabpanel>
</tabpanels>
</tabbox>
</hbox>
</vbox>
</vbox>

View File

@ -32,27 +32,31 @@ this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
* @param nsIDOMNode aNode
* The element associated with the widget.
* @param Object aOptions
* - showArrows: Specifies if items in this container should display
* horizontal arrows.
* - showCheckboxes: Specifies if items in this container should display
* checkboxes.
* - theme: "light" or "dark", defaults to dark if falsy.
* - showArrows: specifies if items should display horizontal arrows.
* - showItemCheckboxes: specifies if items should display checkboxes.
* - showGroupCheckboxes: specifies if groups should display checkboxes.
*/
this.SideMenuWidget = function SideMenuWidget(aNode, aOptions={}) {
this.document = aNode.ownerDocument;
this.window = this.document.defaultView;
this._parent = aNode;
let { showArrows, showCheckboxes } = aOptions;
let { theme, showArrows, showItemCheckboxes, showGroupCheckboxes } = aOptions;
this._theme = theme || "dark";
this._showArrows = showArrows || false;
this._showCheckboxes = showCheckboxes || false;
this._showItemCheckboxes = showItemCheckboxes || false;
this._showGroupCheckboxes = showGroupCheckboxes || false;
// Create an internal scrollbox container.
this._list = this.document.createElement("scrollbox");
this._list.className = "side-menu-widget-container";
this._list.setAttribute("flex", "1");
this._list.setAttribute("orient", "vertical");
this._list.setAttribute("with-arrow", showArrows);
this._list.setAttribute("with-checkboxes", showCheckboxes);
this._list.setAttribute("theme", this._theme);
this._list.setAttribute("with-arrows", this._showArrows);
this._list.setAttribute("with-item-checkboxes", this._showItemCheckboxes);
this._list.setAttribute("with-group-checkboxes", this._showGroupCheckboxes);
this._list.setAttribute("tabindex", "0");
this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
@ -348,7 +352,7 @@ SideMenuWidget.prototype = {
let container = this.document.createElement("vbox");
container.className = "side-menu-widget-empty-notice-container";
container.setAttribute("align", "center");
container.setAttribute("theme", this._theme);
let label = this.document.createElement("label");
label.className = "plain side-menu-widget-empty-notice";
@ -388,9 +392,14 @@ SideMenuWidget.prototype = {
return cachedGroup;
}
let group = new SideMenuGroup(this, aName);
let group = new SideMenuGroup(this, aName, {
theme: this._theme,
showCheckbox: this._showGroupCheckboxes
});
this._groupsByName.set(aName, group);
group.insertSelfAt(this.sortedGroups ? group.findExpectedIndexForSelf() : -1);
return group;
},
@ -408,13 +417,19 @@ SideMenuWidget.prototype = {
* The attachement object.
*/
_getMenuItemForGroup: function(aGroup, aContents, aTooltip, aAttachment) {
return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows, this._showCheckboxes, aAttachment);
return new SideMenuItem(aGroup, aContents, aTooltip, aAttachment, {
theme: this._theme,
showArrow: this._showArrows,
showCheckbox: this._showItemCheckboxes
});
},
window: null,
document: null,
_theme: "",
_showArrows: false,
_showCheckboxes: false,
_showItemCheckboxes: false,
_showGroupCheckboxes: false,
_parent: null,
_list: null,
_boxObject: null,
@ -437,8 +452,12 @@ SideMenuWidget.prototype = {
* The widget to contain this menu item.
* @param string aName
* The string displayed in the container.
* @param object aOptions [optional]
* An object containing the following properties:
* - theme: the theme colors, either "dark" or "light".
* - showCheckbox: specifies if a checkbox should be displayed.
*/
function SideMenuGroup(aWidget, aName) {
function SideMenuGroup(aWidget, aName, aOptions={}) {
this.document = aWidget.document;
this.window = aWidget.window;
this.ownerView = aWidget;
@ -456,6 +475,7 @@ function SideMenuGroup(aWidget, aName) {
let title = this._title = this.document.createElement("hbox");
title.className = "side-menu-widget-group-title";
title.setAttribute("theme", aOptions.theme);
let name = this._name = this.document.createElement("label");
name.className = "plain name";
@ -463,6 +483,13 @@ function SideMenuGroup(aWidget, aName) {
name.setAttribute("crop", "end");
name.setAttribute("flex", "1");
// Show a checkbox before the content.
if (aOptions.showCheckbox) {
let checkbox = this._checkbox = makeCheckbox(title, { description: aName });
checkbox.className = "side-menu-widget-group-checkbox";
checkbox.setAttribute("align", "start");
}
title.appendChild(name);
target.appendChild(title);
target.appendChild(list);
@ -471,6 +498,7 @@ function SideMenuGroup(aWidget, aName) {
else {
let target = this._target = this._list = this.document.createElement("vbox");
target.className = "side-menu-widget-group side-menu-widget-group-list";
target.setAttribute("theme", aOptions.theme);
}
}
@ -523,6 +551,7 @@ SideMenuGroup.prototype = {
ownerView: null,
identifier: "",
_target: null,
_checkbox: null,
_title: null,
_name: null,
_list: null
@ -537,37 +566,39 @@ SideMenuGroup.prototype = {
* A tooltip attribute for the displayed item.
* @param string | nsIDOMNode aContents
* The string or node displayed in the container.
* @param boolean aArrowFlag
* True if a horizontal arrow should be shown.
* @param boolean aCheckboxFlag
* True if a checkbox should be shown.
* @param object aAttachment [optional]
* The attachment object.
* @param object aOptions [optional]
* An object containing the following properties:
* - theme: the theme colors, either "dark" or "light".
* - showArrow: specifies if a horizontal arrow should be displayed.
* - showCheckbox: specifies if a checkbox should be displayed.
*/
function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag, aCheckboxFlag, aAttachment={}) {
function SideMenuItem(aGroup, aContents, aTooltip, aAttachment={}, aOptions={}) {
this.document = aGroup.document;
this.window = aGroup.window;
this.ownerView = aGroup;
if (aArrowFlag || aCheckboxFlag) {
if (aOptions.showArrow || aOptions.showCheckbox) {
let container = this._container = this.document.createElement("hbox");
container.className = "side-menu-widget-item";
container.setAttribute("tooltiptext", aTooltip);
container.setAttribute("theme", aOptions.theme);
let target = this._target = this.document.createElement("vbox");
target.className = "side-menu-widget-item-contents";
// Show a checkbox before the content.
if (aCheckboxFlag) {
let checkbox = this._checkbox = this._makeCheckbox(aAttachment);
if (aOptions.showCheckbox) {
let checkbox = this._checkbox = makeCheckbox(container, aAttachment);
checkbox.className = "side-menu-widget-item-checkbox";
checkbox.setAttribute("align", "start");
container.appendChild(checkbox);
}
container.appendChild(target);
// Show a horizontal arrow towards the content.
if (aArrowFlag) {
if (aOptions.showArrow) {
let arrow = this._arrow = this.document.createElement("hbox");
arrow.className = "side-menu-widget-item-arrow";
container.appendChild(arrow);
@ -577,6 +608,7 @@ function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag, aCheckboxFlag, aA
else {
let target = this._target = this._container = this.document.createElement("hbox");
target.className = "side-menu-widget-item side-menu-widget-item-contents";
target.setAttribute("theme", aOptions.theme);
}
this._target.setAttribute("flex", "1");
@ -588,40 +620,6 @@ SideMenuItem.prototype = {
get _orderedMenuElementsArray() this.ownerView._orderedMenuElementsArray,
get _itemsByElement() { return this.ownerView._itemsByElement; },
/**
* Create the checkbox used when the checkbox flag is true. Emits a "check"
* event whenever the checkbox is checked or unchecked by the user.
*
* @param Object aAttachment
* The attachment object. The following properties are used:
* - checkboxState: true for checked, false for unchecked
8 - checkboxTooltip: The tooltip text of the checkbox
*/
_makeCheckbox: function (aAttachment) {
let checkbox = this.document.createElement("checkbox");
checkbox.className = "side-menu-widget-item-checkbox";
checkbox.setAttribute("tooltiptext", aAttachment.checkboxTooltip);
if (aAttachment.checkboxState) {
checkbox.setAttribute("checked", true);
} else {
checkbox.removeAttribute("checked");
}
// Stop the toggling of the checkbox from selecting the list item.
checkbox.addEventListener("mousedown", function (event) {
event.stopPropagation();
}, false);
checkbox.addEventListener("command", function (event) {
ViewHelpers.dispatchEvent(checkbox, "check", {
checked: checkbox.checked,
});
}, false);
return checkbox;
},
/**
* Inserts this item in the parent group at the specified index.
*
@ -656,11 +654,9 @@ SideMenuItem.prototype = {
if (!this._checkbox) {
throw new Error("Cannot check items that do not have checkboxes.");
}
if (aCheckState) {
this._checkbox.setAttribute("checked", true);
} else {
this._checkbox.removeAttribute("checked");
}
// Don't set or remove the "checked" attribute, assign the property instead.
// Otherwise, the "CheckboxStateChange" event will not be fired. XUL!!
this._checkbox.checked = !!aCheckState;
},
/**
@ -698,3 +694,43 @@ SideMenuItem.prototype = {
_checkbox: null,
_arrow: null
};
/**
* Creates a checkbox to a specified parent node. Emits a "check" event
* whenever the checkbox is checked or unchecked by the user.
*
* @param nsIDOMNode aParentNode
* The parent node to contain this checkbox.
* @param object aOptions
* An object containing some or all of the following properties:
* - description: defaults to "item" if unspecified
* - checkboxState: true for checked, false for unchecked
* - checkboxTooltip: the tooltip text of the checkbox
*/
function makeCheckbox(aParentNode, aOptions) {
let checkbox = aParentNode.ownerDocument.createElement("checkbox");
checkbox.setAttribute("tooltiptext", aOptions.checkboxTooltip);
if (aOptions.checkboxState) {
checkbox.setAttribute("checked", true);
} else {
checkbox.removeAttribute("checked");
}
// Stop the toggling of the checkbox from selecting the list item.
checkbox.addEventListener("mousedown", e => {
e.stopPropagation();
}, false);
// Emit an event from the checkbox when it is toggled. Don't listen for the
// "command" event! It won't fire for programmatic changes. XUL!!
checkbox.addEventListener("CheckboxStateChange", e => {
ViewHelpers.dispatchEvent(checkbox, "check", {
description: aOptions.description || "item",
checked: checkbox.checked
});
}, false);
aParentNode.appendChild(checkbox);
return checkbox;
}

View File

@ -1271,6 +1271,7 @@ this.WidgetMethods = {
* The matched item, or null if nothing is found.
*/
getItemForPredicate: function(aPredicate, aOwner = this) {
// Recursively check the items in this widget for a predicate match.
for (let [element, item] of aOwner._itemsByElement) {
let match;
if (aPredicate(item) && !element.hidden) {
@ -1282,6 +1283,13 @@ this.WidgetMethods = {
return match;
}
}
// Also check the staged items. No need to do this recursively since
// they're not even appended to the view yet.
for (let { item } of this._stagedItems) {
if (aPredicate(item)) {
return item;
}
}
return null;
},
@ -1349,6 +1357,14 @@ this.WidgetMethods = {
return this.items.map(e => e._value);
},
/**
* Returns a list of attachments in this container, in the displayed order.
* @return array
*/
get attachments() {
return this.items.map(e => e.attachment);
},
/**
* Returns a list of all the visible (non-hidden) items in this container,
* in the displayed order

View File

@ -125,6 +125,11 @@ button that pretty prints the selected source. -->
<!ENTITY debuggerUI.seMenuCondBreak "Add conditional breakpoint">
<!ENTITY debuggerUI.seMenuCondBreak.key "B">
<!-- LOCALIZATION NOTE (debuggerUI.instruments.*): This is the text that
- appears in the debugger's instruments pane tabs. -->
<!ENTITY debuggerUI.tabs.variables "Variables">
<!ENTITY debuggerUI.tabs.events "Events">
<!-- LOCALIZATION NOTE (debuggerUI.seMenuAddWatch): This is the text that
- appears in the source editor context menu for adding an expression. -->
<!ENTITY debuggerUI.seMenuAddWatch "Selection to watch expression">

View File

@ -61,7 +61,52 @@ noGlobalsText=No globals
# when there are no scripts.
noSourcesText=This page has no sources.
# LOCALIZATION NOTE (blackBoxCheckboxTooltip) = The tooltip text to display when
# LOCALIZATION NOTE (noEventsTExt): The text to display in the events tab
# when there are no events.
noEventListenersText=No event listeners to display
# LOCALIZATION NOTE (eventCheckboxTooltip): The tooltip text to display when
# the user hovers over the checkbox used to toggle an event breakpoint.
eventCheckboxTooltip=Toggle breaking on this event
# LOCALIZATION NOTE (eventOnSelector): The text to display in the events tab
# for every event item, between the event type and event selector.
eventOnSelector=on
# LOCALIZATION NOTE (eventInSource): The text to display in the events tab
# for every event item, between the event selector and listener's owner source.
eventInSource=in
# LOCALIZATION NOTE (eventNodes): The text to display in the events tab when
# an event is listened on more than one target node.
eventNodes=%S nodes
# LOCALIZATION NOTE (*Events): The text to display in the events tab for
# each group of sub-level event entries.
animationEvents=Animation
audioEvents=Audio
batteryEvents=Battery
clipboardEvents=Clipboard
compositionEvents=Composition
deviceEvents=Device
displayEvents=Display
dragAndDropEvents=Drag and Drop
gamepadEvents=Gamepad
indexedDBEvents=IndexedDB
interactionEvents=Interaction
keyboardEvents=Keyboard
mediaEvents=HTML5 Media
mouseEvents=Mouse
mutationEvents=Mutation
navigationEvents=Navigation
pointerLockEvents=Pointer Lock
sensorEvents=Sensor
storageEvents=Storage
timeEvents=Time
touchEvents=Touch
otherEvents=Other
# LOCALIZATION NOTE (blackBoxCheckboxTooltip): The tooltip text to display when
# the user hovers over the checkbox used to toggle black boxing its associated
# source.
blackBoxCheckboxTooltip=Toggle black boxing

View File

@ -22,28 +22,26 @@
font-weight: bold;
}
.side-menu-widget-item-checkbox {
#sources .side-menu-widget-item-checkbox {
-moz-appearance: none;
-moz-margin-end: -6px;
padding: 0;
opacity: 0;
transition: opacity .15s ease 0s;
}
/* Only show the checkbox when the source is hovered over, is selected, or if it
* is not checked. */
.side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
.side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
.side-menu-widget-item-checkbox:not([checked]) {
#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item-checkbox:not([checked]) {
opacity: 1;
transition: opacity .15s ease 0s;
}
.side-menu-widget-item-checkbox > .checkbox-spacer-box {
#sources .side-menu-widget-item-checkbox > .checkbox-spacer-box {
-moz-appearance: none;
}
.side-menu-widget-item-checkbox > .checkbox-spacer-box > .checkbox-check {
#sources .side-menu-widget-item-checkbox > .checkbox-spacer-box > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
@ -56,15 +54,15 @@
border: 0;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-spacer-box > .checkbox-check {
#sources .side-menu-widget-item-checkbox[checked] > .checkbox-spacer-box > .checkbox-check {
background-position: 0 0;
}
.side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
color: #888;
}
.side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
display: none;
}
@ -153,6 +151,17 @@
margin: 2px;
}
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
min-height: 2em !important;
padding: 0 !important;
}
#instruments-pane > tabpanels > tabpanel {
background: #fff;
}
/* Watch expressions view */
#expressions {
@ -167,13 +176,35 @@
.dbg-expression-arrow {
width: 16px;
height: auto;
background: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 32, 16, 16);
background: -moz-image-rect(url(commandline-icon.png), 0, 32, 16, 16);
}
.dbg-expression-input {
-moz-padding-start: 2px !important;
}
/* Event listeners view */
.dbg-event-listener {
padding: 4px 8px;
}
.dbg-event-listener-type {
font-weight: 600;
}
.dbg-event-listener-separator {
color: #999;
}
.dbg-event-listener-targets {
color: #046;
}
.dbg-event-listener-location {
color: #666;
}
/* Searchbox and the search operations help panel */
.devtools-searchinput {
@ -325,7 +356,7 @@
transform: scale(1.75, 1.75);
}
/* Toolbar Controls */
/* Toolbar controls */
#resumption-panel-desc {
width: 200px;

View File

@ -269,57 +269,74 @@
/* SideMenuWidget */
.side-menu-widget-container {
.side-menu-widget-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
color: #fff;
}
.side-menu-widget-container[with-arrow=true]:-moz-locale-dir(ltr),
.side-menu-widget-group[with-arrow=true]:-moz-locale-dir(ltr),
.side-menu-widget-item[with-arrow=true]:-moz-locale-dir(ltr) {
.side-menu-widget-container[theme="light"] {
background: #fff;
color: #000;
}
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
.side-menu-widget-container[with-arrow=true]:-moz-locale-dir(rtl),
.side-menu-widget-group[with-arrow=true]:-moz-locale-dir(rtl),
.side-menu-widget-item[with-arrow=true]:-moz-locale-dir(rtl) {
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-group-title {
padding: 4px;
}
.side-menu-widget-group-title[theme="dark"] {
background-image: linear-gradient(#1f3e4f, #1b3243);
text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
0 -2px 0 hsla(206,37%,4%,.05) inset,
0 -1px 1px hsla(206,37%,4%,.1) inset;
text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
padding: 4px;
color: #f5f7fa;
transition-property: color, text-shadow;
transition-duration: 0.3s;
}
.side-menu-widget-group:hover > .side-menu-widget-group-title {
text-shadow: 0 0 1px #cfcfcf;
color: #fff;
.side-menu-widget-group-title[theme="light"] {
background-image: linear-gradient(#fff, #eee);
}
.side-menu-widget-item {
.side-menu-widget-group-checkbox {
margin: 0;
padding: 0;
}
.side-menu-widget-item[theme="dark"] {
border-top: 1px solid hsla(210,8%,5%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
margin-top: -1px;
margin-bottom: -1px;
cursor: pointer;
}
.side-menu-widget-item:last-of-type {
.side-menu-widget-item[theme="light"] {
border-top: 1px solid hsla(210,8%,75%,.25);
margin-top: -1px;
}
.side-menu-widget-item[theme="dark"]:last-of-type {
box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
}
.side-menu-widget-item.selected {
.side-menu-widget-item[theme="light"]:last-of-type {
box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
}
.side-menu-widget-item[theme="dark"].selected {
background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
}
.side-menu-widget-item[theme="light"].selected {
/* Nothing here yet */
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow {
background-size: auto, 1px 100%;
background-repeat: no-repeat;
@ -337,7 +354,6 @@
.side-menu-widget-item-label {
padding: 4px 0px;
cursor: inherit;
}
.side-menu-widget-item-arrow {
@ -345,6 +361,11 @@
width: 8px;
}
.side-menu-widget-item-checkbox {
-moz-margin-start: 4px;
-moz-margin-end: -6px;
}
.side-menu-widget-item-other {
background: url(background-noise-toolbar.png), hsla(208,11%,27%, 0.65);
}
@ -365,10 +386,19 @@
}
.side-menu-widget-empty-notice-container {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
padding: 12px;
color: #fff;
}
.side-menu-widget-empty-notice-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
font-weight: 600;
color: #fff;
}
.side-menu-widget-empty-notice-container[theme="light"] {
background: #fff;
padding: 4px 8px;
color: GrayText;
}
/* VariablesView */

View File

@ -24,24 +24,22 @@
font-weight: bold;
}
.side-menu-widget-item-checkbox {
#sources .side-menu-widget-item-checkbox {
-moz-appearance: none;
-moz-margin-end: -6px;
padding: 0;
opacity: 0;
transition: opacity .15s ease-out 0s;
}
/* Only show the checkbox when the source is hovered over, is selected, or if it
* is not checked. */
.side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
.side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
.side-menu-widget-item-checkbox:not([checked]) {
#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item-checkbox:not([checked]) {
opacity: 1;
transition: opacity .15s ease-out 0s;
}
.side-menu-widget-item-checkbox > .checkbox-check {
#sources .side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
@ -54,15 +52,15 @@
border: 0;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
#sources .side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
.side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
color: #888;
}
.side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
display: none;
}
@ -151,6 +149,17 @@
margin: 2px;
}
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
min-height: 2em !important;
padding: 0 !important;
}
#instruments-pane > tabpanels > tabpanel {
background: #fff;
}
/* Watch expressions view */
#expressions {
@ -165,13 +174,35 @@
.dbg-expression-arrow {
width: 16px;
height: auto;
background: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 32, 16, 16);
background: -moz-image-rect(url(commandline-icon.png), 0, 32, 16, 16);
}
.dbg-expression-input {
-moz-padding-start: 2px !important;
}
/* Event listeners view */
.dbg-event-listener {
padding: 4px 8px;
}
.dbg-event-listener-type {
font-weight: 600;
}
.dbg-event-listener-separator {
color: #999;
}
.dbg-event-listener-targets {
color: #046;
}
.dbg-event-listener-location {
color: #666;
}
/* Searchbox and the search operations help panel */
.devtools-searchinput {
@ -323,7 +354,7 @@
transform: scale(1.75, 1.75);
}
/* Toolbar Controls */
/* Toolbar controls */
#resumption-panel-desc {
width: 200px;

View File

@ -269,57 +269,74 @@
/* SideMenuWidget */
.side-menu-widget-container {
.side-menu-widget-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
color: #fff;
}
.side-menu-widget-container[with-arrow=true]:-moz-locale-dir(ltr),
.side-menu-widget-group[with-arrow=true]:-moz-locale-dir(ltr),
.side-menu-widget-item[with-arrow=true]:-moz-locale-dir(ltr) {
.side-menu-widget-container[theme="light"] {
background: #fff;
color: #000;
}
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
.side-menu-widget-container[with-arrow=true]:-moz-locale-dir(rtl),
.side-menu-widget-group[with-arrow=true]:-moz-locale-dir(rtl),
.side-menu-widget-item[with-arrow=true]:-moz-locale-dir(rtl) {
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-group-title {
padding: 4px;
}
.side-menu-widget-group-title[theme="dark"] {
background-image: linear-gradient(#1f3e4f, #1b3243);
text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
0 -2px 0 hsla(206,37%,4%,.05) inset,
0 -1px 1px hsla(206,37%,4%,.1) inset;
text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
padding: 4px;
color: #f5f7fa;
transition-property: color, text-shadow;
transition-duration: 0.3s;
}
.side-menu-widget-group:hover > .side-menu-widget-group-title {
text-shadow: 0 0 1px #cfcfcf;
color: #fff;
.side-menu-widget-group-title[theme="light"] {
background-image: linear-gradient(#fff, #eee);
}
.side-menu-widget-item {
.side-menu-widget-group-checkbox {
margin: 0;
padding: 0;
}
.side-menu-widget-item[theme="dark"] {
border-top: 1px solid hsla(210,8%,5%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
margin-top: -1px;
margin-bottom: -1px;
cursor: pointer;
}
.side-menu-widget-item:last-of-type {
.side-menu-widget-item[theme="light"] {
border-top: 1px solid hsla(210,8%,75%,.25);
margin-top: -1px;
}
.side-menu-widget-item[theme="dark"]:last-of-type {
box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
}
.side-menu-widget-item.selected {
.side-menu-widget-item[theme="light"]:last-of-type {
box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
}
.side-menu-widget-item[theme="dark"].selected {
background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
}
.side-menu-widget-item[theme="light"].selected {
/* Nothing here yet */
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow {
background-size: auto, 1px 100%;
background-repeat: no-repeat;
@ -337,7 +354,6 @@
.side-menu-widget-item-label {
padding: 4px 0px;
cursor: inherit;
}
.side-menu-widget-item-arrow {
@ -345,6 +361,11 @@
width: 8px;
}
.side-menu-widget-item-checkbox {
-moz-margin-start: 4px;
-moz-margin-end: -6px;
}
.side-menu-widget-item-other {
background: url(background-noise-toolbar.png), hsla(208,11%,27%, 0.65);
}
@ -365,10 +386,19 @@
}
.side-menu-widget-empty-notice-container {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
padding: 12px;
color: #fff;
}
.side-menu-widget-empty-notice-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
font-weight: 600;
color: #fff;
}
.side-menu-widget-empty-notice-container[theme="light"] {
background: #fff;
padding: 4px 8px;
color: GrayText;
}
/* VariablesView */

View File

@ -22,7 +22,7 @@
font-weight: bold;
}
.side-menu-widget-item-checkbox {
#sources .side-menu-widget-item-checkbox {
-moz-appearance: none;
-moz-margin-end: -6px;
padding: 0;
@ -32,14 +32,14 @@
/* Only show the checkbox when the source is hovered over, is selected, or if it
* is not checked. */
.side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
.side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
.side-menu-widget-item-checkbox:not([checked]) {
#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item-checkbox:not([checked]) {
opacity: 1;
transition: opacity .15s ease-out 0s;
}
.side-menu-widget-item-checkbox > .checkbox-check {
#sources .side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
@ -52,15 +52,15 @@
border: 0;
}
.side-menu-widget-item-checkbox[checked] > .checkbox-check {
#sources .side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
.side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
color: #888;
}
.side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
display: none;
}
@ -149,6 +149,17 @@
margin: 2px;
}
/* Instruments pane (watch expressions, variables, event listeners...) */
#instruments-pane > tabs > tab {
min-height: 2em !important;
padding: 0 !important;
}
#instruments-pane > tabpanels > tabpanel {
background: #fff;
}
/* Watch expressions view */
#expressions {
@ -163,13 +174,35 @@
.dbg-expression-arrow {
width: 16px;
height: auto;
background: -moz-image-rect(url("chrome://browser/skin/devtools/commandline-icon.png"), 0, 32, 16, 16);
background: -moz-image-rect(url(commandline-icon.png), 0, 32, 16, 16);
}
.dbg-expression-input {
-moz-padding-start: 2px !important;
}
/* Event listeners view */
.dbg-event-listener {
padding: 4px 8px;
}
.dbg-event-listener-type {
font-weight: 600;
}
.dbg-event-listener-separator {
color: #999;
}
.dbg-event-listener-targets {
color: #046;
}
.dbg-event-listener-location {
color: #666;
}
/* Searchbox and the search operations help panel */
.devtools-searchinput {
@ -321,7 +354,7 @@
transform: scale(1.75, 1.75);
}
/* Toolbar Controls */
/* Toolbar controls */
#resumption-panel-desc {
width: 200px;

View File

@ -273,57 +273,74 @@
/* SideMenuWidget */
.side-menu-widget-container {
.side-menu-widget-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
color: #fff;
}
.side-menu-widget-container[with-arrow=true]:-moz-locale-dir(ltr),
.side-menu-widget-group[with-arrow=true]:-moz-locale-dir(ltr),
.side-menu-widget-item[with-arrow=true]:-moz-locale-dir(ltr) {
.side-menu-widget-container[theme="light"] {
background: #fff;
color: #000;
}
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(ltr) {
box-shadow: inset -1px 0 0 #222426;
}
.side-menu-widget-container[with-arrow=true]:-moz-locale-dir(rtl),
.side-menu-widget-group[with-arrow=true]:-moz-locale-dir(rtl),
.side-menu-widget-item[with-arrow=true]:-moz-locale-dir(rtl) {
.side-menu-widget-container[with-arrows=true]:-moz-locale-dir(rtl) {
box-shadow: inset 1px 0 0 #222426;
}
.side-menu-widget-group-title {
padding: 4px;
}
.side-menu-widget-group-title[theme="dark"] {
background-image: linear-gradient(#1f3e4f, #1b3243);
text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
0 -2px 0 hsla(206,37%,4%,.05) inset,
0 -1px 1px hsla(206,37%,4%,.1) inset;
text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
padding: 4px;
color: #f5f7fa;
transition-property: color, text-shadow;
transition-duration: 0.3s;
}
.side-menu-widget-group:hover > .side-menu-widget-group-title {
text-shadow: 0 0 1px #cfcfcf;
color: #fff;
.side-menu-widget-group-title[theme="light"] {
background-image: linear-gradient(#fff, #eee);
}
.side-menu-widget-item {
.side-menu-widget-group-checkbox {
margin: 0;
padding: 0;
}
.side-menu-widget-item[theme="dark"] {
border-top: 1px solid hsla(210,8%,5%,.25);
border-bottom: 1px solid hsla(210,16%,76%,.1);
margin-top: -1px;
margin-bottom: -1px;
cursor: pointer;
}
.side-menu-widget-item:last-of-type {
.side-menu-widget-item[theme="light"] {
border-top: 1px solid hsla(210,8%,75%,.25);
margin-top: -1px;
}
.side-menu-widget-item[theme="dark"]:last-of-type {
box-shadow: inset 0 -1px 0 hsla(210,8%,5%,.25);
}
.side-menu-widget-item.selected {
.side-menu-widget-item[theme="light"]:last-of-type {
box-shadow: inset 0 -1px 0 hsla(210,8%,75%,.25);
}
.side-menu-widget-item[theme="dark"].selected {
background: linear-gradient(hsl(206,61%,40%), hsl(206,61%,31%)) repeat-x top left !important;
box-shadow: inset 0 1px 0 hsla(210,40%,83%,.15);
}
.side-menu-widget-item[theme="light"].selected {
/* Nothing here yet */
}
.side-menu-widget-item.selected > .side-menu-widget-item-arrow {
background-size: auto, 1px 100%;
background-repeat: no-repeat;
@ -341,7 +358,6 @@
.side-menu-widget-item-label {
padding: 4px 0px;
cursor: inherit;
}
.side-menu-widget-item-arrow {
@ -349,6 +365,11 @@
width: 8px;
}
.side-menu-widget-item-checkbox {
-moz-margin-start: 4px;
-moz-margin-end: -6px;
}
.side-menu-widget-item-other {
background: url(background-noise-toolbar.png), hsla(208,11%,27%, 0.65);
}
@ -368,10 +389,19 @@
}
.side-menu-widget-empty-notice-container {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
padding: 12px;
color: #fff;
}
.side-menu-widget-empty-notice-container[theme="dark"] {
background: url(background-noise-toolbar.png), hsl(208,11%,27%);
font-weight: 600;
color: #fff;
}
.side-menu-widget-empty-notice-container[theme="light"] {
background: #fff;
padding: 4px 8px;
color: GrayText;
}
/* VariablesView */