Bug 1101569 - Adds an allTabs menu to the sidebar widget; r=dcamp

This commit is contained in:
Patrick Brosset 2015-01-15 10:47:12 +01:00
parent 30bf0d3aa3
commit 2df9e6f736
15 changed files with 534 additions and 83 deletions

View File

@ -6,6 +6,7 @@
const {Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
@ -18,6 +19,23 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
/**
* ToolSidebar provides methods to register tabs in the sidebar.
* It's assumed that the sidebar contains a xul:tabbox.
* Typically, you'll want the tabbox parameter to be a XUL tabbox like this:
*
* <tabbox id="inspector-sidebar" handleCtrlTab="false" class="devtools-sidebar-tabs">
* <tabs/>
* <tabpanels flex="1"/>
* </tabbox>
*
* The ToolSidebar API has a method to add new tabs, so the tabs and tabpanels
* nodes can be empty. But they can also already contain items before the
* ToolSidebar is created.
*
* Tabs added through the addTab method are only identified by an ID and a URL
* which is used as the href of an iframe node that is inserted in the newly
* created tabpanel.
* Tabs already present before the ToolSidebar is created may contain anything.
* However, these tabs must have ID attributes if it is required for the various
* methods that accept an ID as argument to work here.
*
* @param {Node} tabbox
* <tabbox> node;
@ -25,38 +43,168 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
* Related ToolPanel instance;
* @param {String} uid
* Unique ID
* @param {Boolean} showTabstripe
* Show the tabs.
* @param {Object} options
* - hideTabstripe: Should the tabs be hidden. Defaults to false
* - showAllTabsMenu: Should a drop-down menu be displayed in case tabs
* become hidden. Defaults to false.
* - disableTelemetry: By default, switching tabs on and off in the sidebar
* will record tool usage in telemetry, pass this option to true to avoid it.
*
* Events raised:
* - new-tab-registered : After a tab has been added via addTab. The tab ID
* is passed with the event. This however, is raised before the tab iframe
* is fully loaded.
* - <tabid>-ready : After the tab iframe has been loaded
* - <tabid>-selected : After tab <tabid> was selected
* - select : Same as above, but for any tab, the ID is passed with the event
* - <tabid>-unselected : After tab <tabid> is unselected
*/
function ToolSidebar(tabbox, panel, uid, showTabstripe=true)
{
function ToolSidebar(tabbox, panel, uid, options={}) {
EventEmitter.decorate(this);
this._tabbox = tabbox;
this._uid = uid;
this._panelDoc = this._tabbox.ownerDocument;
this._toolPanel = panel;
this._options = options;
this._onTabBoxOverflow = this._onTabBoxOverflow.bind(this);
this._onTabBoxUnderflow = this._onTabBoxUnderflow.bind(this);
try {
this._width = Services.prefs.getIntPref("devtools.toolsidebar-width." + this._uid);
} catch(e) {}
this._telemetry = new Telemetry();
if (!options.disableTelemetry) {
this._telemetry = new Telemetry();
}
this._tabbox.tabpanels.addEventListener("select", this, true);
this._tabs = new Map();
if (!showTabstripe) {
// Check for existing tabs in the DOM and add them.
this.addExistingTabs();
if (this._options.hideTabstripe) {
this._tabbox.setAttribute("hidetabs", "true");
}
if (this._options.showAllTabsMenu) {
this.addAllTabsMenu();
}
this._toolPanel.emit("sidebar-created", this);
}
exports.ToolSidebar = ToolSidebar;
ToolSidebar.prototype = {
TAB_ID_PREFIX: "sidebar-tab-",
TABPANEL_ID_PREFIX: "sidebar-panel-",
/**
* Add a "…" button at the end of the tabstripe that toggles a dropdown menu
* containing the list of all tabs if any become hidden due to lack of room.
*
* If the ToolSidebar was created with the "showAllTabsMenu" option set to
* true, this is already done automatically. If not, you may call this
* function at any time to add the menu.
*/
addAllTabsMenu: function() {
if (this._allTabsBtn) {
return;
}
let tabs = this._tabbox.tabs;
// Create a toolbar and insert it first in the tabbox
let allTabsToolbar = this._panelDoc.createElementNS(XULNS, "toolbar");
this._tabbox.insertBefore(allTabsToolbar, tabs);
// Move the tabs inside and make them flex
allTabsToolbar.appendChild(tabs);
tabs.setAttribute("flex", "1");
// Create the dropdown menu next to the tabs
this._allTabsBtn = this._panelDoc.createElementNS(XULNS, "toolbarbutton");
this._allTabsBtn.setAttribute("class", "devtools-sidebar-alltabs");
this._allTabsBtn.setAttribute("type", "menu");
this._allTabsBtn.setAttribute("label", l10n("sidebar.showAllTabs.label"));
this._allTabsBtn.setAttribute("tooltiptext", l10n("sidebar.showAllTabs.tooltip"));
this._allTabsBtn.setAttribute("hidden", "true");
allTabsToolbar.appendChild(this._allTabsBtn);
let menuPopup = this._panelDoc.createElementNS(XULNS, "menupopup");
this._allTabsBtn.appendChild(menuPopup);
// Listening to tabs overflow event to toggle the alltabs button
tabs.addEventListener("overflow", this._onTabBoxOverflow, false);
tabs.addEventListener("underflow", this._onTabBoxUnderflow, false);
// Add menuitems to the alltabs menu if there are already tabs in the
// sidebar
for (let [id, tab] of this._tabs) {
this._addItemToAllTabsMenu(id, tab, tab.hasAttribute("selected"));
}
},
removeAllTabsMenu: function() {
if (!this._allTabsBtn) {
return;
}
let tabs = this._tabbox.tabs;
tabs.removeEventListener("overflow", this._onTabBoxOverflow, false);
tabs.removeEventListener("underflow", this._onTabBoxUnderflow, false);
// Moving back the tabs as a first child of the tabbox
this._tabbox.insertBefore(tabs, this._tabbox.tabpanels);
this._tabbox.querySelector("toolbar").remove();
this._allTabsBtn = null;
},
_onTabBoxOverflow: function() {
this._allTabsBtn.removeAttribute("hidden");
},
_onTabBoxUnderflow: function() {
this._allTabsBtn.setAttribute("hidden", "true");
},
/**
* Add an item in the allTabs menu for a given tab.
*/
_addItemToAllTabsMenu: function(id, tab, selected=false) {
if (!this._allTabsBtn) {
return;
}
let item = this._panelDoc.createElementNS(XULNS, "menuitem");
item.setAttribute("id", "sidebar-alltabs-item-" + id);
item.setAttribute("label", tab.getAttribute("label"));
item.setAttribute("type", "checkbox");
if (selected) {
item.setAttribute("checked", true);
}
// The auto-checking of menuitems in this menu doesn't work, so let's do
// it manually
item.setAttribute("autocheck", false);
this._allTabsBtn.querySelector("menupopup").appendChild(item);
item.addEventListener("click", () => {
this._tabbox.selectedTab = tab;
}, false);
tab.allTabsMenuItem = item;
return item;
},
/**
* Register a tab. A tab is a document.
* The document must have a title, which will be used as the name of the tab.
@ -64,22 +212,31 @@ ToolSidebar.prototype = {
* @param {string} tab uniq id
* @param {string} url
*/
addTab: function ToolSidebar_addTab(id, url, selected=false) {
addTab: function(id, url, selected=false) {
let iframe = this._panelDoc.createElementNS(XULNS, "iframe");
iframe.className = "iframe-" + id;
iframe.setAttribute("flex", "1");
iframe.setAttribute("src", url);
iframe.tooltip = "aHTMLTooltip";
let tab = this._tabbox.tabs.appendItem();
// Creating the tab and adding it to the tabbox
let tab = this._panelDoc.createElementNS(XULNS, "tab");
this._tabbox.tabs.appendChild(tab);
tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
tab.setAttribute("id", "sidebar-tab-" + id);
tab.setAttribute("id", this.TAB_ID_PREFIX + id);
// Add the tab to the allTabs menu if exists
let allTabsItem = this._addItemToAllTabsMenu(id, tab, selected);
let onIFrameLoaded = (event) => {
let doc = event.target;
let win = doc.defaultView;
tab.setAttribute("label", doc.title);
if (allTabsItem) {
allTabsItem.setAttribute("label", doc.title);
}
iframe.removeEventListener("load", onIFrameLoaded, true);
if ("setPanel" in win) {
win.setPanel(this._toolPanel, iframe);
@ -90,7 +247,7 @@ ToolSidebar.prototype = {
iframe.addEventListener("load", onIFrameLoaded, true);
let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel");
tabpanel.setAttribute("id", "sidebar-panel-" + id);
tabpanel.setAttribute("id", this.TABPANEL_ID_PREFIX + id);
tabpanel.appendChild(iframe);
this._tabbox.tabpanels.appendChild(tabpanel);
@ -99,7 +256,7 @@ ToolSidebar.prototype = {
tabpanel.appendChild(this._tooltip);
this._tooltip.page = true;
tab.linkedPanel = "sidebar-panel-" + id;
tab.linkedPanel = this.TABPANEL_ID_PREFIX + id;
// We store the index of this tab.
this._tabs.set(id, tab);
@ -116,37 +273,78 @@ ToolSidebar.prototype = {
this.emit("new-tab-registered", id);
},
untitledTabsIndex: 0,
/**
* Search for existing tabs in the markup that aren't know yet and add them.
*/
addExistingTabs: function() {
let knownTabs = [...this._tabs.values()];
for (let tab of this._tabbox.tabs.querySelectorAll("tab")) {
if (knownTabs.indexOf(tab) !== -1) {
continue;
}
// Find an ID for this unknown tab
let id = tab.getAttribute("id") || "untitled-tab-" + (this.untitledTabsIndex++);
// Register the tab
this._tabs.set(id, tab);
this.emit("new-tab-registered", id);
}
},
/**
* Remove an existing tab.
* @param {String} tabId The ID of the tab that was used to register it, or
* the tab id attribute value if the tab existed before the sidebar got created.
* @param {String} tabPanelId Optional. If provided, this ID will be used
* instead of the tabId to retrieve and remove the corresponding <tabpanel>
*/
removeTab: Task.async(function*(id) {
let tab = this._tabbox.tabs.querySelector("tab#sidebar-tab-" + id);
removeTab: Task.async(function*(tabId, tabPanelId) {
// Remove the tab if it can be found
let tab = this.getTab(tabId);
if (!tab) {
return;
}
let win = this.getWindowForTab(id);
if ("destroy" in win) {
let win = this.getWindowForTab(tabId);
if (win && ("destroy" in win)) {
yield win.destroy();
}
tab.remove();
let panel = this.getTab(id);
// Also remove the tabpanel
let panel = this.getTabPanel(tabPanelId || tabId);
if (panel) {
panel.remove();
}
this._tabs.delete(id);
this.emit("tab-unregistered", id);
this._tabs.delete(tabId);
this.emit("tab-unregistered", tabId);
}),
/**
* Show or hide a specific tab
*/
toggleTab: function(id, isVisible) {
let tab = this.getTab(id);
if (!tab) {
return;
}
tab.hidden = !isVisible;
if (this._allTabsBtn) {
this._allTabsBtn.querySelector("#sidebar-alltabs-item-" + id).hidden = !isVisible;
}
},
/**
* Select a specific tab.
*/
select: function ToolSidebar_select(id) {
let tab = this._tabs.get(id);
select: function(id) {
let tab = this.getTab(id);
if (tab) {
this._tabbox.selectedTab = tab;
}
@ -155,7 +353,7 @@ ToolSidebar.prototype = {
/**
* Return the id of the selected tab.
*/
getCurrentTabID: function ToolSidebar_getCurrentTabID() {
getCurrentTabID: function() {
let currentID = null;
for (let [id, tab] of this._tabs) {
if (this._tabbox.tabs.selectedItem == tab) {
@ -167,42 +365,75 @@ ToolSidebar.prototype = {
},
/**
* Returns the requested tab based on the id.
*
* @param String id
* unique id of the requested tab.
* Returns the requested tab panel based on the id.
* @param {String} id
* @return {DOMNode}
*/
getTab: function ToolSidebar_getTab(id) {
return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id);
getTabPanel: function(id) {
// Search with and without the ID prefix as there might have been existing
// tabpanels by the time the sidebar got created
return this._tabbox.tabpanels.querySelector("#" + this.TABPANEL_ID_PREFIX + id + ", #" + id);
},
/**
* Return the tab based on the provided id, if one was registered with this id.
* @param {String} id
* @return {DOMNode}
*/
getTab: function(id) {
return this._tabs.get(id);
},
/**
* Event handler.
*/
handleEvent: function ToolSidebar_eventHandler(event) {
if (event.type == "select") {
if (this._currentTool == this.getCurrentTabID()) {
// Tool hasn't changed.
return;
}
handleEvent: function(event) {
if (event.type !== "select" || this._destroyed) {
return;
}
let previousTool = this._currentTool;
this._currentTool = this.getCurrentTabID();
if (previousTool) {
if (this._currentTool == this.getCurrentTabID()) {
// Tool hasn't changed.
return;
}
let previousTool = this._currentTool;
this._currentTool = this.getCurrentTabID();
if (previousTool) {
if (this._telemetry) {
this._telemetry.toolClosed(previousTool);
this.emit(previousTool + "-unselected");
}
this.emit(previousTool + "-unselected");
}
if (this._telemetry) {
this._telemetry.toolOpened(this._currentTool);
this.emit(this._currentTool + "-selected");
this.emit("select", this._currentTool);
}
this.emit(this._currentTool + "-selected");
this.emit("select", this._currentTool);
// Handlers for "select"/"...-selected"/"...-unselected" events might have
// destroyed the sidebar in the meantime.
if (this._destroyed) {
return;
}
// Handle menuitem selection if the allTabsMenu is there by unchecking all
// items except the selected one.
let tab = this._tabbox.selectedTab;
if (tab.allTabsMenuItem) {
for (let otherItem of this._allTabsBtn.querySelectorAll("menuitem")) {
otherItem.removeAttribute("checked");
}
tab.allTabsMenuItem.setAttribute("checked", true);
}
},
/**
* Toggle sidebar's visibility state.
*/
toggle: function ToolSidebar_toggle() {
toggle: function() {
if (this._tabbox.hasAttribute("hidden")) {
this.show();
} else {
@ -213,7 +444,7 @@ ToolSidebar.prototype = {
/**
* Show the sidebar.
*/
show: function ToolSidebar_show() {
show: function() {
if (this._width) {
this._tabbox.width = this._width;
}
@ -225,7 +456,7 @@ ToolSidebar.prototype = {
/**
* Show the sidebar.
*/
hide: function ToolSidebar_hide() {
hide: function() {
Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
this._tabbox.setAttribute("hidden", "true");
@ -235,12 +466,16 @@ ToolSidebar.prototype = {
/**
* Return the window containing the tab content.
*/
getWindowForTab: function ToolSidebar_getWindowForTab(id) {
getWindowForTab: function(id) {
if (!this._tabs.has(id)) {
return null;
}
let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel);
// Get the tabpanel and make sure it contains an iframe
let panel = this.getTabPanel(id);
if (!panel || !panel.firstChild || !panel.firstChild.contentWindow) {
return;
}
return panel.firstChild.contentWindow;
},
@ -249,12 +484,16 @@ ToolSidebar.prototype = {
*/
destroy: Task.async(function*() {
if (this._destroyed) {
return promise.resolve(null);
return;
}
this._destroyed = true;
Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
if (this._allTabsBtn) {
this.removeAllTabsMenu();
}
this._tabbox.tabpanels.removeEventListener("select", this, true);
// Note that we check for the existence of this._tabbox.tabpanels at each
@ -263,7 +502,7 @@ ToolSidebar.prototype = {
while (this._tabbox.tabpanels && this._tabbox.tabpanels.hasChildNodes()) {
let panel = this._tabbox.tabpanels.firstChild;
let win = panel.firstChild.contentWindow;
if ("destroy" in win) {
if (win && ("destroy" in win)) {
yield win.destroy();
}
panel.remove();
@ -273,7 +512,7 @@ ToolSidebar.prototype = {
this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
}
if (this._currentTool) {
if (this._currentTool && this._telemetry) {
this._telemetry.toolClosed(this._currentTool);
}
@ -283,7 +522,21 @@ ToolSidebar.prototype = {
this._tabbox = null;
this._panelDoc = null;
this._toolPanel = null;
return promise.resolve(null);
}),
})
}
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
let l10n = function(aName, ...aArgs) {
try {
if (aArgs.length == 0) {
return bundle.GetStringFromName(aName);
} else {
return bundle.formatStringFromName(aName, aArgs, aArgs.length);
}
} catch (ex) {
Services.console.logStringMessage("Error reading '" + aName + "'");
}
};
return l10n;
});

View File

@ -4,6 +4,7 @@ support-files =
browser_toolbox_options_disable_js.html
browser_toolbox_options_disable_js_iframe.html
browser_toolbox_options_disable_cache.sjs
browser_toolbox_sidebar_tool.xul
head.js
helper_disable_cache.js
doc_theme.css
@ -39,6 +40,8 @@ skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e1
skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
[browser_toolbox_sidebar.js]
[browser_toolbox_sidebar_events.js]
[browser_toolbox_sidebar_existing_tabs.js]
[browser_toolbox_sidebar_overflow_menu.js]
[browser_toolbox_tabsswitch_shortcuts.js]
[browser_toolbox_tool_ready.js]
[browser_toolbox_tool_remote_reopen.js]

View File

@ -3,8 +3,7 @@
// Tests that disabling JavaScript for a tab works as it should.
const TEST_URI = "http://example.com/browser/browser/devtools/framework/" +
"test/browser_toolbox_options_disable_js.html";
const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_js.html";
let doc;
let toolbox;

View File

@ -111,7 +111,7 @@ function test() {
is(label, 4, "Found the right amount of tabs.");
is(panel.sidebar._tabbox.selectedPanel, panels[0], "First tab is selected");
ok(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
is(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
panel.sidebar.once("tab1-unselected", function() {
ok(true, "received 'unselected' event");
@ -154,6 +154,7 @@ function test() {
panel.sidebar = new ToolSidebar(tabbox, panel, "testbug865688", true);
panel.sidebar.show();
is(panel.panelDoc.getElementById("sidebar").width, 420, "Width restored")
finishUp(panel);
});
}

View File

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the sidebar widget auto-registers existing tabs.
const Cu = Components.utils;
const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
const testToolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
"<?xml-stylesheet href='chrome://browser/skin/devtools/common.css' type='text/css'?>" +
"<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
"<hbox flex='1'><description flex='1'>test tool</description>" +
"<splitter class='devtools-side-splitter'/>" +
"<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'>" +
"<tabs><tab id='tab1' label='tab 1'></tab><tab id='tab2' label='tab 2'></tab></tabs>" +
"<tabpanels flex='1'><tabpanel id='tabpanel1'>tab 1</tabpanel><tabpanel id='tabpanel2'>tab 2</tabpanel></tabpanels>" +
"</tabbox></hbox></window>";
const testToolDefinition = {
id: "testTool",
url: testToolURL,
label: "Test Tool",
isTargetSupported: () => true,
build: (iframeWindow, toolbox) => {
return promise.resolve({
target: toolbox.target,
toolbox: toolbox,
isReady: true,
destroy: () => {},
panelDoc: iframeWindow.document,
});
}
};
add_task(function*() {
let tab = yield addTab("about:blank");
let target = TargetFactory.forTab(tab);
gDevTools.registerTool(testToolDefinition);
let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
let toolPanel = toolbox.getPanel(testToolDefinition.id);
let tabbox = toolPanel.panelDoc.getElementById("sidebar");
info("Creating the sidebar widget");
let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569");
info("Checking that existing tabs have been registered");
ok(sidebar.getTab("tab1"), "Existing tab 1 was found");
ok(sidebar.getTab("tab2"), "Existing tab 2 was found");
ok(sidebar.getTabPanel("tabpanel1"), "Existing tabpanel 1 was found");
ok(sidebar.getTabPanel("tabpanel2"), "Existing tabpanel 2 was found");
info("Checking that the sidebar API works with existing tabs");
sidebar.select("tab2");
is(tabbox.selectedTab, tabbox.querySelector("#tab2"),
"Existing tabs can be selected");
sidebar.select("tab1");
is(tabbox.selectedTab, tabbox.querySelector("#tab1"),
"Existing tabs can be selected");
is(sidebar.getCurrentTabID(), "tab1", "getCurrentTabID returns the expected id");
info("Removing a tab");
sidebar.removeTab("tab2", "tabpanel2");
ok(!sidebar.getTab("tab2"), "Tab 2 was removed correctly");
ok(!sidebar.getTabPanel("tabpanel2"), "Tabpanel 2 was removed correctly");
sidebar.destroy();
gDevTools.unregisterTool(testToolDefinition.id);
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,76 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the sidebar widget correctly displays the "all tabs..." button
// when the tabs overflow.
const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
const testToolDefinition = {
id: "testTool",
url: CHROME_URL_ROOT + "browser_toolbox_sidebar_tool.xul",
label: "Test Tool",
isTargetSupported: () => true,
build: (iframeWindow, toolbox) => {
return {
target: toolbox.target,
toolbox: toolbox,
isReady: true,
destroy: () => {},
panelDoc: iframeWindow.document,
};
}
};
add_task(function*() {
let tab = yield addTab("about:blank");
let target = TargetFactory.forTab(tab);
gDevTools.registerTool(testToolDefinition);
let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
let toolPanel = toolbox.getPanel(testToolDefinition.id);
let tabbox = toolPanel.panelDoc.getElementById("sidebar");
info("Creating the sidebar widget");
let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569", {
showAllTabsMenu: true
});
let allTabsMenu = toolPanel.panelDoc.querySelector(".devtools-sidebar-alltabs");
ok(allTabsMenu, "The all-tabs menu is available");
is(allTabsMenu.getAttribute("hidden"), "true", "The menu is hidden for now");
info("Adding 10 tabs to the sidebar widget");
for (let nb = 0; nb < 10; nb ++) {
let url = `data:text/html;charset=utf8,<title>tab ${nb}</title><p>Test tab ${nb}</p>`;
sidebar.addTab("tab" + nb, url, nb === 0);
}
info("Fake an overflow event so that the all-tabs menu is visible");
sidebar._onTabBoxOverflow();
ok(!allTabsMenu.hasAttribute("hidden"), "The all-tabs menu is now shown");
info("Select each tab, one by one");
for (let nb = 0; nb < 10; nb ++) {
let id = "tab" + nb;
info("Found tab item nb " + nb);
let item = allTabsMenu.querySelector("#sidebar-alltabs-item-" + id);
info("Click on the tab");
EventUtils.sendMouseEvent({type: "click"}, item, toolPanel.panelDoc.defaultView);
is(tabbox.selectedTab.id, "sidebar-tab-" + id,
"The selected tab is now nb " + nb);
}
info("Fake an underflow event so that the all-tabs menu gets hidden");
sidebar._onTabBoxUnderflow();
is(allTabsMenu.getAttribute("hidden"), "true", "The all-tabs menu is hidden");
yield sidebar.destroy();
gDevTools.unregisterTool(testToolDefinition.id);
gBrowser.removeCurrentTab();
});

View File

@ -0,0 +1,19 @@
<?xml version="1.0"?>
<!-- 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/. -->
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
<box flex="1" class="devtools-responsive-container theme-body">
<vbox flex="1" class="devtools-main-content" id="content">test</vbox>
<splitter class="devtools-side-splitter"/>
<tabbox flex="1" id="sidebar" class="devtools-sidebar-tabs">
<tabs/>
<tabpanels flex="1"/>
</tabbox>
</box>
</window>

View File

@ -8,6 +8,9 @@ const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {})
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const URL_ROOT = "http://example.com/browser/browser/devtools/framework/test/";
const CHROME_URL_ROOT = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
let TargetFactory = devtools.TargetFactory;
// All test are asynchronous

View File

@ -4,8 +4,7 @@
"use strict";
// Common code shared by browser_toolbox_options_disable_cache-*.js
const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/framework/" +
"test/browser_toolbox_options_disable_cache.sjs";
const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_cache.sjs";
let tabs = [
{
title: "Tab 0",

View File

@ -347,8 +347,6 @@ InspectorPanel.prototype = {
"animationinspector" == defaultTab);
}
let ruleViewTab = this.sidebar.getTab("ruleview");
this.sidebar.show();
},

View File

@ -3555,8 +3555,8 @@ JSTerm.prototype = {
deferred.resolve(window);
};
let tab = this.sidebar.getTab("variablesview");
if (tab) {
let tabPanel = this.sidebar.getTabPanel("variablesview");
if (tabPanel) {
if (this.sidebar.getCurrentTabID() == "variablesview") {
onTabReady();
}

View File

@ -71,6 +71,16 @@ browserConsoleCmd.commandkey=j
# This is the tooltip of the pick button in the toolbox toolbar
pickButton.tooltip=Pick an element from the page
# LOCALIZATION NOTE (sidebar.showAllTabs.label)
# This is the label shown in the show all tabs button in the tabbed side
# bar, when there's no enough space to show all tabs at once
sidebar.showAllTabs.label=
# LOCALIZATION NOTE (sidebar.showAllTabs.tooltip)
# This is the tooltip shown when hover over the '…' button in the tabbed side
# bar, when there's no enough space to show all tabs at once
sidebar.showAllTabs.tooltip=All tabs
# LOCALIZATION NOTE (options.darkTheme.label)
# Used as a label for dark theme
options.darkTheme.label=Dark theme

View File

@ -191,7 +191,8 @@
.theme-toolbar,
.devtools-toolbar,
.devtools-sidebar-tabs > tabs,
.devtools-sidebar-tabs tabs,
.devtools-sidebar-alltabs,
.CodeMirror-dialog { /* General toolbar styling */
color: var(--theme-body-color-alt);
background-color: var(--theme-toolbar-background);

View File

@ -194,7 +194,8 @@
.theme-toolbar,
.devtools-toolbar,
.devtools-sidebar-tabs > tabs,
.devtools-sidebar-tabs tabs,
.devtools-sidebar-alltabs,
.CodeMirror-dialog { /* General toolbar styling */
color: var(--theme-body-color-alt);
background-color: var(--theme-toolbar-background);

View File

@ -11,7 +11,8 @@
/* Toolbars */
.devtools-toolbar,
.devtools-sidebar-tabs > tabs {
.devtools-sidebar-tabs tabs,
.devtools-sidebar-alltabs {
-moz-appearance: none;
padding: 0;
border-width: 0;
@ -416,19 +417,30 @@
color: var(--theme-body-color);
}
.devtools-sidebar-tabs > tabs {
.devtools-sidebar-tabs tabs {
position: static;
font: inherit;
margin-bottom: 0;
overflow: hidden;
}
.devtools-sidebar-tabs > tabs > .tabs-right,
.devtools-sidebar-tabs > tabs > .tabs-left {
.devtools-sidebar-alltabs {
margin: 0;
border-width: 0 0 1px 0;
-moz-border-start-width: 1px;
border-style: solid;
}
.devtools-sidebar-alltabs dropmarker {
display: none;
}
.devtools-sidebar-tabs > tabs > tab {
.devtools-sidebar-tabs tabs > .tabs-right,
.devtools-sidebar-tabs tabs > .tabs-left {
display: none;
}
.devtools-sidebar-tabs tabs > tab {
-moz-appearance: none;
/* We want to match the height of a toolbar with a toolbarbutton
* First, we need to replicated the padding of toolbar (4px),
@ -449,70 +461,70 @@
text-shadow: none;
}
.devtools-sidebar-tabs > tabs > tab:first-child {
.devtools-sidebar-tabs tabs > tab:first-child {
-moz-border-start-width: 0;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab {
.theme-dark .devtools-sidebar-tabs tabs > tab {
border-image: @smallSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab:hover {
.theme-dark .devtools-sidebar-tabs tabs > tab:hover {
background: hsla(206,37%,4%,.2);
border-image: @smallSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab:hover:active {
.theme-dark .devtools-sidebar-tabs tabs > tab:hover:active {
background: hsla(206,37%,4%,.4);
border-image: @smallSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab {
border-image: @solidSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
background: hsla(206,37%,4%,.2);
border-image: @solidSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover:active {
background: hsla(206,37%,4%,.4);
border-image: @solidSeparatorDark@ 1 1;
}
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected],
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
.theme-dark .devtools-sidebar-tabs tabs > tab[selected],
.theme-dark .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
color: var(--theme-selection-color);
background: #1d4f73;
border-image: @solidSeparatorDark@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab {
.theme-light .devtools-sidebar-tabs tabs > tab {
border-image: @smallSeparatorLight@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab:hover {
.theme-light .devtools-sidebar-tabs tabs > tab:hover {
background: #ddd;
border-image: @smallSeparatorLight@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab:hover:active {
.theme-light .devtools-sidebar-tabs tabs > tab:hover:active {
background: #ddd;
border-image: @smallSeparatorLight@ 1 1;
}
.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab {
.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab {
border-image: @solidSeparatorLight@;
}
.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
background: #ddd;
border-image: @solidSeparatorLight@;
}
.theme-light .devtools-sidebar-tabs > tabs > tab[selected],
.theme-light .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
.theme-light .devtools-sidebar-tabs tabs > tab[selected],
.theme-light .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
color: var(--theme-selection-color);
background: #4c9ed9;
border-image: @solidSeparatorLight@;