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,
// Optional prefix for panel IDs.
idPrefix: PropTypes.string,
// Optional mount callback
// Optional mount callback.
onMount: PropTypes.func,
// Optional unmount callback.
onUnmount: PropTypes.func,
};
}
@ -52,6 +54,10 @@ class InspectorTabPanel extends Component {
let doc = this.refs.content.ownerDocument;
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.
panels.appendChild(this.refs.content.firstChild);
}

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

@ -137,6 +137,7 @@ ToolSidebar.prototype = {
title: title,
url: url,
onMount: this.onSidePanelMounted.bind(this),
onUnmount: this.onSidePanelUnmounted.bind(this),
});
this.addTab(id, title, panel, selected, index);
@ -163,6 +164,20 @@ ToolSidebar.prototype = {
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.
* @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,
onAfterChange: this.onTabChanged},
TabPanel({
id: "json",
className: "json",
title: JSONView.Locale.$STR("jsonViewer.tab.JSON")},
JsonPanel({
@ -71,6 +72,7 @@ define(function (require, exports, module) {
})
),
TabPanel({
id: "rawdata",
className: "rawdata",
title: JSONView.Locale.$STR("jsonViewer.tab.RawData")},
TextPanel({
@ -81,6 +83,7 @@ define(function (require, exports, module) {
})
),
TabPanel({
id: "headers",
className: "headers",
title: JSONView.Locale.$STR("jsonViewer.tab.Headers")},
HeadersPanel({

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

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

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

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