--- a/devtools/client/animationinspector/animation-controller.js
+++ b/devtools/client/animationinspector/animation-controller.js
@@ -210,17 +210,17 @@ var AnimationsController = {
if (this.isListeningToMutations) {
this.animationsFront.off("mutations", this.onAnimationMutations);
}
},
isPanelVisible: function () {
return gToolbox.currentToolId === "inspector" &&
gInspector.sidebar &&
- gInspector.sidebar.getCurrentTabID() == "animationinspector";
+ gInspector.sidebar.tabbar.getCurrentTabId() == "animationinspector";
},
onPanelVisibilityChange: Task.async(function* () {
if (this.isPanelVisible()) {
this.onNewNodeFront();
}
}),
--- a/devtools/client/animationinspector/test/browser_animation_refresh_when_active.js
+++ b/devtools/client/animationinspector/test/browser_animation_refresh_when_active.js
@@ -15,39 +15,39 @@ add_task(function* () {
yield testRefresh(inspector, panel);
});
function* testRefresh(inspector, panel) {
info("Select a non animated node");
yield selectNodeAndWaitForAnimations(".still", inspector);
info("Switch to the rule-view panel");
- inspector.sidebar.select("ruleview");
+ inspector.sidebar.tabbar.select("ruleview");
info("Select the animated node now");
yield selectNodeAndWaitForAnimations(".animated", inspector);
assertAnimationsDisplayed(panel, 0,
"The panel doesn't show the animation data while inactive");
info("Switch to the animation panel");
- inspector.sidebar.select("animationinspector");
+ inspector.sidebar.tabbar.select("animationinspector");
yield panel.once(panel.UI_UPDATED_EVENT);
assertAnimationsDisplayed(panel, 1,
"The panel shows the animation data after selecting it");
info("Switch again to the rule-view");
- inspector.sidebar.select("ruleview");
+ inspector.sidebar.tabbar.select("ruleview");
info("Select the non animated node again");
yield selectNodeAndWaitForAnimations(".still", inspector);
assertAnimationsDisplayed(panel, 1,
"The panel still shows the previous animation data since it is inactive");
info("Switch to the animation panel again");
- inspector.sidebar.select("animationinspector");
+ inspector.sidebar.tabbar.select("animationinspector");
yield panel.once(panel.UI_UPDATED_EVENT);
assertAnimationsDisplayed(panel, 0,
"The panel is now empty after refreshing");
}
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -6,16 +6,18 @@
"use strict";
/* import-globals-from ../../inspector/test/head.js */
// Import the inspector's head.js first (which itself imports shared-head.js).
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
this);
+// Services.prefs.setBoolPref("devtools.dump.emit", true);
+
const FRAME_SCRIPT_URL = CHROME_URL_ROOT + "doc_frame_script.js";
const COMMON_FRAME_SCRIPT_URL = "chrome://devtools/content/shared/frame-script-utils.js";
const TAB_NAME = "animationinspector";
const ANIMATION_L10N =
new LocalizationHelper("devtools/locale/animationinspector.properties");
// Auto clean-up when a test ends
registerCleanupFunction(function* () {
@@ -25,16 +27,17 @@ registerCleanupFunction(function* () {
gBrowser.removeCurrentTab();
}
});
// Clean-up all prefs that might have been changed during a test run
// (safer here because if the test fails, then the pref is never reverted)
registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
+ Services.prefs.clearUserPref("devtools.dump.emit");
});
// WebAnimations API is not enabled by default in all release channels yet, see
// Bug 1264101.
function enableWebAnimationsAPI() {
return new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [
["dom.animations-api.core.enabled", true]
@@ -86,17 +89,17 @@ function* reloadTab(inspector) {
and animations of its subtree are properly displayed.
*/
var selectNodeAndWaitForAnimations = Task.async(
function* (data, inspector, reason = "test") {
yield selectNode(data, inspector, reason);
// We want to make sure the rest of the test waits for the animations to
// be properly displayed (wait for all target DOM nodes to be previewed).
- let {AnimationsPanel} = inspector.sidebar.getWindowForTab(TAB_NAME);
+ let {AnimationsPanel} = inspector.sidebar.tabbar.getWindowForTab(TAB_NAME);
yield waitForAllAnimationTargets(AnimationsPanel);
}
);
/**
* Check if there are the expected number of animations being displayed in the
* panel right now.
* @param {AnimationsPanel} panel
@@ -114,17 +117,17 @@ function assertAnimationsDisplayed(panel
* Takes an Inspector panel that was just created, and waits
* for a "inspector-updated" event as well as the animation inspector
* sidebar to be ready. Returns a promise once these are completed.
*
* @param {InspectorPanel} inspector
* @return {Promise}
*/
var waitForAnimationInspectorReady = Task.async(function* (inspector) {
- let win = inspector.sidebar.getWindowForTab(TAB_NAME);
+ let win = inspector.sidebar.tabbar.getWindowForTab(TAB_NAME);
let updated = inspector.once("inspector-updated");
// In e10s, if we wait for underlying toolbox actors to
// load (by setting DevToolsUtils.testing to true), we miss the
// "animationinspector-ready" event on the sidebar, so check to see if the
// iframe is already loaded.
let tabReady = win.document.readyState === "complete" ?
promise.resolve() :
@@ -139,17 +142,17 @@ var waitForAnimationInspectorReady = Tas
* @return a promise that resolves when the inspector is ready.
*/
var openAnimationInspector = Task.async(function* () {
let {inspector, toolbox} = yield openInspectorSidebarTab(TAB_NAME);
info("Waiting for the inspector and sidebar to be ready");
yield waitForAnimationInspectorReady(inspector);
- let win = inspector.sidebar.getWindowForTab(TAB_NAME);
+ let win = inspector.sidebar.tabbar.getWindowForTab(TAB_NAME);
let {AnimationsController, AnimationsPanel} = win;
info("Waiting for the animation controller and panel to be ready");
if (AnimationsPanel.initialized) {
yield AnimationsPanel.initialized;
} else {
yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
}
--- a/devtools/client/inspector/components/box-model.js
+++ b/devtools/client/inspector/components/box-model.js
@@ -417,17 +417,17 @@ BoxModelView.prototype = {
},
/**
* Is the BoxModelView visible in the sidebar.
* @return {Boolean}
*/
isViewVisible: function () {
return this.inspector &&
- this.inspector.sidebar.getCurrentTabID() == "computedview";
+ this.inspector.sidebar.tabbar.getCurrentTabId() == "computedview";
},
/**
* Is the BoxModelView visible in the sidebar and is the current node valid to
* be displayed in the view.
* @return {Boolean}
*/
isViewVisibleAndNodeValid: function () {
--- a/devtools/client/inspector/components/inspector-tab-panel.js
+++ b/devtools/client/inspector/components/inspector-tab-panel.js
@@ -15,18 +15,16 @@ const { div } = DOM;
* Helper panel component that is using an existing DOM node
* as the content. It's used by Sidebar as well as SplitBox
* components.
*/
var InspectorTabPanel = createClass({
displayName: "InspectorTabPanel",
propTypes: {
- // ID of the node that should be rendered as the content.
- id: PropTypes.string.isRequired,
// Optional prefix for panel IDs.
idPrefix: PropTypes.string,
// Optional mount callback
onMount: PropTypes.func,
},
getDefaultProps: function () {
return {
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -1417,17 +1417,17 @@ function ComputedViewTool(inspector, win
this.onSelected();
}
ComputedViewTool.prototype = {
isSidebarActive: function () {
if (!this.computedView) {
return false;
}
- return this.inspector.sidebar.getCurrentTabID() == "computedview";
+ return this.inspector.sidebar.tabbar.getCurrentTabId() == "computedview";
},
onSelected: function (event) {
// Ignore the event if the view has been destroyed, or if it's inactive.
// But only if the current selection isn't null. If it's been set to null,
// let the update go through as this is needed to empty the view on
// navigation.
if (!this.computedView) {
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -43,17 +43,17 @@ FontInspector.prototype = {
this.update();
},
/**
* Is the fontinspector visible in the sidebar?
*/
isActive: function () {
return this.inspector.sidebar &&
- this.inspector.sidebar.getCurrentTabID() == "fontinspector";
+ this.inspector.sidebar.tabbar.getCurrentTabId() == "fontinspector";
},
/**
* Remove listeners.
*/
destroy: function () {
this.chromeDoc = null;
this.inspector.sidebar.off("fontinspector-selected", this.onNewNode);
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -533,74 +533,74 @@ Inspector.prototype = {
Services.prefs.setIntPref("devtools.toolsidebar-height.inspector", state.height);
},
/**
* Build the sidebar.
*/
setupSidebar: function () {
let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
- this.sidebar = new ToolSidebar(tabbox, this, "inspector", {
+ this.sidebar = new ToolSidebar("sidebar-panel-", tabbox, this, "inspector", {
showAllTabsMenu: true
});
let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
if (!Services.prefs.getBoolPref("devtools.fontinspector.enabled") &&
defaultTab == "fontinspector") {
defaultTab = "ruleview";
}
// Append all side panels
- this.sidebar.addExistingTab(
+ this.sidebar.tabbar.addExistingTab(
"ruleview",
INSPECTOR_L10N.getStr("inspector.sidebar.ruleViewTitle"),
defaultTab == "ruleview");
- this.sidebar.addExistingTab(
+ this.sidebar.tabbar.addExistingTab(
"computedview",
INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"),
defaultTab == "computedview");
this._setDefaultSidebar = (event, toolId) => {
Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
};
this.sidebar.on("select", this._setDefaultSidebar);
this.ruleview = new RuleViewTool(this, this.panelWin);
this.computedview = new ComputedViewTool(this, this.panelWin);
if (Services.prefs.getBoolPref("devtools.layoutview.enabled")) {
- this.sidebar.addExistingTab(
+ this.sidebar.tabbar.addExistingTab(
"layoutview",
INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle"),
defaultTab == "layoutview"
);
this.layoutview = new LayoutViewTool(this, this.panelWin);
}
if (this.target.form.animationsActor) {
- this.sidebar.addFrameTab(
+ this.sidebar.tabbar.addFrameTab(
"animationinspector",
INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle"),
"chrome://devtools/content/animationinspector/animation-inspector.xhtml",
defaultTab == "animationinspector");
}
if (Services.prefs.getBoolPref("devtools.fontinspector.enabled") &&
this.canGetUsedFontFaces) {
- this.sidebar.addExistingTab(
+ this.sidebar.tabbar.addExistingTab(
"fontinspector",
INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle"),
defaultTab == "fontinspector");
this.fontInspector = new FontInspector(this, this.panelWin);
- this.sidebar.toggleTab(true, "fontinspector");
+ this.sidebar.tabbar.toggleTab(true, "fontinspector");
}
// Setup the splitter before the sidebar is displayed so,
// we don't miss any events.
this.setupSplitter();
this.sidebar.show(defaultTab);
},
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -1,13 +1,14 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
+/* globals gDevTools */
"use strict";
const promise = require("promise");
const Services = require("Services");
const {Task} = require("devtools/shared/task");
const {Tools} = require("devtools/client/definitions");
const {l10n} = require("devtools/shared/inspector/css-logic");
@@ -1525,17 +1526,17 @@ function RuleViewTool(inspector, window)
this.onSelected();
}
RuleViewTool.prototype = {
isSidebarActive: function () {
if (!this.view) {
return false;
}
- return this.inspector.sidebar.getCurrentTabID() == "ruleview";
+ return this.inspector.sidebar.tabbar.getCurrentTabId() == "ruleview";
},
onSelected: function (event) {
// Ignore the event if the view has been destroyed, or if it's inactive.
// But only if the current selection isn't null. If it's been set to null,
// let the update go through as this is needed to empty the view on
// navigation.
if (!this.view) {
--- a/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
+++ b/devtools/client/inspector/shared/test/browser_styleinspector_context-menu-copy-color_01.js
@@ -18,17 +18,17 @@ add_task(function* () {
let {inspector} = yield openInspector();
yield testView("ruleview", inspector);
yield testView("computedview", inspector);
});
function* testView(viewId, inspector) {
info("Testing " + viewId);
- yield inspector.sidebar.select(viewId);
+ yield inspector.sidebar.tabbar.select(viewId);
let view = inspector[viewId].view || inspector[viewId].computedView;
yield selectNode("div", inspector);
testIsColorValueNode(view);
testIsColorPopupOnAllNodes(view);
yield clearCurrentNodeSelection(inspector);
}
--- a/devtools/client/inspector/shared/test/head.js
+++ b/devtools/client/inspector/shared/test/head.js
@@ -37,17 +37,17 @@ registerCleanupFunction(() => {
*
* Most of these functions are async too and return promises.
*
* All tests should follow the following pattern:
*
* add_task(function*() {
* yield addTab(TEST_URI);
* let {toolbox, inspector} = yield openInspector();
- * inspector.sidebar.select(viewId);
+ * inspector.sidebar.tabbar.select(viewId);
* let view = inspector[viewId].view;
* yield selectNode("#test", inspector);
* yield someAsyncTestFunction(view);
* });
*
* add_task is the way to define the testcase in the test file. It accepts
* a single generator-function argument.
* The generator function should yield any async call.
--- a/devtools/client/inspector/test/browser_inspector_addSidebarTab.js
+++ b/devtools/client/inspector/test/browser_inspector_addSidebarTab.js
@@ -30,33 +30,33 @@ add_task(function* () {
)
);
}
}));
// Append custom panel (tab) into the Inspector panel and
// make sure it's selected by default (the last arg = true).
inspector.addSidebarTab("myPanel", "My Panel", tabPanel, true);
- is(inspector.sidebar.getCurrentTabID(), "myPanel",
+ is(inspector.sidebar.tabbar.getCurrentTabId(), "myPanel",
"My Panel is selected by default");
// Define another custom side-panel.
tabPanel = React.createFactory(React.createClass({
displayName: "myTabPanel2",
render: function () {
return (
div({className: "my-tab-panel2"},
"Another Content"
)
);
}
}));
// Append second panel, but don't select it by default.
inspector.addSidebarTab("myPanel", "My Panel", tabPanel, false);
- is(inspector.sidebar.getCurrentTabID(), "myPanel",
+ is(inspector.sidebar.tabbar.getCurrentTabId(), "myPanel",
"My Panel is selected by default");
// Check the the panel content is properly rendered.
let tabPanelNode = inspector.panelDoc.querySelector(".my-tab-panel");
is(tabPanelNode.textContent, CONTENT_TEXT,
"Side panel content has been rendered.");
});
--- a/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js
+++ b/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js
@@ -18,17 +18,17 @@ const TEST_URL = "data:text/html;charset
" </div>" +
"</body>";
add_task(function* () {
info("Creating the test tab and opening the rule-view");
let {toolbox, inspector, testActor} = yield openInspectorForURL(TEST_URL);
info("Selecting the ruleview sidebar");
- inspector.sidebar.select("ruleview");
+ inspector.sidebar.tabbar.select("ruleview");
let view = inspector.ruleview.view;
info("Selecting the test node");
yield selectNode("#div-1", inspector);
yield togglePseudoClass(inspector);
yield assertPseudoAddedToNode(inspector, testActor, view);
--- a/devtools/client/inspector/test/browser_inspector_sidebarstate.js
+++ b/devtools/client/inspector/test/browser_inspector_sidebarstate.js
@@ -5,34 +5,34 @@
const TEST_URI = "data:text/html;charset=UTF-8," +
"<h1>browser_inspector_sidebarstate.js</h1>";
add_task(function* () {
let { inspector, toolbox } = yield openInspectorForURL(TEST_URI);
info("Selecting ruleview.");
- inspector.sidebar.select("ruleview");
+ inspector.sidebar.tabbar.select("ruleview");
- is(inspector.sidebar.getCurrentTabID(), "ruleview",
+ is(inspector.sidebar.tabbar.getCurrentTabId(), "ruleview",
"Rule View is selected by default");
info("Selecting computed view.");
- inspector.sidebar.select("computedview");
+ inspector.sidebar.tabbar.select("computedview");
// Finish initialization of the computed panel before
// destroying the toolbox.
yield waitForTick();
info("Closing inspector.");
yield toolbox.destroy();
info("Re-opening inspector.");
inspector = (yield openInspector()).inspector;
- if (!inspector.sidebar.getCurrentTabID()) {
+ if (!inspector.sidebar.tabbar.getCurrentTabId()) {
info("Default sidebar still to be selected, adding select listener.");
yield inspector.sidebar.once("select");
}
- is(inspector.sidebar.getCurrentTabID(), "computedview",
+ is(inspector.sidebar.tabbar.getCurrentTabId(), "computedview",
"Computed view is selected by default.");
});
--- a/devtools/client/inspector/test/shared-head.js
+++ b/devtools/client/inspector/test/shared-head.js
@@ -44,17 +44,17 @@ var openInspector = Task.async(function*
* The ID of the sidebar tab to be opened
* @return a promise that resolves when the inspector is ready and the tab is
* visible and ready
*/
var openInspectorSidebarTab = Task.async(function* (id) {
let {toolbox, inspector, testActor} = yield openInspector();
info("Selecting the " + id + " sidebar");
- inspector.sidebar.select(id);
+ inspector.sidebar.tabbar.select(id);
return {
toolbox,
inspector,
testActor
};
});
@@ -101,29 +101,29 @@ function openComputedView() {
/**
* Select the rule view sidebar tab on an already opened inspector panel.
*
* @param {InspectorPanel} inspector
* The opened inspector panel
* @return {CssRuleView} the rule view
*/
function selectRuleView(inspector) {
- inspector.sidebar.select("ruleview");
+ inspector.sidebar.tabbar.select("ruleview");
return inspector.ruleview.view;
}
/**
* Select the computed view sidebar tab on an already opened inspector panel.
*
* @param {InspectorPanel} inspector
* The opened inspector panel
* @return {CssComputedView} the computed view
*/
function selectComputedView(inspector) {
- inspector.sidebar.select("computedview");
+ inspector.sidebar.tabbar.select("computedview");
return inspector.computedview.computedView;
}
/**
* Get the NodeFront for a node that matches a given css selector, via the
* protocol.
* @param {String|NodeFront} selector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently
--- a/devtools/client/inspector/toolsidebar.js
+++ b/devtools/client/inspector/toolsidebar.js
@@ -17,45 +17,42 @@ var { Task } = require("devtools/shared/
* This new component is part of devtools.html aimed at
* removing XUL and use HTML for entire DevTools UI.
* There are currently two implementation of the side bar since
* the `sidebar.js` module (mentioned above) is still used by
* other panels.
* As soon as all panels are using this HTML based
* implementation it can be removed.
*/
-function ToolSidebar(tabbox, panel, uid, options = {}) {
+function ToolSidebar(idPrefix, tabbox, panel, uid, options = {}) {
EventEmitter.decorate(this);
+ this._idPrefix = idPrefix;
this._tabbox = tabbox;
this._uid = uid;
this._panelDoc = this._tabbox.ownerDocument;
this._toolPanel = panel;
this._options = options;
if (!options.disableTelemetry) {
this._telemetry = new Telemetry();
}
- this._tabs = [];
-
if (this._options.hideTabstripe) {
this._tabbox.setAttribute("hidetabs", "true");
}
this.render();
- this._toolPanel.emit("sidebar-created", this);
+ this._toolPanel.emit(this._idPrefix + "-created", this);
}
exports.ToolSidebar = ToolSidebar;
ToolSidebar.prototype = {
- TABPANEL_ID_PREFIX: "sidebar-panel-",
-
// React
get React() {
return this._toolPanel.React;
},
get ReactDOM() {
return this._toolPanel.ReactDOM;
@@ -69,157 +66,80 @@ ToolSidebar.prototype = {
return this._toolPanel.InspectorTabPanel;
},
// Rendering
render: function () {
let Tabbar = this.React.createFactory(this.browserRequire(
"devtools/client/shared/components/tabs/tabbar"));
+ let PanelTab = this.browserRequire(
+ "devtools/client/shared/components/tabs/paneltab");
- let sidebar = Tabbar({
- toolbox: this._toolPanel._toolbox,
+ let tabbar = Tabbar({
+ panel: this._toolPanel,
+ panelDoc: this._panelDoc,
showAllTabsMenu: true,
+ idPrefix: this._idPrefix,
onSelect: this.handleSelectionChange.bind(this),
+ onPanelReady: this.handlePanelReady.bind(this),
+ onNewTabRegistered: this.handleNewTabRegistered.bind(this),
+ onToolReady: this.handleToolReady.bind(this),
+ onTabUnregistered: this.handleTabUnregistered.bind(this),
+
+ InspectorTabPanel: this.InspectorTabPanel.bind(this),
+ addTab: PanelTab.addTab,
+ addExistingTab: PanelTab.addExistingTab,
+ addFrameTab: PanelTab.addFrameTab,
+ onPanelMounted: PanelTab.onPanelMounted,
+ getTabPanel: PanelTab.getTabPanel,
});
- this._tabbar = this.ReactDOM.render(sidebar, this._tabbox);
+ this.tabbar = this.ReactDOM.render(tabbar, this._tabbox);
+
+ return this.tabbar;
},
/**
* Register a side-panel tab.
*
* @param {string} tab uniq id
* @param {string} title tab title
* @param {React.Component} panel component. See `InspectorPanelTab` as an example.
* @param {boolean} selected true if the panel should be selected
*/
addTab: function (id, title, panel, selected) {
- this._tabbar.addTab(id, title, selected, panel);
+ this.tabbar.addTab(id, title, selected, panel);
this.emit("new-tab-registered", id);
},
/**
- * Helper API for adding side-panels that use existing DOM nodes
- * (defined within inspector.xhtml) as the content.
- *
- * @param {string} tab uniq id
- * @param {string} title tab title
- * @param {boolean} selected true if the panel should be selected
- */
- addExistingTab: function (id, title, selected) {
- let panel = this.InspectorTabPanel({
- id: id,
- idPrefix: this.TABPANEL_ID_PREFIX,
- key: id,
- title: title,
- });
-
- this.addTab(id, title, panel, selected);
- },
-
- /**
- * Helper API for adding side-panels that use existing <iframe> nodes
- * (defined within inspector.xhtml) as the content.
- * The document must have a title, which will be used as the name of the tab.
- *
- * @param {string} tab uniq id
- * @param {string} title tab title
- * @param {string} url
- * @param {boolean} selected true if the panel should be selected
- */
- addFrameTab: function (id, title, url, selected) {
- let panel = this.InspectorTabPanel({
- id: id,
- idPrefix: this.TABPANEL_ID_PREFIX,
- key: id,
- title: title,
- url: url,
- onMount: this.onSidePanelMounted.bind(this),
- });
-
- this.addTab(id, title, panel, selected);
- },
-
- onSidePanelMounted: function (content, props) {
- let iframe = content.querySelector("iframe");
- if (!iframe || iframe.getAttribute("src")) {
- return;
- }
-
- let onIFrameLoaded = (event) => {
- iframe.removeEventListener("load", onIFrameLoaded, true);
-
- let doc = event.target;
- let win = doc.defaultView;
- if ("setPanel" in win) {
- win.setPanel(this._toolPanel, iframe);
- }
- this.emit(props.id + "-ready");
- };
-
- iframe.addEventListener("load", onIFrameLoaded, true);
- iframe.setAttribute("src", props.url);
- },
-
- /**
- * 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* (tabId, tabPanelId) {
- this._tabbar.removeTab(tabId);
-
- let win = this.getWindowForTab(tabId);
- if (win && ("destroy" in win)) {
- yield win.destroy();
- }
-
- this.emit("tab-unregistered", tabId);
- }),
-
- /**
* Show or hide a specific tab.
* @param {Boolean} isVisible True to show the tab/tabpanel, False to hide it.
* @param {String} id The ID of the tab to be hidden.
*/
toggleTab: function (isVisible, id) {
- this._tabbar.toggleTab(id, isVisible);
+ this.tabbar.toggleTab(id, isVisible);
},
/**
* Select a specific tab.
*/
select: function (id) {
- this._tabbar.select(id);
+ this.tabbar.select(id);
},
/**
* Return the id of the selected tab.
*/
getCurrentTabID: function () {
return this._currentTool;
},
/**
- * Returns the requested tab panel based on the id.
- * @param {String} id
- * @return {DOMNode}
- */
- 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._panelDoc.querySelector("#" +
- this.TABPANEL_ID_PREFIX + id + ", #" + id);
- },
-
- /**
* Event handler.
*/
handleSelectionChange: function (id) {
if (this._destroyed) {
return;
}
let previousTool = this._currentTool;
@@ -235,61 +155,68 @@ ToolSidebar.prototype = {
if (this._telemetry) {
this._telemetry.toolOpened(this._currentTool);
}
this.emit(this._currentTool + "-selected");
this.emit("select", this._currentTool);
},
+ handlePanelReady: function (id) {
+ if (this._destroyed) {
+ return;
+ }
+
+ this.emit(id + "-ready");
+ },
+
+ handleTabUnregistered: function (tabId) {
+ this.emit("tab-unregistered", tabId);
+ },
+
+ handleNewTabRegistered: function (tabId) {
+ this.emit("new-tab-registered", tabId);
+ },
+
+ handleToolReady: function (toolId) {
+ this.emit(toolId + "-ready");
+ },
+
/**
- * Show the sidebar.
+ * Show the tabbar.
*
* @param {String} id
- * The sidebar tab id to select.
+ * The tab id to select.
*/
show: function (id) {
this._tabbox.removeAttribute("hidden");
- // If an id is given, select the corresponding sidebar tab and record the
- // tool opened.
+ // If an id is given, select the corresponding tab and record the tool
+ // opened.
if (id) {
this._currentTool = id;
if (this._telemetry) {
this._telemetry.toolOpened(this._currentTool);
}
}
this.emit("show");
},
/**
- * Show the sidebar.
+ * Hide the tabbar.
*/
hide: function () {
this._tabbox.setAttribute("hidden", "true");
this.emit("hide");
},
/**
- * Return the window containing the tab content.
- */
- getWindowForTab: function (id) {
- // Get the tabpanel and make sure it contains an iframe
- let panel = this.getTabPanel(id);
- if (!panel || !panel.firstElementChild || !panel.firstElementChild.contentWindow) {
- return null;
- }
-
- return panel.firstElementChild.contentWindow;
- },
-
- /**
* Clean-up.
*/
destroy: Task.async(function* () {
if (this._destroyed) {
return;
}
this._destroyed = true;
@@ -310,16 +237,16 @@ ToolSidebar.prototype = {
}
panel.remove();
}
if (this._currentTool && this._telemetry) {
this._telemetry.toolClosed(this._currentTool);
}
- this._toolPanel.emit("sidebar-destroyed", this);
+ this._toolPanel.emit(this._idPrefix + "-destroyed", this);
- this._tabs = null;
+ this._idPrefix = null;
this._tabbox = null;
this._panelDoc = null;
this._toolPanel = null;
})
};
--- a/devtools/client/responsivedesign/test/head.js
+++ b/devtools/client/responsivedesign/test/head.js
@@ -140,34 +140,34 @@ function waitForToolboxFrameFocus(toolbo
* corresponds to the given id selected
* @return a promise that resolves when the inspector is ready and the sidebar
* view is visible and ready
*/
var openInspectorSideBar = Task.async(function* (id) {
let {toolbox, inspector} = yield openInspector();
info("Selecting the " + id + " sidebar");
- inspector.sidebar.select(id);
+ inspector.sidebar.tabbar.select(id);
return {
toolbox: toolbox,
inspector: inspector,
view: inspector[id].view || inspector[id].computedView
};
});
/**
* Checks whether the inspector's sidebar corresponding to the given id already
* exists
* @param {InspectorPanel}
* @param {String}
* @return {Boolean}
*/
function hasSideBarTab(inspector, id) {
- return !!inspector.sidebar.getWindowForTab(id);
+ return !!inspector.sidebar.tabbar.getWindowForTab(id);
}
/**
* Open the toolbox, with the inspector tool visible, and the computed-view
* sidebar tab selected.
* @return a promise that resolves when the inspector is ready and the computed
* view is visible and ready
*/
--- a/devtools/client/shared/components/tabs/moz.build
+++ b/devtools/client/shared/components/tabs/moz.build
@@ -1,12 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DevToolsModules(
+ 'paneltab.js',
'tabbar.css',
'tabbar.js',
'tabs.css',
'tabs.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/tabs/paneltab.js
@@ -0,0 +1,96 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+module.exports = {
+ addTab(id, title, selected = false, panel, url) {
+ let tabs = this.state.tabs.slice();
+ tabs.push({id, title, panel, url});
+
+ let newState = Object.assign({}, this.state, {
+ tabs: tabs,
+ });
+
+ if (selected) {
+ newState.activeTab = tabs.length - 1;
+ }
+
+ this.setState(newState);
+ },
+
+ addExistingTab(id, title, selected) {
+ this.addTab(id, title, selected, this.InspectorTabPanel);
+
+ if (this.props.onNewTabRegistered) {
+ this.props.onNewTabRegistered(id);
+ }
+ },
+
+ /**
+ * Register a tab. A tab is a document.
+ * The document must have a title, which will be used as the name of the tab.
+ *
+ * @param {string} tab uniq id
+ * @param {string} url
+ */
+ addFrameTab(id, title, url, selected) {
+ let panel = this.InspectorTabPanel({
+ idPrefix: this.props.idPrefix,
+ id: id,
+ key: id,
+ title: title,
+ url: url,
+ onMount: this.onPanelMounted.bind(this),
+ });
+
+ this.addTab(id, title, selected, panel);
+
+ if (this.props.onNewTabRegistered) {
+ this.props.onNewTabRegistered(id);
+ }
+ },
+
+ onPanelMounted(content, props) {
+ let iframe = content.querySelector("iframe");
+ if (!iframe || iframe.getAttribute("src")) {
+ return;
+ }
+
+ let onIFrameLoaded = event => {
+ iframe.removeEventListener("load", onIFrameLoaded, true);
+
+ let doc = event.target;
+ let win = doc.defaultView;
+ if ("setPanel" in win) {
+ win.setPanel(this.props.panel, iframe);
+ }
+
+ if (this.props.onToolReady) {
+ this.props.onToolReady(props.id);
+ }
+
+ if (this.props.onPanelReady) {
+ this.props.onPanelReady(props.id);
+ }
+ };
+
+ iframe.addEventListener("load", onIFrameLoaded, true);
+ iframe.setAttribute("src", props.url);
+ },
+
+ /**
+ * Returns the requested tab panel based on the id.
+ * @param {String} id
+ * @return {DOMNode}
+ */
+ getTabPanel(id) {
+ // Search with and without the ID prefix as there might have been existing
+ // tabpanels by the time the tabbar got created
+ return this.props.panelDoc.querySelector("#" +
+ this.props.idPrefix + id + ", #" + id);
+ }
+};
--- a/devtools/client/shared/components/tabs/tabbar.js
+++ b/devtools/client/shared/components/tabs/tabbar.js
@@ -1,151 +1,190 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { DOM, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
+const { Component, DOM, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const Tabs = createFactory(require("devtools/client/shared/components/tabs/tabs").Tabs);
-
const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item");
// Shortcuts
const { div } = DOM;
/**
* Renders Tabbar component.
*/
-let Tabbar = createClass({
- displayName: "Tabbar",
-
- propTypes: {
- onSelect: PropTypes.func,
- showAllTabsMenu: PropTypes.bool,
- toolbox: PropTypes.object,
- },
+class Tabbar extends Component { //eslint-disable-line
+ constructor(props) {
+ super(props);
- getDefaultProps: function () {
- return {
- showAllTabsMenu: false,
- };
- },
-
- getInitialState: function () {
- return {
+ this.state = {
tabs: [],
activeTab: 0
};
- },
+
+ this.onTabChanged = this.onTabChanged.bind(this);
+
+ if (typeof this.props.InspectorTabPanel !== "undefined") {
+ this.InspectorTabPanel = this.props.InspectorTabPanel.bind(this);
+ }
+
+ if (typeof this.props.addTab !== "undefined") {
+ this.addTab = this.props.addTab.bind(this);
+ }
+
+ if (typeof this.props.addExistingTab !== "undefined") {
+ this.addExistingTab = this.props.addExistingTab.bind(this);
+ }
+
+ if (typeof this.props.addFrameTab !== "undefined") {
+ this.addFrameTab = this.props.addFrameTab.bind(this);
+ }
+
+ if (typeof this.props.onPanelMounted !== "undefined") {
+ this.onPanelMounted = this.props.onPanelMounted.bind(this);
+ }
+
+ if (typeof this.props.InspectorTabPanel !== "undefined") {
+ this.getTabPanel = this.props.getTabPanel.bind(this);
+ }
+ }
// Public API
- addTab: function (id, title, selected = false, panel, url) {
- let tabs = this.state.tabs.slice();
- tabs.push({id, title, panel, url});
+ get browserRequire() {
+ return this.props.panel.browserRequire;
+ }
- let newState = Object.assign({}, this.state, {
- tabs: tabs,
- });
+ /**
+ * 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 tabbar
+ * 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(tabId, tabPanelId) {
+ let index = this.getTabIndex(tabId);
- if (selected) {
- newState.activeTab = tabs.length - 1;
+ if (index < 0) {
+ return;
}
- this.setState(newState, () => {
- if (this.props.onSelect && selected) {
- this.props.onSelect(id);
- }
- });
- },
+ let tabs = this.state.tabs.slice();
+ tabs.splice(index, 1);
+
+ this.setState(Object.assign({}, this.state, {
+ tabs: tabs,
+ }));
- toggleTab: function (tabId, isVisible) {
+ let win = this.getWindowForTab(tabId);
+ if (win && ("destroy" in win)) {
+ yield win.destroy();
+ }
+
+ if (this.props.onTabUnregistered) {
+ this.props.onTabUnregistered(tabId);
+ }
+ }
+
+ toggleTab(tabId, isVisible) {
let index = this.getTabIndex(tabId);
if (index < 0) {
return;
}
let tabs = this.state.tabs.slice();
tabs[index] = Object.assign({}, tabs[index], {
isVisible: isVisible
});
this.setState(Object.assign({}, this.state, {
tabs: tabs,
}));
- },
-
- removeTab: function (tabId) {
- let index = this.getTabIndex(tabId);
- if (index < 0) {
- return;
- }
+ }
- let tabs = this.state.tabs.slice();
- tabs.splice(index, 1);
-
- this.setState(Object.assign({}, this.state, {
- tabs: tabs,
- }));
- },
-
- select: function (tabId) {
+ select(tabId) {
let index = this.getTabIndex(tabId);
if (index < 0) {
return;
}
let newState = Object.assign({}, this.state, {
activeTab: index,
});
this.setState(newState, () => {
if (this.props.onSelect) {
this.props.onSelect(tabId);
}
});
- },
+ }
// Helpers
- getTabIndex: function (tabId) {
- let tabIndex = -1;
- this.state.tabs.forEach((tab, index) => {
+ getTab(tabId) {
+ for (let tab of this.state.tabs) {
if (tab.id == tabId) {
- tabIndex = index;
+ return tab;
}
- });
- return tabIndex;
- },
+ }
+
+ return null;
+ }
- getTabId: function (index) {
+ getTabIndex(tabId) {
+ for (let [index, tab] of this.state.tabs.entries()) {
+ if (tab.id === tabId) {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ getTabId(index) {
return this.state.tabs[index].id;
- },
+ }
- getCurrentTabId: function () {
+ getCurrentTabId() {
return this.state.tabs[this.state.activeTab].id;
- },
+ }
// Event Handlers
- onTabChanged: function (index) {
+ onTabChanged(index) {
this.setState({
activeTab: index
+ }, () => {
+ if (this.props.onSelect) {
+ this.props.onSelect(this.state.tabs[index].id);
+ }
});
+ }
- if (this.props.onSelect) {
- this.props.onSelect(this.state.tabs[index].id);
+ /**
+ * Return the window containing the tab content.
+ */
+ getWindowForTab(id) {
+ // Get the tabpanel and make sure it contains an iframe
+ let panel = this.getTabPanel(id);
+ if (!panel || !panel.firstElementChild || !panel.firstElementChild.contentWindow) {
+ return null;
}
- },
- onAllTabsMenuClick: function (event) {
+ return panel.firstElementChild.contentWindow;
+ }
+
+ onAllTabsMenuClick(event) {
let menu = new Menu();
let target = event.target;
// Generate list of menu items from the list of tabs.
this.state.tabs.forEach(tab => {
menu.append(new MenuItem({
label: tab.title,
type: "checkbox",
@@ -160,45 +199,68 @@ let Tabbar = createClass({
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/openPopup
// https://bugzilla.mozilla.org/show_bug.cgi?id=1274551
let rect = target.getBoundingClientRect();
let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
menu.popup(rect.left + screenX, rect.bottom + screenY, this.props.toolbox);
return menu;
- },
+ }
// Rendering
- renderTab: function (tab) {
+ renderTab(tab) {
if (typeof tab.panel === "function") {
return tab.panel({
+ idPrefix: this.props.idPrefix,
key: tab.id,
title: tab.title,
id: tab.id,
url: tab.url,
});
}
return tab.panel;
- },
+ }
- render: function () {
+ render() {
let tabs = this.state.tabs.map(tab => {
return this.renderTab(tab);
});
return (
div({className: "devtools-sidebar-tabs"},
Tabs({
onAllTabsMenuClick: this.onAllTabsMenuClick,
showAllTabsMenu: this.props.showAllTabsMenu,
tabActive: this.state.activeTab,
onAfterChange: this.onTabChanged},
tabs
)
)
);
- },
-});
+ }
+}
+
+Tabbar.displayName = "Tabbar";
+
+Tabbar.propTypes = {
+ idPrefix: PropTypes.string,
+ onSelect: PropTypes.func,
+ onTabUnregistered: PropTypes.func,
+ showAllTabsMenu: PropTypes.bool,
+ toolbox: PropTypes.object,
+
+ InspectorTabPanel: PropTypes.func,
+ addTab: PropTypes.func,
+ addExistingTab: PropTypes.func,
+ addFrameTab: PropTypes.func,
+ onPanelMounted: PropTypes.func,
+ getTabPanel: PropTypes.func,
+};
+
+Tabbar.defaultProps = {
+ idPrefix: "",
+ showAllTabsMenu: false,
+};
module.exports = Tabbar;
--- a/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
+++ b/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
@@ -15,17 +15,19 @@ Test tabs accessibility.
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
const React = browserRequire("devtools/client/shared/vendor/react");
const { Simulate } = React.addons.TestUtils;
const InspectorTabPanel = React.createFactory(browserRequire("devtools/client/inspector/components/inspector-tab-panel"));
const Tabbar = React.createFactory(browserRequire("devtools/client/shared/components/tabs/tabbar"));
- const tabbar = Tabbar();
+ const tabbar = Tabbar({
+ idPrefix: "sidebar",
+ });
const tabbarReact = ReactDOM.render(tabbar, window.document.body);
const tabbarEl = ReactDOM.findDOMNode(tabbarReact);
// Setup for InspectorTabPanel
const tabpanels = document.createElement("div");
tabpanels.id = "tabpanels";
document.body.appendChild(tabpanels);
--- a/devtools/client/shared/test/browser_telemetry_sidebar.js
+++ b/devtools/client/shared/test/browser_telemetry_sidebar.js
@@ -33,17 +33,17 @@ function* testSidebar(toolbox) {
// Concatenate the array with itself so that we can open each tool twice.
sidebarTools.push.apply(sidebarTools, sidebarTools);
return new Promise(resolve => {
// See TOOL_DELAY for why we need setTimeout here
setTimeout(function selectSidebarTab() {
let tool = sidebarTools.pop();
if (tool) {
- inspector.sidebar.select(tool);
+ inspector.sidebar.tabbar.select(tool);
setTimeout(function () {
setTimeout(selectSidebarTab, TOOL_DELAY);
}, TOOL_DELAY);
} else {
resolve();
}
}, TOOL_DELAY);
});