зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1407347 - Prevent iframes in the inspector sidebar from rerendering when a sidebar tab is removed. r=Honza
This commit is contained in:
Родитель
e80f2e7469
Коммит
49de74e595
|
@ -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")},
|
||||
|
|
Загрузка…
Ссылка в новой задаче