Bug 1463555 - Queue adding the tabs to the inspector sidebar. r=Honza
MozReview-Commit-ID: IoKLFVH03AZ
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -724,17 +724,17 @@ Inspector.prototype = {
this.sidebarSplitBox.setState({
endPanelControl: false,
splitterSize: 0,
});
this.ruleViewSideBar.hide();
await this.ruleViewSideBar.removeTab("ruleview");
- this.sidebar.addExistingTab(
+ this.sidebar.queueExistingTab(
"ruleview",
INSPECTOR_L10N.getStr("inspector.sidebar.ruleViewTitle"),
defaultTab == "ruleview",
0);
}
this.emit("ruleview-added");
},
@@ -804,17 +804,17 @@ Inspector.prototype = {
// Append all side panels
await this.addRuleView(defaultTab);
// Inject a lazy loaded react tab by exposing a fake React object
// with a lazy defined Tab thanks to `panel` being a function
let layoutId = "layoutview";
let layoutTitle = INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2");
- this.sidebar.addTab(
+ this.sidebar.queueTab(
layoutId,
layoutTitle,
{
props: {
id: layoutId,
title: layoutTitle
},
panel: () => {
@@ -824,56 +824,56 @@ Inspector.prototype = {
this.layoutview = new LayoutView(this, this.panelWin);
}
return this.layoutview.provider;
}
},
defaultTab == layoutId);
- this.sidebar.addExistingTab(
+ this.sidebar.queueExistingTab(
"computedview",
INSPECTOR_L10N.getStr("inspector.sidebar.computedViewTitle"),
defaultTab == "computedview");
const animationTitle =
INSPECTOR_L10N.getStr("inspector.sidebar.animationInspectorTitle");
if (Services.prefs.getBoolPref("devtools.new-animationinspector.enabled")) {
const animationId = "newanimationinspector";
- this.sidebar.addTab(
+ this.sidebar.queueTab(
animationId,
animationTitle,
{
props: {
id: animationId,
title: animationTitle
},
panel: () => {
const AnimationInspector =
this.browserRequire("devtools/client/inspector/animation/animation");
this.animationinspector = new AnimationInspector(this, this.panelWin);
return this.animationinspector.provider;
}
},
defaultTab == animationId);
} else {
- this.sidebar.addFrameTab(
+ this.sidebar.queueFrameTab(
"animationinspector",
animationTitle,
"chrome://devtools/content/inspector/animation-old/animation-inspector.xhtml",
defaultTab == "animationinspector");
}
// Inject a lazy loaded react tab by exposing a fake React object
// with a lazy defined Tab thanks to `panel` being a function
let fontId = "fontinspector";
let fontTitle = INSPECTOR_L10N.getStr("inspector.sidebar.fontInspectorTitle");
- this.sidebar.addTab(
+ this.sidebar.queueTab(
fontId,
fontTitle,
{
props: {
id: fontId,
title: fontTitle
},
panel: () => {
@@ -883,16 +883,18 @@ Inspector.prototype = {
this.fontinspector = new FontInspector(this, this.panelWin);
}
return this.fontinspector.provider;
}
},
defaultTab == fontId);
+ this.sidebar.addAllQueuedTabs();
+
// Persist splitter state in preferences.
this.sidebar.on("show", this.onSidebarShown);
this.sidebar.on("hide", this.onSidebarHidden);
this.sidebar.on("destroy", this.onSidebarHidden);
this.sidebar.show(defaultTab);
},
--- a/devtools/client/inspector/toolsidebar.js
+++ b/devtools/client/inspector/toolsidebar.js
@@ -80,16 +80,23 @@ ToolSidebar.prototype = {
sidebarToggleButton: this._options.sidebarToggleButton,
onSelect: this.handleSelectionChange.bind(this),
});
this._tabbar = this.ReactDOM.render(sidebar, this._tabbox);
},
/**
+ * Adds all the queued tabs.
+ */
+ addAllQueuedTabs: function() {
+ this._tabbar.addAllQueuedTabs();
+ },
+
+ /**
* 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
* @param {Number} index the position where the tab should be inserted
*/
@@ -138,16 +145,75 @@ ToolSidebar.prototype = {
url: url,
onMount: this.onSidePanelMounted.bind(this),
onUnmount: this.onSidePanelUnmounted.bind(this),
});
this.addTab(id, title, panel, selected, index);
},
+ /**
+ * Queues a side-panel tab to be added..
+ *
+ * @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
+ * @param {Number} index the position where the tab should be inserted
+ */
+ queueTab: function(id, title, panel, selected, index) {
+ this._tabbar.queueTab(id, title, selected, panel, null, index);
+ this.emit("new-tab-registered", id);
+ },
+
+ /**
+ * Helper API for queuing 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
+ * @param {Number} index the position where the tab should be inserted
+ */
+ queueExistingTab: function(id, title, selected, index) {
+ let panel = this.InspectorTabPanel({
+ id: id,
+ idPrefix: this.TABPANEL_ID_PREFIX,
+ key: id,
+ title: title,
+ });
+
+ this.queueTab(id, title, panel, selected, index);
+ },
+
+ /**
+ * Helper API for queuing 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
+ * @param {Number} index the position where the tab should be inserted
+ */
+ queueFrameTab: function(id, title, url, selected, index) {
+ let panel = this.InspectorTabPanel({
+ id: id,
+ idPrefix: this.TABPANEL_ID_PREFIX,
+ key: id,
+ title: title,
+ url: url,
+ onMount: this.onSidePanelMounted.bind(this),
+ onUnmount: this.onSidePanelUnmounted.bind(this),
+ });
+
+ this.queueTab(id, title, panel, selected, index);
+ },
+
onSidePanelMounted: function(content, props) {
let iframe = content.querySelector("iframe");
if (!iframe || iframe.getAttribute("src")) {
return;
}
let onIFrameLoaded = (event) => {
iframe.removeEventListener("load", onIFrameLoaded, true);
--- a/devtools/client/shared/components/tabs/TabBar.js
+++ b/devtools/client/shared/components/tabs/TabBar.js
@@ -58,18 +58,23 @@ class Tabbar extends Component {
let tabs = this.createTabs(children);
let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
this.state = {
activeTab: activeTab === -1 ? 0 : activeTab,
tabs,
};
+ // Array of queued tabs to add to the Tabbar.
+ this.queuedTabs = [];
+
this.createTabs = this.createTabs.bind(this);
this.addTab = this.addTab.bind(this);
+ this.addAllQueuedTabs = this.addAllQueuedTabs.bind(this);
+ this.queueTab = this.queueTab.bind(this);
this.toggleTab = this.toggleTab.bind(this);
this.removeTab = this.removeTab.bind(this);
this.select = this.select.bind(this);
this.getTabIndex = this.getTabIndex.bind(this);
this.getTabId = this.getTabId.bind(this);
this.getCurrentTabId = this.getCurrentTabId.bind(this);
this.onTabChanged = this.onTabChanged.bind(this);
this.onAllTabsMenuClick = this.onAllTabsMenuClick.bind(this);
@@ -109,30 +114,83 @@ class Tabbar extends Component {
if (index >= 0) {
tabs.splice(index, 0, {id, title, panel, url});
} else {
tabs.push({id, title, panel, url});
}
let newState = Object.assign({}, this.state, {
- tabs: tabs,
+ tabs,
});
if (selected) {
newState.activeTab = index >= 0 ? index : tabs.length - 1;
}
this.setState(newState, () => {
if (this.props.onSelect && selected) {
this.props.onSelect(id);
}
});
}
+ addAllQueuedTabs() {
+ if (!this.queuedTabs.length) {
+ return;
+ }
+
+ let tabs = this.state.tabs.slice();
+ let activeId;
+ let activeTab;
+
+ for (let { id, index, panel, selected, title, url } of this.queuedTabs) {
+ if (index >= 0) {
+ tabs.splice(index, 0, {id, title, panel, url});
+ } else {
+ tabs.push({id, title, panel, url});
+ }
+
+ if (selected) {
+ activeId = id;
+ activeTab = index >= 0 ? index : tabs.length - 1;
+ }
+ }
+
+ let newState = Object.assign({}, this.state, {
+ activeTab,
+ tabs,
+ });
+
+ this.setState(newState, () => {
+ if (this.props.onSelect) {
+ this.props.onSelect(activeId);
+ }
+ });
+
+ this.queuedTabs = [];
+ }
+
+ /**
+ * Queues a tab to be added. This is more performant than calling addTab for every
+ * single tab to be added since we will limit the number of renders happening when
+ * a new state is set. Once all the tabs to be added have been queued, call
+ * addAllQueuedTabs() to populate the TabBar with all the queued tabs.
+ */
+ queueTab(id, title, selected = false, panel, url, index = -1) {
+ this.queuedTabs.push({
+ id,
+ index,
+ panel,
+ selected,
+ title,
+ url,
+ });
+ }
+
toggleTab(tabId, isVisible) {
let index = this.getTabIndex(tabId);
if (index < 0) {
return;
}
let tabs = this.state.tabs.slice();
tabs[index] = Object.assign({}, tabs[index], {