Bug 1407347 - Prevent iframes in the inspector sidebar from rerendering when a sidebar tab is removed. r=Honza

This commit is contained in:
Gabriel Luong 2018-01-29 13:01:54 -05:00
Родитель e80f2e7469
Коммит 49de74e595
5 изменённых файлов: 82 добавлений и 21 удалений

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

@ -25,8 +25,10 @@ class InspectorTabPanel extends Component {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
// Optional prefix for panel IDs. // Optional prefix for panel IDs.
idPrefix: PropTypes.string, idPrefix: PropTypes.string,
// Optional mount callback // Optional mount callback.
onMount: PropTypes.func, onMount: PropTypes.func,
// Optional unmount callback.
onUnmount: PropTypes.func,
}; };
} }
@ -52,6 +54,10 @@ class InspectorTabPanel extends Component {
let doc = this.refs.content.ownerDocument; let doc = this.refs.content.ownerDocument;
let panels = doc.getElementById("tabpanels"); let panels = doc.getElementById("tabpanels");
if (this.props.onUnmount) {
this.props.onUnmount(this.refs.content, this.props);
}
// Move panel's content node back into list of tab panels. // Move panel's content node back into list of tab panels.
panels.appendChild(this.refs.content.firstChild); panels.appendChild(this.refs.content.firstChild);
} }

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

@ -137,6 +137,7 @@ ToolSidebar.prototype = {
title: title, title: title,
url: url, url: url,
onMount: this.onSidePanelMounted.bind(this), onMount: this.onSidePanelMounted.bind(this),
onUnmount: this.onSidePanelUnmounted.bind(this),
}); });
this.addTab(id, title, panel, selected, index); this.addTab(id, title, panel, selected, index);
@ -163,6 +164,20 @@ ToolSidebar.prototype = {
iframe.setAttribute("src", props.url); iframe.setAttribute("src", props.url);
}, },
onSidePanelUnmounted: function (content, props) {
let iframe = content.querySelector("iframe");
if (!iframe || !iframe.hasAttribute("src")) {
return;
}
let win = iframe.contentWindow;
if ("destroy" in win) {
win.destroy(this._toolPanel, iframe);
}
iframe.removeAttribute("src");
},
/** /**
* Remove an existing tab. * Remove an existing tab.
* @param {String} tabId The ID of the tab that was used to register it, or * @param {String} tabId The ID of the tab that was used to register it, or

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

@ -61,6 +61,7 @@ define(function (require, exports, module) {
tabActive: this.state.tabActive, tabActive: this.state.tabActive,
onAfterChange: this.onTabChanged}, onAfterChange: this.onTabChanged},
TabPanel({ TabPanel({
id: "json",
className: "json", className: "json",
title: JSONView.Locale.$STR("jsonViewer.tab.JSON")}, title: JSONView.Locale.$STR("jsonViewer.tab.JSON")},
JsonPanel({ JsonPanel({
@ -71,6 +72,7 @@ define(function (require, exports, module) {
}) })
), ),
TabPanel({ TabPanel({
id: "rawdata",
className: "rawdata", className: "rawdata",
title: JSONView.Locale.$STR("jsonViewer.tab.RawData")}, title: JSONView.Locale.$STR("jsonViewer.tab.RawData")},
TextPanel({ TextPanel({
@ -81,6 +83,7 @@ define(function (require, exports, module) {
}) })
), ),
TabPanel({ TabPanel({
id: "headers",
className: "headers", className: "headers",
title: JSONView.Locale.$STR("jsonViewer.tab.Headers")}, title: JSONView.Locale.$STR("jsonViewer.tab.Headers")},
HeadersPanel({ HeadersPanel({

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

@ -72,14 +72,15 @@ define(function (require, exports, module) {
this.state = { this.state = {
tabActive: props.tabActive, tabActive: props.tabActive,
// This array is used to store an information whether a tab // This array is used to store an object containing information on whether a tab
// at specific index has already been created (e.g. selected // at a specified index has already been created (e.g. selected at least once) and
// at least once). // the tab id. An example of the object structure is the following:
// If yes, it's rendered even if not currently selected. // [{ isCreated: true, tabId: "ruleview" }, { isCreated: false, tabId: "foo" }].
// This is because in some cases we don't want to re-create // If the tab at the specified index has already been created, it's rendered even
// tab content when it's being unselected/selected. // if not currently selected. This is because in some cases we don't want
// E.g. in case of an iframe being used as a tab-content // to re-create tab content when it's being unselected/selected.
// we want the iframe to stay in the DOM. // E.g. in case of an iframe being used as a tab-content we want the iframe to
// stay in the DOM.
created: [], created: [],
// True if tabs can't fit into available horizontal space. // True if tabs can't fit into available horizontal space.
@ -116,24 +117,46 @@ define(function (require, exports, module) {
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
let { children, tabActive } = nextProps; let { children, tabActive } = nextProps;
let panels = children.filter(panel => panel);
let created = [...this.state.created];
// Check type of 'tabActive' props to see if it's valid // If the children props has changed due to an addition or removal of a tab,
// (it's 0-based index). // update the state's created array with the latest tab ids and whether or not
// the tab is already created.
if (this.state.created.length != panels.length) {
created = panels.map(panel => {
// Get whether or not the tab has already been created from the previous state.
let createdEntry = this.state.created.find(entry => {
return entry && entry.tabId === panel.props.id;
});
let isCreated = !!createdEntry && createdEntry.isCreated;
let tabId = panel.props.id;
return {
isCreated,
tabId,
};
});
}
// Check type of 'tabActive' props to see if it's valid (it's 0-based index).
if (typeof tabActive === "number") { if (typeof tabActive === "number") {
let panels = children.filter((panel) => panel);
// Reset to index 0 if index overflows the range of panel array // Reset to index 0 if index overflows the range of panel array
tabActive = (tabActive < panels.length && tabActive >= 0) ? tabActive = (tabActive < panels.length && tabActive >= 0) ?
tabActive : 0; tabActive : 0;
let created = [...this.state.created]; created[tabActive] = Object.assign({}, created[tabActive], {
created[tabActive] = true; isCreated: true,
});
this.setState({ this.setState({
created,
tabActive, tabActive,
}); });
} }
this.setState({
created,
});
} }
componentWillUnmount() { componentWillUnmount() {
@ -209,11 +232,13 @@ define(function (require, exports, module) {
} }
let created = [...this.state.created]; let created = [...this.state.created];
created[index] = true; created[index] = Object.assign({}, created[index], {
isCreated: true,
});
let newState = Object.assign({}, this.state, { let newState = Object.assign({}, this.state, {
created,
tabActive: index, tabActive: index,
created: created
}); });
this.setState(newState, () => { this.setState(newState, () => {
@ -335,6 +360,8 @@ define(function (require, exports, module) {
} }
let id = tab.props.id; let id = tab.props.id;
let isCreated = this.state.created[index] &&
this.state.created[index].isCreated;
// Use 'visibility:hidden' + 'height:0' for hiding content of non-selected // Use 'visibility:hidden' + 'height:0' for hiding content of non-selected
// tab. It's faster than 'display:none' because it avoids triggering frame // tab. It's faster than 'display:none' because it avoids triggering frame
@ -354,13 +381,13 @@ define(function (require, exports, module) {
return ( return (
dom.div({ dom.div({
id: id ? id + "-panel" : "panel-" + index, id: id ? id + "-panel" : "panel-" + index,
key: index, key: id,
style: style, style: style,
className: selected ? "tab-panel-box" : "tab-panel-box hidden", className: selected ? "tab-panel-box" : "tab-panel-box hidden",
role: "tabpanel", role: "tabpanel",
"aria-labelledby": id ? id + "-tab" : "tab-" + index, "aria-labelledby": id ? id + "-tab" : "tab-" + index,
}, },
(selected || this.state.created[index]) ? panel : null (selected || isCreated) ? panel : null
) )
); );
}); });
@ -388,6 +415,8 @@ define(function (require, exports, module) {
class Panel extends Component { class Panel extends Component {
static get propTypes() { static get propTypes() {
return { return {
id: PropTypes.string.isRequired,
className: PropTypes.string,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
children: PropTypes.oneOfType([ children: PropTypes.oneOfType([
PropTypes.array, PropTypes.array,

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

@ -98,6 +98,7 @@ class NetInfoBody extends Component {
// Headers tab // Headers tab
panels.push( panels.push(
TabPanel({ TabPanel({
id: "headers",
className: "headers", className: "headers",
key: "headers", key: "headers",
title: Locale.$STR("netRequest.headers")}, title: Locale.$STR("netRequest.headers")},
@ -109,6 +110,7 @@ class NetInfoBody extends Component {
if (hasParams) { if (hasParams) {
panels.push( panels.push(
TabPanel({ TabPanel({
id: "params",
className: "params", className: "params",
key: "params", key: "params",
title: Locale.$STR("netRequest.params")}, title: Locale.$STR("netRequest.params")},
@ -121,6 +123,7 @@ class NetInfoBody extends Component {
if (hasPostData) { if (hasPostData) {
panels.push( panels.push(
TabPanel({ TabPanel({
id: "post",
className: "post", className: "post",
key: "post", key: "post",
title: Locale.$STR("netRequest.post")}, title: Locale.$STR("netRequest.post")},
@ -131,7 +134,10 @@ class NetInfoBody extends Component {
// Response tab // Response tab
panels.push( panels.push(
TabPanel({className: "response", key: "response", TabPanel({
id: "response",
className: "response",
key: "response",
title: Locale.$STR("netRequest.response")}, title: Locale.$STR("netRequest.response")},
ResponseTab({data: data, actions: actions}) ResponseTab({data: data, actions: actions})
) )
@ -141,6 +147,7 @@ class NetInfoBody extends Component {
if (this.hasCookies()) { if (this.hasCookies()) {
panels.push( panels.push(
TabPanel({ TabPanel({
id: "cookies",
className: "cookies", className: "cookies",
key: "cookies", key: "cookies",
title: Locale.$STR("netRequest.cookies")}, title: Locale.$STR("netRequest.cookies")},
@ -156,6 +163,7 @@ class NetInfoBody extends Component {
if (this.hasStackTrace()) { if (this.hasStackTrace()) {
panels.push( panels.push(
TabPanel({ TabPanel({
id: "stacktrace-tab",
className: "stacktrace-tab", className: "stacktrace-tab",
key: "stacktrace", key: "stacktrace",
title: Locale.$STR("netRequest.callstack")}, title: Locale.$STR("netRequest.callstack")},