Source code
Revision control
Copy as Markdown
Other Tools
/* 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
"use strict";
const {
Component,
} = require("resource://devtools/client/shared/vendor/react.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const {
connect,
} = require("resource://devtools/client/shared/redux/visibility-handler-connect.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const Editor = require("resource://devtools/client/shared/sourceeditor/editor.js");
const {
setTargetSearchResult,
} = require("resource://devtools/client/netmonitor/src/actions/search.js");
const { div } = dom;
/**
* CodeMirror editor as a React component
*/
class SourcePreview extends Component {
static get propTypes() {
return {
// Source editor syntax highlight mode, which is a mime type defined in CodeMirror
mode: PropTypes.string,
// Source editor content
text: PropTypes.string,
// Search result text to select
targetSearchResult: PropTypes.object,
// Reset target search result that has been used for navigation in this panel.
// This is done to avoid second navigation the next time.
resetTargetSearchResult: PropTypes.func,
};
}
componentDidMount() {
const { mode, text } = this.props;
this.loadEditor(mode, text);
}
shouldComponentUpdate(nextProps) {
return (
nextProps.mode !== this.props.mode ||
nextProps.text !== this.props.text ||
nextProps.targetSearchResult !== this.props.targetSearchResult
);
}
componentDidUpdate(prevProps) {
const { mode, targetSearchResult, text } = this.props;
if (prevProps.text !== text) {
// When updating from editor to editor
this.updateEditor(mode, text);
} else if (prevProps.targetSearchResult !== targetSearchResult) {
this.findSearchResult();
}
}
componentWillUnmount() {
this.unloadEditor();
}
loadEditor(mode, text) {
this.editor = new Editor({
lineNumbers: true,
lineWrapping: false,
mode: null, // Disable auto syntax detection, but then we set mode asynchronously
readOnly: true,
theme: "mozilla",
value: text,
});
// Delay to CodeMirror initialization content to prevent UI freezing
this.editorTimeout = setTimeout(() => {
this.editorTimeout = null;
this.editor.appendToLocalElement(this.refs.editorElement);
// CodeMirror's setMode() (syntax highlight) is the performance bottleneck when
// processing large content, so we enable it asynchronously within the setTimeout
// to avoid UI blocking. (rendering source code -> drawing syntax highlight)
this.editorSetModeTimeout = setTimeout(() => {
this.editorSetModeTimeout = null;
this.editor.setMode(mode);
this.findSearchResult();
});
});
}
updateEditor(mode, text) {
// Reset the existed 'mode' attribute in order to make setText() process faster
// to prevent drawing unnecessary syntax highlight.
if (this?.editor?.hasCodeMirror) {
this.editor.setMode(null);
this.editor.setText(text);
}
if (this.editorSetModeTimeout) {
clearTimeout(this.editorSetModeTimeout);
}
// CodeMirror's setMode() (syntax highlight) is the performance bottleneck when
// processing large content, so we enable it asynchronously within the setTimeout
// to avoid UI blocking. (rendering source code -> drawing syntax highlight)
this.editorSetModeTimeout = setTimeout(() => {
this.editorSetModeTimeout = null;
this.editor.setMode(mode);
this.findSearchResult();
});
}
unloadEditor() {
clearTimeout(this.editorTimeout);
clearTimeout(this.editorSetModeTimeout);
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
}
findSearchResult() {
const { targetSearchResult, resetTargetSearchResult } = this.props;
if (targetSearchResult?.line) {
const { line } = targetSearchResult;
// scroll the editor to center the line
// with the target search result
if (this.editor) {
this.editor.setCursor({ line: line - 1 }, "center");
}
}
resetTargetSearchResult();
}
// Scroll to specified line if the user clicks on search results.
scrollToLine(element) {
const { targetSearchResult, resetTargetSearchResult } = this.props;
// The following code is responsible for scrolling given line
// to visible view-port.
// It gets the <div> child element representing the target
// line (by index) and uses `scrollIntoView` API to make sure
// it's visible to the user.
if (element && targetSearchResult && targetSearchResult.line) {
const child = element.children[targetSearchResult.line - 1];
if (child) {
const range = document.createRange();
range.selectNode(child);
document.getSelection().addRange(range);
child.scrollIntoView({ block: "center" });
}
resetTargetSearchResult();
}
}
renderEditor() {
return div(
{ className: "editor-row-container" },
div({
ref: "editorElement",
className: "source-editor-mount devtools-monospace",
})
);
}
render() {
return div(
{ key: "EDITOR_CONFIG", className: "editor-row-container" },
this.renderEditor()
);
}
}
module.exports = connect(null, dispatch => ({
resetTargetSearchResult: () => dispatch(setTargetSearchResult(null)),
}))(SourcePreview);