mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-06 15:58:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
11
devtools/client/jsonview/.eslintrc.js
Normal file
11
devtools/client/jsonview/.eslintrc.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"globals": {
|
||||
"define": true,
|
||||
"document": true,
|
||||
"window": true,
|
||||
"CustomEvent": true,
|
||||
"Locale": true
|
||||
}
|
||||
};
|
||||
79
devtools/client/jsonview/components/headers-panel.js
Normal file
79
devtools/client/jsonview/components/headers-panel.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { Headers } = createFactories(require("./headers"));
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
|
||||
const { div } = dom;
|
||||
|
||||
/**
|
||||
* This template represents the 'Headers' panel
|
||||
* s responsible for rendering its content.
|
||||
*/
|
||||
let HeadersPanel = createClass({
|
||||
displayName: "HeadersPanel",
|
||||
|
||||
propTypes: {
|
||||
actions: PropTypes.object,
|
||||
data: PropTypes.object,
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
data: {}
|
||||
};
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let data = this.props.data;
|
||||
|
||||
return (
|
||||
div({className: "headersPanelBox"},
|
||||
HeadersToolbar({actions: this.props.actions}),
|
||||
div({className: "panelContent"},
|
||||
Headers({data: data})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This template is responsible for rendering a toolbar
|
||||
* within the 'Headers' panel.
|
||||
*/
|
||||
let HeadersToolbar = createFactory(createClass({
|
||||
displayName: "HeadersToolbar",
|
||||
|
||||
propTypes: {
|
||||
actions: PropTypes.object,
|
||||
},
|
||||
|
||||
// Commands
|
||||
|
||||
onCopy: function (event) {
|
||||
this.props.actions.onCopyHeaders();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
Toolbar({},
|
||||
ToolbarButton({className: "btn copy", onClick: this.onCopy},
|
||||
Locale.$STR("jsonViewer.Copy")
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
// Exports from this module
|
||||
exports.HeadersPanel = HeadersPanel;
|
||||
});
|
||||
105
devtools/client/jsonview/components/headers.js
Normal file
105
devtools/client/jsonview/components/headers.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { div, span, table, tbody, tr, td, } = dom;
|
||||
|
||||
/**
|
||||
* This template is responsible for rendering basic layout
|
||||
* of the 'Headers' panel. It displays HTTP headers groups such as
|
||||
* received or response headers.
|
||||
*/
|
||||
let Headers = createClass({
|
||||
displayName: "Headers",
|
||||
|
||||
propTypes: {
|
||||
data: PropTypes.object,
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let data = this.props.data;
|
||||
|
||||
return (
|
||||
div({className: "netInfoHeadersTable"},
|
||||
div({className: "netHeadersGroup"},
|
||||
div({className: "netInfoHeadersGroup"},
|
||||
Locale.$STR("jsonViewer.responseHeaders")
|
||||
),
|
||||
table({cellPadding: 0, cellSpacing: 0},
|
||||
HeaderList({headers: data.response})
|
||||
)
|
||||
),
|
||||
div({className: "netHeadersGroup"},
|
||||
div({className: "netInfoHeadersGroup"},
|
||||
Locale.$STR("jsonViewer.requestHeaders")
|
||||
),
|
||||
table({cellPadding: 0, cellSpacing: 0},
|
||||
HeaderList({headers: data.request})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This template renders headers list,
|
||||
* name + value pairs.
|
||||
*/
|
||||
let HeaderList = createFactory(createClass({
|
||||
displayName: "HeaderList",
|
||||
|
||||
propTypes: {
|
||||
headers: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.string
|
||||
}))
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
headers: []
|
||||
};
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let headers = this.props.headers;
|
||||
|
||||
headers.sort(function (a, b) {
|
||||
return a.name > b.name ? 1 : -1;
|
||||
});
|
||||
|
||||
let rows = [];
|
||||
headers.forEach(header => {
|
||||
rows.push(
|
||||
tr({key: header.name},
|
||||
td({className: "netInfoParamName"},
|
||||
span({title: header.name}, header.name)
|
||||
),
|
||||
td({className: "netInfoParamValue"}, header.value)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
tbody({},
|
||||
rows
|
||||
)
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
// Exports from this module
|
||||
exports.Headers = Headers;
|
||||
});
|
||||
194
devtools/client/jsonview/components/json-panel.js
Normal file
194
devtools/client/jsonview/components/json-panel.js
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
|
||||
const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
|
||||
const { SearchBox } = createFactories(require("./search-box"));
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
|
||||
const { div } = dom;
|
||||
const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
|
||||
const AUTO_EXPAND_MAX_LEVEL = 7;
|
||||
|
||||
/**
|
||||
* This template represents the 'JSON' panel. The panel is
|
||||
* responsible for rendering an expandable tree that allows simple
|
||||
* inspection of JSON structure.
|
||||
*/
|
||||
let JsonPanel = createClass({
|
||||
displayName: "JsonPanel",
|
||||
|
||||
propTypes: {
|
||||
data: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.array,
|
||||
PropTypes.object
|
||||
]),
|
||||
jsonTextLength: PropTypes.number,
|
||||
searchFilter: PropTypes.string,
|
||||
actions: PropTypes.object,
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
document.addEventListener("keypress", this.onKeyPress, true);
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
document.removeEventListener("keypress", this.onKeyPress, true);
|
||||
},
|
||||
|
||||
onKeyPress: function (e) {
|
||||
// XXX shortcut for focusing the Filter field (see Bug 1178771).
|
||||
},
|
||||
|
||||
onFilter: function (object) {
|
||||
if (!this.props.searchFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let json = JSON.stringify(object).toLowerCase();
|
||||
return json.indexOf(this.props.searchFilter.toLowerCase()) >= 0;
|
||||
},
|
||||
|
||||
getExpandedNodes: function (object, path = "", level = 0) {
|
||||
if (typeof object != "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (level > AUTO_EXPAND_MAX_LEVEL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let expandedNodes = new Set();
|
||||
for (let prop in object) {
|
||||
let nodePath = path + "/" + prop;
|
||||
expandedNodes.add(nodePath);
|
||||
|
||||
let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1);
|
||||
if (nodes) {
|
||||
expandedNodes = new Set([...expandedNodes, ...nodes]);
|
||||
}
|
||||
}
|
||||
return expandedNodes;
|
||||
},
|
||||
|
||||
renderValue: props => {
|
||||
let member = props.member;
|
||||
|
||||
// Hide object summary when object is expanded (bug 1244912).
|
||||
if (typeof member.value == "object" && member.open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Render the value (summary) using Reps library.
|
||||
return Rep(Object.assign({}, props, {
|
||||
cropLimit: 50,
|
||||
}));
|
||||
},
|
||||
|
||||
renderTree: function () {
|
||||
// Append custom column for displaying values. This column
|
||||
// Take all available horizontal space.
|
||||
let columns = [{
|
||||
id: "value",
|
||||
width: "100%"
|
||||
}];
|
||||
|
||||
// Expand the document by default if its size isn't bigger than 100KB.
|
||||
let expandedNodes = new Set();
|
||||
if (this.props.jsonTextLength <= AUTO_EXPAND_MAX_SIZE) {
|
||||
expandedNodes = this.getExpandedNodes(this.props.data);
|
||||
}
|
||||
|
||||
// Render tree component.
|
||||
return TreeView({
|
||||
object: this.props.data,
|
||||
mode: "tiny",
|
||||
onFilter: this.onFilter,
|
||||
columns: columns,
|
||||
renderValue: this.renderValue,
|
||||
expandedNodes: expandedNodes,
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let content;
|
||||
let data = this.props.data;
|
||||
|
||||
try {
|
||||
if (typeof data == "object") {
|
||||
content = this.renderTree();
|
||||
} else {
|
||||
content = div({className: "jsonParseError"},
|
||||
data + ""
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
content = div({className: "jsonParseError"},
|
||||
err + ""
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
div({className: "jsonPanelBox"},
|
||||
JsonToolbar({actions: this.props.actions}),
|
||||
div({className: "panelContent"},
|
||||
content
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This template represents a toolbar within the 'JSON' panel.
|
||||
*/
|
||||
let JsonToolbar = createFactory(createClass({
|
||||
displayName: "JsonToolbar",
|
||||
|
||||
propTypes: {
|
||||
actions: PropTypes.object,
|
||||
},
|
||||
|
||||
// Commands
|
||||
|
||||
onSave: function (event) {
|
||||
this.props.actions.onSaveJson();
|
||||
},
|
||||
|
||||
onCopy: function (event) {
|
||||
this.props.actions.onCopyJson();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
Toolbar({},
|
||||
ToolbarButton({className: "btn save", onClick: this.onSave},
|
||||
Locale.$STR("jsonViewer.Save")
|
||||
),
|
||||
ToolbarButton({className: "btn copy", onClick: this.onCopy},
|
||||
Locale.$STR("jsonViewer.Copy")
|
||||
),
|
||||
SearchBox({
|
||||
actions: this.props.actions
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
// Exports from this module
|
||||
exports.JsonPanel = JsonPanel;
|
||||
});
|
||||
89
devtools/client/jsonview/components/main-tabbed-area.js
Normal file
89
devtools/client/jsonview/components/main-tabbed-area.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const { createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { JsonPanel } = createFactories(require("./json-panel"));
|
||||
const { TextPanel } = createFactories(require("./text-panel"));
|
||||
const { HeadersPanel } = createFactories(require("./headers-panel"));
|
||||
const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/tabs"));
|
||||
|
||||
/**
|
||||
* This object represents the root application template
|
||||
* responsible for rendering the basic tab layout.
|
||||
*/
|
||||
let MainTabbedArea = createClass({
|
||||
displayName: "MainTabbedArea",
|
||||
|
||||
propTypes: {
|
||||
jsonText: PropTypes.string,
|
||||
tabActive: PropTypes.number,
|
||||
actions: PropTypes.object,
|
||||
headers: PropTypes.object,
|
||||
searchFilter: PropTypes.string,
|
||||
json: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.object,
|
||||
PropTypes.array
|
||||
])
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
json: {},
|
||||
headers: {},
|
||||
jsonText: this.props.jsonText,
|
||||
tabActive: this.props.tabActive
|
||||
};
|
||||
},
|
||||
|
||||
onTabChanged: function (index) {
|
||||
this.setState({tabActive: index});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
Tabs({
|
||||
tabActive: this.state.tabActive,
|
||||
onAfterChange: this.onTabChanged},
|
||||
TabPanel({
|
||||
className: "json",
|
||||
title: Locale.$STR("jsonViewer.tab.JSON")},
|
||||
JsonPanel({
|
||||
data: this.props.json,
|
||||
jsonTextLength: this.props.jsonText.length,
|
||||
actions: this.props.actions,
|
||||
searchFilter: this.state.searchFilter
|
||||
})
|
||||
),
|
||||
TabPanel({
|
||||
className: "rawdata",
|
||||
title: Locale.$STR("jsonViewer.tab.RawData")},
|
||||
TextPanel({
|
||||
data: this.state.jsonText,
|
||||
actions: this.props.actions
|
||||
})
|
||||
),
|
||||
TabPanel({
|
||||
className: "headers",
|
||||
title: Locale.$STR("jsonViewer.tab.Headers")},
|
||||
HeadersPanel({
|
||||
data: this.props.headers,
|
||||
actions: this.props.actions,
|
||||
searchFilter: this.props.searchFilter
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.MainTabbedArea = MainTabbedArea;
|
||||
});
|
||||
18
devtools/client/jsonview/components/moz.build
Normal file
18
devtools/client/jsonview/components/moz.build
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += [
|
||||
'reps'
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'headers-panel.js',
|
||||
'headers.js',
|
||||
'json-panel.js',
|
||||
'main-tabbed-area.js',
|
||||
'search-box.js',
|
||||
'text-panel.js'
|
||||
)
|
||||
9
devtools/client/jsonview/components/reps/moz.build
Normal file
9
devtools/client/jsonview/components/reps/moz.build
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'toolbar.js',
|
||||
)
|
||||
58
devtools/client/jsonview/components/reps/toolbar.js
Normal file
58
devtools/client/jsonview/components/reps/toolbar.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const React = require("devtools/client/shared/vendor/react");
|
||||
const DOM = React.DOM;
|
||||
|
||||
/**
|
||||
* Renders a simple toolbar.
|
||||
*/
|
||||
let Toolbar = React.createClass({
|
||||
displayName: "Toolbar",
|
||||
|
||||
propTypes: {
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.array,
|
||||
React.PropTypes.element
|
||||
])
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
DOM.div({className: "toolbar"},
|
||||
this.props.children
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders a simple toolbar button.
|
||||
*/
|
||||
let ToolbarButton = React.createClass({
|
||||
displayName: "ToolbarButton",
|
||||
|
||||
propTypes: {
|
||||
active: React.PropTypes.bool,
|
||||
disabled: React.PropTypes.bool,
|
||||
children: React.PropTypes.string,
|
||||
},
|
||||
|
||||
render: function () {
|
||||
let props = Object.assign({className: "btn"}, this.props);
|
||||
return (
|
||||
DOM.button(props, this.props.children)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.Toolbar = Toolbar;
|
||||
exports.ToolbarButton = ToolbarButton;
|
||||
});
|
||||
55
devtools/client/jsonview/components/search-box.js
Normal file
55
devtools/client/jsonview/components/search-box.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
|
||||
const { input } = dom;
|
||||
|
||||
// For smooth incremental searching (in case the user is typing quickly).
|
||||
const searchDelay = 250;
|
||||
|
||||
/**
|
||||
* This object represents a search box located at the
|
||||
* top right corner of the application.
|
||||
*/
|
||||
let SearchBox = createClass({
|
||||
displayName: "SearchBox",
|
||||
|
||||
propTypes: {
|
||||
actions: PropTypes.object,
|
||||
},
|
||||
|
||||
onSearch: function (event) {
|
||||
let searchBox = event.target;
|
||||
let win = searchBox.ownerDocument.defaultView;
|
||||
|
||||
if (this.searchTimeout) {
|
||||
win.clearTimeout(this.searchTimeout);
|
||||
}
|
||||
|
||||
let callback = this.doSearch.bind(this, searchBox);
|
||||
this.searchTimeout = win.setTimeout(callback, searchDelay);
|
||||
},
|
||||
|
||||
doSearch: function (searchBox) {
|
||||
this.props.actions.onSearch(searchBox.value);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
input({className: "searchBox",
|
||||
placeholder: Locale.$STR("jsonViewer.filterJSON"),
|
||||
onChange: this.onSearch})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Exports from this module
|
||||
exports.SearchBox = SearchBox;
|
||||
});
|
||||
95
devtools/client/jsonview/components/text-panel.js
Normal file
95
devtools/client/jsonview/components/text-panel.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
|
||||
const { div, pre } = dom;
|
||||
|
||||
/**
|
||||
* This template represents the 'Raw Data' panel displaying
|
||||
* JSON as a text received from the server.
|
||||
*/
|
||||
let TextPanel = createClass({
|
||||
displayName: "TextPanel",
|
||||
|
||||
propTypes: {
|
||||
actions: PropTypes.object,
|
||||
data: PropTypes.string
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
div({className: "textPanelBox"},
|
||||
TextToolbar({actions: this.props.actions}),
|
||||
div({className: "panelContent"},
|
||||
pre({className: "data"},
|
||||
this.props.data
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This object represents a toolbar displayed within the
|
||||
* 'Raw Data' panel.
|
||||
*/
|
||||
let TextToolbar = createFactory(createClass({
|
||||
displayName: "TextToolbar",
|
||||
|
||||
propTypes: {
|
||||
actions: PropTypes.object,
|
||||
},
|
||||
|
||||
// Commands
|
||||
|
||||
onPrettify: function (event) {
|
||||
this.props.actions.onPrettify();
|
||||
},
|
||||
|
||||
onSave: function (event) {
|
||||
this.props.actions.onSaveJson();
|
||||
},
|
||||
|
||||
onCopy: function (event) {
|
||||
this.props.actions.onCopyJson();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return (
|
||||
Toolbar({},
|
||||
ToolbarButton({
|
||||
className: "btn save",
|
||||
onClick: this.onSave},
|
||||
Locale.$STR("jsonViewer.Save")
|
||||
),
|
||||
ToolbarButton({
|
||||
className: "btn copy",
|
||||
onClick: this.onCopy},
|
||||
Locale.$STR("jsonViewer.Copy")
|
||||
),
|
||||
ToolbarButton({
|
||||
className: "btn prettyprint",
|
||||
onClick: this.onPrettify},
|
||||
Locale.$STR("jsonViewer.PrettyPrint")
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
// Exports from this module
|
||||
exports.TextPanel = TextPanel;
|
||||
});
|
||||
345
devtools/client/jsonview/converter-child.js
Normal file
345
devtools/client/jsonview/converter-child.js
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, components} = require("chrome");
|
||||
const Services = require("Services");
|
||||
const {Class} = require("sdk/core/heritage");
|
||||
const {Unknown} = require("sdk/platform/xpcom");
|
||||
const xpcom = require("sdk/platform/xpcom");
|
||||
const Events = require("sdk/dom/events");
|
||||
const Clipboard = require("sdk/clipboard");
|
||||
|
||||
loader.lazyRequireGetter(this, "NetworkHelper",
|
||||
"devtools/shared/webconsole/network-helper");
|
||||
loader.lazyRequireGetter(this, "JsonViewUtils",
|
||||
"devtools/client/jsonview/utils");
|
||||
|
||||
const childProcessMessageManager =
|
||||
Cc["@mozilla.org/childprocessmessagemanager;1"]
|
||||
.getService(Ci.nsISyncMessageSender);
|
||||
|
||||
// Amount of space that will be allocated for the stream's backing-store.
|
||||
// Must be power of 2. Used to copy the data stream in onStopRequest.
|
||||
const SEGMENT_SIZE = Math.pow(2, 17);
|
||||
|
||||
const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view";
|
||||
const CONTRACT_ID = "@mozilla.org/streamconv;1?from=" +
|
||||
JSON_VIEW_MIME_TYPE + "&to=*/*";
|
||||
const CLASS_ID = "{d8c9acee-dec5-11e4-8c75-1681e6b88ec1}";
|
||||
|
||||
// Localization
|
||||
let jsonViewStrings = Services.strings.createBundle(
|
||||
"chrome://devtools/locale/jsonview.properties");
|
||||
|
||||
/**
|
||||
* This object detects 'application/vnd.mozilla.json.view' content type
|
||||
* and converts it into a JSON Viewer application that allows simple
|
||||
* JSON inspection.
|
||||
*
|
||||
* Inspired by JSON View: https://github.com/bhollis/jsonview/
|
||||
*/
|
||||
let Converter = Class({
|
||||
extends: Unknown,
|
||||
|
||||
interfaces: [
|
||||
"nsIStreamConverter",
|
||||
"nsIStreamListener",
|
||||
"nsIRequestObserver"
|
||||
],
|
||||
|
||||
get wrappedJSObject() {
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* This component works as such:
|
||||
* 1. asyncConvertData captures the listener
|
||||
* 2. onStartRequest fires, initializes stuff, modifies the listener
|
||||
* to match our output type
|
||||
* 3. onDataAvailable transcodes the data into a UTF-8 string
|
||||
* 4. onStopRequest gets the collected data and converts it,
|
||||
* spits it to the listener
|
||||
* 5. convert does nothing, it's just the synchronous version
|
||||
* of asyncConvertData
|
||||
*/
|
||||
convert: function (fromStream, fromType, toType, ctx) {
|
||||
return fromStream;
|
||||
},
|
||||
|
||||
asyncConvertData: function (fromType, toType, listener, ctx) {
|
||||
this.listener = listener;
|
||||
},
|
||||
|
||||
onDataAvailable: function (request, context, inputStream, offset, count) {
|
||||
// From https://developer.mozilla.org/en/Reading_textual_data
|
||||
let is = Cc["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Ci.nsIConverterInputStream);
|
||||
is.init(inputStream, this.charset, -1,
|
||||
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
|
||||
// Seed it with something positive
|
||||
while (count) {
|
||||
let str = {};
|
||||
let bytesRead = is.readString(count, str);
|
||||
if (!bytesRead) {
|
||||
break;
|
||||
}
|
||||
count -= bytesRead;
|
||||
this.data += str.value;
|
||||
}
|
||||
},
|
||||
|
||||
onStartRequest: function (request, context) {
|
||||
this.data = "";
|
||||
this.uri = request.QueryInterface(Ci.nsIChannel).URI.spec;
|
||||
|
||||
// Sets the charset if it is available. (For documents loaded from the
|
||||
// filesystem, this is not set.)
|
||||
this.charset =
|
||||
request.QueryInterface(Ci.nsIChannel).contentCharset || "UTF-8";
|
||||
|
||||
this.channel = request;
|
||||
this.channel.contentType = "text/html";
|
||||
this.channel.contentCharset = "UTF-8";
|
||||
// Because content might still have a reference to this window,
|
||||
// force setting it to a null principal to avoid it being same-
|
||||
// origin with (other) content.
|
||||
this.channel.loadInfo.resetPrincipalsToNullPrincipal();
|
||||
|
||||
this.listener.onStartRequest(this.channel, context);
|
||||
},
|
||||
|
||||
/**
|
||||
* This should go something like this:
|
||||
* 1. Make sure we have a unicode string.
|
||||
* 2. Convert it to a Javascript object.
|
||||
* 2.1 Removes the callback
|
||||
* 3. Convert that to HTML? Or XUL?
|
||||
* 4. Spit it back out at the listener
|
||||
*/
|
||||
onStopRequest: function (request, context, statusCode) {
|
||||
let headers = {
|
||||
response: [],
|
||||
request: []
|
||||
};
|
||||
|
||||
let win = NetworkHelper.getWindowForRequest(request);
|
||||
|
||||
let Locale = {
|
||||
$STR: key => {
|
||||
try {
|
||||
return jsonViewStrings.GetStringFromName(key);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
JsonViewUtils.exportIntoContentScope(win, Locale, "Locale");
|
||||
|
||||
Events.once(win, "DOMContentLoaded", event => {
|
||||
win.addEventListener("contentMessage",
|
||||
this.onContentMessage.bind(this), false, true);
|
||||
});
|
||||
|
||||
// The request doesn't have to be always nsIHttpChannel
|
||||
// (e.g. in case of data: URLs)
|
||||
if (request instanceof Ci.nsIHttpChannel) {
|
||||
request.visitResponseHeaders({
|
||||
visitHeader: function (name, value) {
|
||||
headers.response.push({name: name, value: value});
|
||||
}
|
||||
});
|
||||
|
||||
request.visitRequestHeaders({
|
||||
visitHeader: function (name, value) {
|
||||
headers.request.push({name: name, value: value});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let outputDoc = "";
|
||||
|
||||
try {
|
||||
headers = JSON.stringify(headers);
|
||||
outputDoc = this.toHTML(this.data, headers, this.uri);
|
||||
} catch (e) {
|
||||
console.error("JSON Viewer ERROR " + e);
|
||||
outputDoc = this.toErrorPage(e, this.data, this.uri);
|
||||
}
|
||||
|
||||
let storage = Cc["@mozilla.org/storagestream;1"]
|
||||
.createInstance(Ci.nsIStorageStream);
|
||||
|
||||
storage.init(SEGMENT_SIZE, 0xffffffff, null);
|
||||
let out = storage.getOutputStream(0);
|
||||
|
||||
let binout = Cc["@mozilla.org/binaryoutputstream;1"]
|
||||
.createInstance(Ci.nsIBinaryOutputStream);
|
||||
|
||||
binout.setOutputStream(out);
|
||||
binout.writeUtf8Z(outputDoc);
|
||||
binout.close();
|
||||
|
||||
// We need to trim 4 bytes off the front (this could be underlying bug).
|
||||
let trunc = 4;
|
||||
let instream = storage.newInputStream(trunc);
|
||||
|
||||
// Pass the data to the main content listener
|
||||
this.listener.onDataAvailable(this.channel, context, instream, 0,
|
||||
instream.available());
|
||||
|
||||
this.listener.onStopRequest(this.channel, context, statusCode);
|
||||
|
||||
this.listener = null;
|
||||
},
|
||||
|
||||
htmlEncode: function (t) {
|
||||
return t !== null ? t.toString()
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">") : "";
|
||||
},
|
||||
|
||||
toHTML: function (json, headers, title) {
|
||||
let themeClassName = "theme-" + JsonViewUtils.getCurrentTheme();
|
||||
let clientBaseUrl = "resource://devtools/client/";
|
||||
let baseUrl = clientBaseUrl + "jsonview/";
|
||||
let themeVarsUrl = clientBaseUrl + "themes/variables.css";
|
||||
let commonUrl = clientBaseUrl + "themes/common.css";
|
||||
let toolbarsUrl = clientBaseUrl + "themes/toolbars.css";
|
||||
|
||||
let os;
|
||||
let platform = Services.appinfo.OS;
|
||||
if (platform.startsWith("WINNT")) {
|
||||
os = "win";
|
||||
} else if (platform.startsWith("Darwin")) {
|
||||
os = "mac";
|
||||
} else {
|
||||
os = "linux";
|
||||
}
|
||||
|
||||
return "<!DOCTYPE html>\n" +
|
||||
"<html platform=\"" + os + "\" class=\"" + themeClassName + "\">" +
|
||||
"<head><title>" + this.htmlEncode(title) + "</title>" +
|
||||
"<base href=\"" + this.htmlEncode(baseUrl) + "\">" +
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
|
||||
themeVarsUrl + "\">" +
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
|
||||
commonUrl + "\">" +
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
|
||||
toolbarsUrl + "\">" +
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css\">" +
|
||||
"<script data-main=\"viewer-config\" src=\"lib/require.js\"></script>" +
|
||||
"</head><body>" +
|
||||
"<div id=\"content\"></div>" +
|
||||
"<div id=\"json\">" + this.htmlEncode(json) + "</div>" +
|
||||
"<div id=\"headers\">" + this.htmlEncode(headers) + "</div>" +
|
||||
"</body></html>";
|
||||
},
|
||||
|
||||
toErrorPage: function (error, data, uri) {
|
||||
// Escape unicode nulls
|
||||
data = data.replace("\u0000", "\uFFFD");
|
||||
|
||||
let errorInfo = error + "";
|
||||
|
||||
let output = "<div id=\"error\">" + "error parsing";
|
||||
if (errorInfo.message) {
|
||||
output += "<div class=\"errormessage\">" + errorInfo.message + "</div>";
|
||||
}
|
||||
|
||||
output += "</div><div id=\"json\">" + this.highlightError(data,
|
||||
errorInfo.line, errorInfo.column) + "</div>";
|
||||
|
||||
return "<!DOCTYPE html>\n" +
|
||||
"<html><head><title>" + this.htmlEncode(uri + " - Error") + "</title>" +
|
||||
"<base href=\"" + this.htmlEncode(this.data.url()) + "\">" +
|
||||
"</head><body>" +
|
||||
output +
|
||||
"</body></html>";
|
||||
},
|
||||
|
||||
// Chrome <-> Content communication
|
||||
|
||||
onContentMessage: function (e) {
|
||||
// Do not handle events from different documents.
|
||||
let win = NetworkHelper.getWindowForRequest(this.channel);
|
||||
if (win != e.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
let value = e.detail.value;
|
||||
switch (e.detail.type) {
|
||||
case "copy":
|
||||
Clipboard.set(value, "text");
|
||||
break;
|
||||
|
||||
case "copy-headers":
|
||||
this.copyHeaders(value);
|
||||
break;
|
||||
|
||||
case "save":
|
||||
childProcessMessageManager.sendAsyncMessage(
|
||||
"devtools:jsonview:save", value);
|
||||
}
|
||||
},
|
||||
|
||||
copyHeaders: function (headers) {
|
||||
let value = "";
|
||||
let eol = (Services.appinfo.OS !== "WINNT") ? "\n" : "\r\n";
|
||||
|
||||
let responseHeaders = headers.response;
|
||||
for (let i = 0; i < responseHeaders.length; i++) {
|
||||
let header = responseHeaders[i];
|
||||
value += header.name + ": " + header.value + eol;
|
||||
}
|
||||
|
||||
value += eol;
|
||||
|
||||
let requestHeaders = headers.request;
|
||||
for (let i = 0; i < requestHeaders.length; i++) {
|
||||
let header = requestHeaders[i];
|
||||
value += header.name + ": " + header.value + eol;
|
||||
}
|
||||
|
||||
Clipboard.set(value, "text");
|
||||
}
|
||||
});
|
||||
|
||||
// Stream converter component definition
|
||||
let service = xpcom.Service({
|
||||
id: components.ID(CLASS_ID),
|
||||
contract: CONTRACT_ID,
|
||||
Component: Converter,
|
||||
register: false,
|
||||
unregister: false
|
||||
});
|
||||
|
||||
function register() {
|
||||
if (!xpcom.isRegistered(service)) {
|
||||
xpcom.register(service);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function unregister() {
|
||||
if (xpcom.isRegistered(service)) {
|
||||
xpcom.unregister(service);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
exports.JsonViewService = {
|
||||
register: register,
|
||||
unregister: unregister
|
||||
};
|
||||
97
devtools/client/jsonview/converter-observer.js
Normal file
97
devtools/client/jsonview/converter-observer.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
// Load devtools module lazily.
|
||||
XPCOMUtils.defineLazyGetter(this, "devtools", function () {
|
||||
const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
return devtools;
|
||||
});
|
||||
|
||||
// Load JsonView services lazily.
|
||||
XPCOMUtils.defineLazyGetter(this, "JsonViewService", function () {
|
||||
const {JsonViewService} = devtools.require("devtools/client/jsonview/converter-child");
|
||||
return JsonViewService;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "JsonViewSniffer", function () {
|
||||
const {JsonViewSniffer} = devtools.require("devtools/client/jsonview/converter-sniffer");
|
||||
return JsonViewSniffer;
|
||||
});
|
||||
|
||||
// Constants
|
||||
const JSON_VIEW_PREF = "devtools.jsonview.enabled";
|
||||
|
||||
/**
|
||||
* Listen for 'devtools.jsonview.enabled' preference changes and
|
||||
* register/unregister the JSON View XPCOM services as appropriate.
|
||||
*/
|
||||
function ConverterObserver() {
|
||||
}
|
||||
|
||||
ConverterObserver.prototype = {
|
||||
initialize: function () {
|
||||
// Only the DevEdition has this feature available by default.
|
||||
// Users need to manually flip 'devtools.jsonview.enabled' preference
|
||||
// to have it available in other distributions.
|
||||
if (this.isEnabled()) {
|
||||
this.register();
|
||||
}
|
||||
|
||||
Services.prefs.addObserver(JSON_VIEW_PREF, this, false);
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "xpcom-shutdown":
|
||||
this.onShutdown();
|
||||
break;
|
||||
case "nsPref:changed":
|
||||
this.onPrefChanged();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onShutdown: function () {
|
||||
Services.prefs.removeObserver(JSON_VIEW_PREF, observer);
|
||||
Services.obs.removeObserver(observer, "xpcom-shutdown");
|
||||
},
|
||||
|
||||
onPrefChanged: function () {
|
||||
if (this.isEnabled()) {
|
||||
this.register();
|
||||
} else {
|
||||
this.unregister();
|
||||
}
|
||||
},
|
||||
|
||||
register: function () {
|
||||
JsonViewSniffer.register();
|
||||
JsonViewService.register();
|
||||
},
|
||||
|
||||
unregister: function () {
|
||||
JsonViewSniffer.unregister();
|
||||
JsonViewService.unregister();
|
||||
},
|
||||
|
||||
isEnabled: function () {
|
||||
return Services.prefs.getBoolPref(JSON_VIEW_PREF);
|
||||
},
|
||||
};
|
||||
|
||||
// Listen to JSON View 'enable' pref and perform dynamic
|
||||
// registration or unregistration of the main application
|
||||
// component.
|
||||
var observer = new ConverterObserver();
|
||||
observer.initialize();
|
||||
106
devtools/client/jsonview/converter-sniffer.js
Normal file
106
devtools/client/jsonview/converter-sniffer.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Cc, Ci, components} = require("chrome");
|
||||
const xpcom = require("sdk/platform/xpcom");
|
||||
const {Unknown} = require("sdk/platform/xpcom");
|
||||
const {Class} = require("sdk/core/heritage");
|
||||
|
||||
const categoryManager = Cc["@mozilla.org/categorymanager;1"]
|
||||
.getService(Ci.nsICategoryManager);
|
||||
|
||||
loader.lazyRequireGetter(this, "NetworkHelper",
|
||||
"devtools/shared/webconsole/network-helper");
|
||||
|
||||
// Constants
|
||||
const JSON_TYPE = "application/json";
|
||||
const CONTRACT_ID = "@mozilla.org/devtools/jsonview-sniffer;1";
|
||||
const CLASS_ID = "{4148c488-dca1-49fc-a621-2a0097a62422}";
|
||||
const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view";
|
||||
const JSON_VIEW_TYPE = "JSON View";
|
||||
const CONTENT_SNIFFER_CATEGORY = "net-content-sniffers";
|
||||
|
||||
/**
|
||||
* This component represents a sniffer (implements nsIContentSniffer
|
||||
* interface) responsible for changing top level 'application/json'
|
||||
* document types to: 'application/vnd.mozilla.json.view'.
|
||||
*
|
||||
* This internal type is consequently rendered by JSON View component
|
||||
* that represents the JSON through a viewer interface.
|
||||
*/
|
||||
var Sniffer = Class({
|
||||
extends: Unknown,
|
||||
|
||||
interfaces: [
|
||||
"nsIContentSniffer",
|
||||
],
|
||||
|
||||
get wrappedJSObject() {
|
||||
return this;
|
||||
},
|
||||
|
||||
getMIMETypeFromContent: function (request, data, length) {
|
||||
// JSON View is enabled only for top level loads only.
|
||||
if (!NetworkHelper.isTopLevelLoad(request)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (request instanceof Ci.nsIChannel) {
|
||||
try {
|
||||
if (request.contentDisposition ==
|
||||
Ci.nsIChannel.DISPOSITION_ATTACHMENT) {
|
||||
return "";
|
||||
}
|
||||
} catch (e) {
|
||||
// Channel doesn't support content dispositions
|
||||
}
|
||||
|
||||
// Check the response content type and if it's application/json
|
||||
// change it to new internal type consumed by JSON View.
|
||||
if (request.contentType == JSON_TYPE) {
|
||||
return JSON_VIEW_MIME_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
var service = xpcom.Service({
|
||||
id: components.ID(CLASS_ID),
|
||||
contract: CONTRACT_ID,
|
||||
Component: Sniffer,
|
||||
register: false,
|
||||
unregister: false
|
||||
});
|
||||
|
||||
function register() {
|
||||
if (!xpcom.isRegistered(service)) {
|
||||
xpcom.register(service);
|
||||
categoryManager.addCategoryEntry(CONTENT_SNIFFER_CATEGORY, JSON_VIEW_TYPE,
|
||||
CONTRACT_ID, false, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function unregister() {
|
||||
if (xpcom.isRegistered(service)) {
|
||||
categoryManager.deleteCategoryEntry(CONTENT_SNIFFER_CATEGORY,
|
||||
JSON_VIEW_TYPE, false);
|
||||
xpcom.unregister(service);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
exports.JsonViewSniffer = {
|
||||
register: register,
|
||||
unregister: unregister
|
||||
};
|
||||
46
devtools/client/jsonview/css/general.css
Normal file
46
devtools/client/jsonview/css/general.css
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/******************************************************************************/
|
||||
/* General */
|
||||
|
||||
body {
|
||||
color: var(--theme-body-color);
|
||||
background-color: var(--theme-body-background);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
#content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: white;
|
||||
border: none;
|
||||
font-family: var(--monospace-font-family);
|
||||
}
|
||||
|
||||
#json,
|
||||
#headers {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Dark Theme */
|
||||
|
||||
body.theme-dark {
|
||||
color: var(--theme-body-color);
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
.theme-dark pre {
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
78
devtools/client/jsonview/css/headers-panel.css
Normal file
78
devtools/client/jsonview/css/headers-panel.css
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/******************************************************************************/
|
||||
/* Headers Panel */
|
||||
|
||||
.headersPanelBox {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.headersPanelBox .netInfoHeadersTable {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.headersPanelBox .netHeadersGroup {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.headersPanelBox td {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.headersPanelBox .netInfoHeadersGroup {
|
||||
color: var(--theme-body-color-alt);
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
padding-top: 8px;
|
||||
padding-bottom: 4px;
|
||||
font-weight: bold;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.headersPanelBox .netInfoParamValue {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.headersPanelBox .netInfoParamName {
|
||||
padding: 2px 10px 0 0;
|
||||
font-weight: bold;
|
||||
vertical-align: top;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Theme colors have been generated/copied from Network Panel's header view */
|
||||
|
||||
/* Light Theme */
|
||||
.theme-light .netInfoParamName {
|
||||
color: var(--theme-highlight-red);
|
||||
}
|
||||
|
||||
.theme-light .netInfoParamValue {
|
||||
color: var(--theme-highlight-purple);
|
||||
}
|
||||
|
||||
/* Dark Theme */
|
||||
.theme-dark .netInfoParamName {
|
||||
color: var(--theme-highlight-purple);
|
||||
}
|
||||
|
||||
.theme-dark .netInfoParamValue {
|
||||
color: var(--theme-highlight-gray);
|
||||
}
|
||||
|
||||
/* Firebug Theme */
|
||||
.theme-firebug .netInfoHeadersTable {
|
||||
font-family: Lucida Grande, Tahoma, sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.theme-firebug .netInfoParamValue {
|
||||
font-family: var(--monospace-font-family);
|
||||
}
|
||||
16
devtools/client/jsonview/css/json-panel.css
Normal file
16
devtools/client/jsonview/css/json-panel.css
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/******************************************************************************/
|
||||
/* JSON Panel */
|
||||
|
||||
.jsonParseError {
|
||||
font-size: 12px;
|
||||
font-family: Lucida Grande, Tahoma, sans-serif;
|
||||
line-height: 15px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
color: red;
|
||||
}
|
||||
59
devtools/client/jsonview/css/main.css
Normal file
59
devtools/client/jsonview/css/main.css
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
@import "resource://devtools/client/shared/components/reps/reps.css";
|
||||
@import "resource://devtools/client/shared/components/tree/tree-view.css";
|
||||
@import "resource://devtools/client/shared/components/tabs/tabs.css";
|
||||
|
||||
@import "general.css";
|
||||
@import "search-box.css";
|
||||
@import "toolbar.css";
|
||||
@import "json-panel.css";
|
||||
@import "text-panel.css";
|
||||
@import "headers-panel.css";
|
||||
|
||||
/******************************************************************************/
|
||||
/* Panel Content */
|
||||
|
||||
.panelContent {
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* The tree takes the entire horizontal space within the panel content. */
|
||||
.panelContent .treeTable {
|
||||
width: 100%;
|
||||
font-family: var(--monospace-font-family);
|
||||
}
|
||||
|
||||
:root[platform="linux"] .treeTable {
|
||||
font-size: 80%; /* To handle big monospace font */
|
||||
}
|
||||
|
||||
/* Make sure there is a little space between label and value columns. */
|
||||
.panelContent .treeTable .treeLabelCell {
|
||||
padding-right: 17px;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Theme Firebug */
|
||||
|
||||
.theme-firebug .panelContent {
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
/* JSON View is using bigger font-size for the main tabs so,
|
||||
let's overwrite the default value. */
|
||||
.theme-firebug .tabs .tabs-navigation {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Theme Light & Theme Dark*/
|
||||
|
||||
.theme-dark .panelContent,
|
||||
.theme-light .panelContent {
|
||||
height: calc(100% - 27px);
|
||||
}
|
||||
16
devtools/client/jsonview/css/moz.build
Normal file
16
devtools/client/jsonview/css/moz.build
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'general.css',
|
||||
'headers-panel.css',
|
||||
'json-panel.css',
|
||||
'main.css',
|
||||
'search-box.css',
|
||||
'search.svg',
|
||||
'text-panel.css',
|
||||
'toolbar.css'
|
||||
)
|
||||
24
devtools/client/jsonview/css/search-box.css
Normal file
24
devtools/client/jsonview/css/search-box.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/******************************************************************************/
|
||||
/* Search Box */
|
||||
|
||||
.searchBox {
|
||||
height: 18px;
|
||||
font: message-box;
|
||||
background-color: var(--theme-body-background);
|
||||
background-image: url("chrome://devtools/skin/images/filter.svg#filterinput");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px center;
|
||||
border: 1px solid var(--theme-splitter-color);
|
||||
border-radius: 2px;
|
||||
color: var(--theme-content-color1);
|
||||
width: 200px;
|
||||
margin-top: 0;
|
||||
margin-right: 1px;
|
||||
float: right;
|
||||
padding-left: 20px;
|
||||
}
|
||||
22
devtools/client/jsonview/css/search.svg
Normal file
22
devtools/client/jsonview/css/search.svg
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<!-- 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
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="a">
|
||||
<stop offset="0" stop-color="#427dc2"/>
|
||||
<stop offset="1" stop-color="#5e9fce"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b">
|
||||
<stop offset="0" stop-color="#2f5d93"/>
|
||||
<stop offset="1" stop-color="#3a87bd"/>
|
||||
</linearGradient>
|
||||
<filter id="c" width="1.239" height="1.241" x="-.12" y="-.12" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation=".637"/>
|
||||
</filter>
|
||||
<linearGradient id="d" x1="4.094" x2="4.094" y1="13.423" y2="2.743" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient id="e" x1="8.711" x2="8.711" y1="13.58" y2="2.566" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
|
||||
</defs>
|
||||
<path fill="#fff" stroke="#fff" stroke-width="1.5" d="M10.14 1.656c-2.35 0-4.25 1.9-4.25 4.25 0 .752.19 1.45.532 2.063L1.61 12.78l1.562 1.564 4.78-4.78c.64.384 1.387.592 2.19.592 2.35 0 4.25-1.9 4.25-4.25s-1.9-4.25-4.25-4.25zm0 1.532c1.504 0 2.72 1.214 2.72 2.718s-1.216 2.72-2.72 2.72c-1.503 0-2.718-1.216-2.718-2.72 0-1.504 1.215-2.718 2.72-2.718z" stroke-linejoin="round" filter="url(#c)"/>
|
||||
<path fill="url(#d)" stroke="url(#e)" stroke-width=".6" d="M10 2C7.79 2 6 3.79 6 6c0 .828.256 1.612.688 2.25l-4.875 4.875 1.062 1.063L7.75 9.31C8.388 9.745 9.172 10 10 10c2.21 0 4-1.79 4-4s-1.79-4-4-4zm0 1c1.657 0 3 1.343 3 3s-1.343 3-3 3-3-1.343-3-3 1.343-3 3-3z" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
26
devtools/client/jsonview/css/text-panel.css
Normal file
26
devtools/client/jsonview/css/text-panel.css
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/******************************************************************************/
|
||||
/* Text Panel */
|
||||
|
||||
.textPanelBox {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.textPanelBox .data {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.textPanelBox pre {
|
||||
margin: 0;
|
||||
font-family: var(--monospace-font-family);
|
||||
color: var(--theme-content-color1);
|
||||
}
|
||||
|
||||
:root[platform="linux"] .textPanelBox .data {
|
||||
font-size: 80%; /* To handle big monospace font */
|
||||
}
|
||||
92
devtools/client/jsonview/css/toolbar.css
Normal file
92
devtools/client/jsonview/css/toolbar.css
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/******************************************************************************/
|
||||
/* Toolbar */
|
||||
|
||||
.toolbar {
|
||||
line-height: 20px;
|
||||
height: 22px;
|
||||
font: message-box;
|
||||
padding: 4px 0 3px 0;
|
||||
}
|
||||
|
||||
.toolbar .btn {
|
||||
margin-left: 5px;
|
||||
background-color: #E6E6E6;
|
||||
border: 1px solid rgb(204, 204, 204);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.toolbar .btn::-moz-focus-inner {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Firebug Theme */
|
||||
|
||||
.theme-firebug .toolbar {
|
||||
border-bottom: 1px solid rgb(170, 188, 207);
|
||||
background-color: var(--theme-tab-toolbar-background) !important;
|
||||
background-image: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.2));
|
||||
}
|
||||
|
||||
.theme-firebug .toolbar .btn {
|
||||
border-radius: 2px;
|
||||
color: #141414;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.theme-firebug .toolbar .btn:hover {
|
||||
color: #333;
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
|
||||
.theme-firebug .toolbar .btn:active {
|
||||
background-image: none;
|
||||
outline: 0;
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* Light Theme & Dark Theme*/
|
||||
|
||||
.theme-dark .toolbar,
|
||||
.theme-light .toolbar {
|
||||
background-color: var(--theme-toolbar-background);
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
padding: 1px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.theme-dark .toolbar .btn,
|
||||
.theme-light .toolbar .btn {
|
||||
min-height: 18px;
|
||||
color: var(--theme-content-color1);
|
||||
text-shadow: none;
|
||||
margin: 1px 2px 1px 2px;
|
||||
border: none;
|
||||
background-color: rgba(170, 170, 170, .2); /* --toolbar-tab-hover */
|
||||
transition: background 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.theme-dark .toolbar .btn:hover,
|
||||
.theme-light .toolbar .btn:hover {
|
||||
background: rgba(170, 170, 170, .3); /* Splitters */
|
||||
}
|
||||
|
||||
.theme-dark .toolbar .btn:not([disabled]):hover:active,
|
||||
.theme-light .toolbar .btn:not([disabled]):hover:active {
|
||||
background: rgba(170, 170, 170, .4); /* --toolbar-tab-hover-active */
|
||||
}
|
||||
112
devtools/client/jsonview/json-viewer.js
Normal file
112
devtools/client/jsonview/json-viewer.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
define(function (require, exports, module) {
|
||||
const { render } = require("devtools/client/shared/vendor/react-dom");
|
||||
const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
|
||||
const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
|
||||
|
||||
const json = document.getElementById("json");
|
||||
const headers = document.getElementById("headers");
|
||||
|
||||
let jsonData;
|
||||
|
||||
try {
|
||||
jsonData = JSON.parse(json.textContent);
|
||||
} catch (err) {
|
||||
jsonData = err + "";
|
||||
}
|
||||
|
||||
// Application state object.
|
||||
let input = {
|
||||
jsonText: json.textContent,
|
||||
jsonPretty: null,
|
||||
json: jsonData,
|
||||
headers: JSON.parse(headers.textContent),
|
||||
tabActive: 0,
|
||||
prettified: false
|
||||
};
|
||||
|
||||
json.remove();
|
||||
headers.remove();
|
||||
|
||||
/**
|
||||
* Application actions/commands. This list implements all commands
|
||||
* available for the JSON viewer.
|
||||
*/
|
||||
input.actions = {
|
||||
onCopyJson: function () {
|
||||
dispatchEvent("copy", input.prettified ? input.jsonPretty : input.jsonText);
|
||||
},
|
||||
|
||||
onSaveJson: function () {
|
||||
dispatchEvent("save", input.prettified ? input.jsonPretty : input.jsonText);
|
||||
},
|
||||
|
||||
onCopyHeaders: function () {
|
||||
dispatchEvent("copy-headers", input.headers);
|
||||
},
|
||||
|
||||
onSearch: function (value) {
|
||||
theApp.setState({searchFilter: value});
|
||||
},
|
||||
|
||||
onPrettify: function (data) {
|
||||
if (input.prettified) {
|
||||
theApp.setState({jsonText: input.jsonText});
|
||||
} else {
|
||||
if (!input.jsonPretty) {
|
||||
input.jsonPretty = JSON.stringify(jsonData, null, " ");
|
||||
}
|
||||
theApp.setState({jsonText: input.jsonPretty});
|
||||
}
|
||||
|
||||
input.prettified = !input.prettified;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for dispatching an event. It's handled in chrome scope.
|
||||
*
|
||||
* @param {String} type Event detail type
|
||||
* @param {Object} value Event detail value
|
||||
*/
|
||||
function dispatchEvent(type, value) {
|
||||
let data = {
|
||||
detail: {
|
||||
type,
|
||||
value,
|
||||
}
|
||||
};
|
||||
|
||||
let contentMessageEvent = new CustomEvent("contentMessage", data);
|
||||
window.dispatchEvent(contentMessageEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the main application component. It's the main tab bar displayed
|
||||
* at the top of the window. This component also represents ReacJS root.
|
||||
*/
|
||||
let content = document.getElementById("content");
|
||||
let theApp = render(MainTabbedArea(input), content);
|
||||
|
||||
let onResize = event => {
|
||||
window.document.body.style.height = window.innerHeight + "px";
|
||||
window.document.body.style.width = window.innerWidth + "px";
|
||||
};
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
onResize();
|
||||
|
||||
// Send notification event to the window. Can be useful for
|
||||
// tests as well as extensions.
|
||||
let event = new CustomEvent("JSONViewInitialized", {});
|
||||
window.jsonViewInitialized = true;
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
|
||||
9
devtools/client/jsonview/lib/moz.build
Normal file
9
devtools/client/jsonview/lib/moz.build
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'require.js'
|
||||
)
|
||||
2076
devtools/client/jsonview/lib/require.js
Normal file
2076
devtools/client/jsonview/lib/require.js
Normal file
File diff suppressed because it is too large
Load diff
62
devtools/client/jsonview/main.js
Normal file
62
devtools/client/jsonview/main.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals JsonViewUtils*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const Services = require("Services");
|
||||
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "JsonViewUtils", function () {
|
||||
return require("devtools/client/jsonview/utils");
|
||||
});
|
||||
|
||||
/**
|
||||
* Singleton object that represents the JSON View in-content tool.
|
||||
* It has the same lifetime as the browser. Initialization done by
|
||||
* DevTools() object from devtools/client/framework/devtools.js
|
||||
*/
|
||||
var JsonView = {
|
||||
initialize: function () {
|
||||
// Load JSON converter module. This converter is responsible
|
||||
// for handling 'application/json' documents and converting
|
||||
// them into a simple web-app that allows easy inspection
|
||||
// of the JSON data.
|
||||
Services.ppmm.loadProcessScript(
|
||||
"resource://devtools/client/jsonview/converter-observer.js",
|
||||
true);
|
||||
|
||||
this.onSaveListener = this.onSave.bind(this);
|
||||
|
||||
// Register for messages coming from the child process.
|
||||
Services.ppmm.addMessageListener(
|
||||
"devtools:jsonview:save", this.onSaveListener);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
Services.ppmm.removeMessageListener(
|
||||
"devtools:jsonview:save", this.onSaveListener);
|
||||
},
|
||||
|
||||
// Message handlers for events from child processes
|
||||
|
||||
/**
|
||||
* Save JSON to a file needs to be implemented here
|
||||
* in the parent process.
|
||||
*/
|
||||
onSave: function (message) {
|
||||
let value = message.data;
|
||||
let file = JsonViewUtils.getTargetFile();
|
||||
if (file) {
|
||||
JsonViewUtils.saveToFile(file, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
module.exports.JsonView = JsonView;
|
||||
23
devtools/client/jsonview/moz.build
Normal file
23
devtools/client/jsonview/moz.build
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += [
|
||||
'components',
|
||||
'css',
|
||||
'lib'
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'converter-child.js',
|
||||
'converter-observer.js',
|
||||
'converter-sniffer.js',
|
||||
'json-viewer.js',
|
||||
'main.js',
|
||||
'utils.js',
|
||||
'viewer-config.js'
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
6
devtools/client/jsonview/test/.eslintrc.js
Normal file
6
devtools/client/jsonview/test/.eslintrc.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../.eslintrc.mochitests.js"
|
||||
};
|
||||
1
devtools/client/jsonview/test/array_json.json
Normal file
1
devtools/client/jsonview/test/array_json.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[{"name": "jan"},{"name": "honza"},{"name": "odvarko"}]
|
||||
1
devtools/client/jsonview/test/array_json.json^headers^
Normal file
1
devtools/client/jsonview/test/array_json.json^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/json; charset=utf-8
|
||||
28
devtools/client/jsonview/test/browser.ini
Normal file
28
devtools/client/jsonview/test/browser.ini
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
array_json.json
|
||||
array_json.json^headers^
|
||||
doc_frame_script.js
|
||||
head.js
|
||||
invalid_json.json
|
||||
invalid_json.json^headers^
|
||||
simple_json.json
|
||||
simple_json.json^headers^
|
||||
valid_json.json
|
||||
valid_json.json^headers^
|
||||
!/devtools/client/commandline/test/head.js
|
||||
!/devtools/client/framework/test/head.js
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
|
||||
[browser_jsonview_copy_headers.js]
|
||||
subsuite = clipboard
|
||||
[browser_jsonview_copy_json.js]
|
||||
subsuite = clipboard
|
||||
[browser_jsonview_copy_rawdata.js]
|
||||
subsuite = clipboard
|
||||
[browser_jsonview_filter.js]
|
||||
[browser_jsonview_invalid_json.js]
|
||||
[browser_jsonview_valid_json.js]
|
||||
[browser_jsonview_save_json.js]
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_JSON_URL = URL_ROOT + "valid_json.json";
|
||||
|
||||
add_task(function* () {
|
||||
info("Test valid JSON started");
|
||||
|
||||
yield addJsonViewTab(TEST_JSON_URL);
|
||||
|
||||
// Select the RawData tab
|
||||
yield selectJsonViewContentTab("headers");
|
||||
|
||||
// Check displayed headers
|
||||
let count = yield getElementCount(".headersPanelBox .netHeadersGroup");
|
||||
is(count, 2, "There must be two header groups");
|
||||
|
||||
let text = yield getElementText(".headersPanelBox .netInfoHeadersTable");
|
||||
isnot(text, "", "Headers text must not be empty");
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
// Verify JSON copy into the clipboard.
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
".headersPanelBox .toolbar button.copy",
|
||||
{}, browser);
|
||||
}, function validator(value) {
|
||||
return value.indexOf("application/json") > 0;
|
||||
});
|
||||
});
|
||||
31
devtools/client/jsonview/test/browser_jsonview_copy_json.js
Normal file
31
devtools/client/jsonview/test/browser_jsonview_copy_json.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_JSON_URL = URL_ROOT + "simple_json.json";
|
||||
|
||||
add_task(function* () {
|
||||
info("Test copy JSON started");
|
||||
|
||||
yield addJsonViewTab(TEST_JSON_URL);
|
||||
|
||||
let countBefore = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
|
||||
ok(countBefore == 1, "There must be one row");
|
||||
|
||||
let text = yield getElementText(".jsonPanelBox .treeTable .treeRow");
|
||||
is(text, "name\"value\"", "There must be proper JSON displayed");
|
||||
|
||||
// Verify JSON copy into the clipboard.
|
||||
let value = "{\"name\": \"value\"}\n";
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let selector = ".jsonPanelBox .toolbar button.copy";
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
BrowserTestUtils.synthesizeMouseAtCenter(selector, {}, browser);
|
||||
}, function validator(result) {
|
||||
let str = normalizeNewLines(result);
|
||||
return str == value;
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_JSON_URL = URL_ROOT + "simple_json.json";
|
||||
|
||||
let jsonText = "{\"name\": \"value\"}\n";
|
||||
let prettyJson = "{\n \"name\": \"value\"\n}";
|
||||
|
||||
add_task(function* () {
|
||||
info("Test copy raw data started");
|
||||
|
||||
yield addJsonViewTab(TEST_JSON_URL);
|
||||
|
||||
// Select the RawData tab
|
||||
yield selectJsonViewContentTab("rawdata");
|
||||
|
||||
// Check displayed JSON
|
||||
let text = yield getElementText(".textPanelBox .data");
|
||||
is(text, jsonText, "Proper JSON must be displayed in DOM");
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
// Verify JSON copy into the clipboard.
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
".textPanelBox .toolbar button.copy",
|
||||
{}, browser);
|
||||
}, jsonText);
|
||||
|
||||
// Click 'Pretty Print' button
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
".textPanelBox .toolbar button.prettyprint",
|
||||
{}, browser);
|
||||
|
||||
let prettyText = yield getElementText(".textPanelBox .data");
|
||||
prettyText = normalizeNewLines(prettyText);
|
||||
ok(prettyText.startsWith(prettyJson),
|
||||
"Pretty printed JSON must be displayed");
|
||||
|
||||
// Verify JSON copy into the clipboard.
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
".textPanelBox .toolbar button.copy",
|
||||
{}, browser);
|
||||
}, function validator(value) {
|
||||
let str = normalizeNewLines(value);
|
||||
return str == prettyJson;
|
||||
});
|
||||
});
|
||||
28
devtools/client/jsonview/test/browser_jsonview_filter.js
Normal file
28
devtools/client/jsonview/test/browser_jsonview_filter.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_JSON_URL = URL_ROOT + "array_json.json";
|
||||
|
||||
add_task(function* () {
|
||||
info("Test valid JSON started");
|
||||
|
||||
yield addJsonViewTab(TEST_JSON_URL);
|
||||
|
||||
let count = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
|
||||
is(count, 6, "There must be expected number of rows");
|
||||
|
||||
// XXX use proper shortcut to focus the filter box
|
||||
// as soon as bug Bug 1178771 is fixed.
|
||||
yield sendString("h", ".jsonPanelBox .searchBox");
|
||||
|
||||
// The filtering is done asynchronously so, we need to wait.
|
||||
yield waitForFilter();
|
||||
|
||||
let hiddenCount = yield getElementCount(
|
||||
".jsonPanelBox .treeTable .treeRow.hidden");
|
||||
is(hiddenCount, 4, "There must be expected number of hidden rows");
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_JSON_URL = URL_ROOT + "invalid_json.json";
|
||||
|
||||
add_task(function* () {
|
||||
info("Test invalid JSON started");
|
||||
|
||||
yield addJsonViewTab(TEST_JSON_URL);
|
||||
|
||||
let count = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
|
||||
ok(count == 0, "There must be no row");
|
||||
|
||||
let text = yield getElementText(".jsonPanelBox .jsonParseError");
|
||||
ok(text, "There must be an error description");
|
||||
});
|
||||
38
devtools/client/jsonview/test/browser_jsonview_save_json.js
Normal file
38
devtools/client/jsonview/test/browser_jsonview_save_json.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_JSON_URL = URL_ROOT + "valid_json.json";
|
||||
|
||||
let { MockFilePicker } = SpecialPowers;
|
||||
|
||||
MockFilePicker.init(window);
|
||||
MockFilePicker.returnValue = MockFilePicker.returnCancel;
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
MockFilePicker.cleanup();
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
info("Test save JSON started");
|
||||
|
||||
yield addJsonViewTab(TEST_JSON_URL);
|
||||
|
||||
let promise = new Promise((resolve) => {
|
||||
MockFilePicker.showCallback = () => {
|
||||
MockFilePicker.showCallback = null;
|
||||
ok(true, "File picker was opened");
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
yield BrowserTestUtils.synthesizeMouseAtCenter(
|
||||
".jsonPanelBox button.save",
|
||||
{}, browser);
|
||||
|
||||
yield promise;
|
||||
});
|
||||
33
devtools/client/jsonview/test/browser_jsonview_valid_json.js
Normal file
33
devtools/client/jsonview/test/browser_jsonview_valid_json.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_JSON_URL = URL_ROOT + "valid_json.json";
|
||||
|
||||
add_task(function* () {
|
||||
info("Test valid JSON started");
|
||||
|
||||
let tab = yield addJsonViewTab(TEST_JSON_URL);
|
||||
|
||||
ok(tab.linkedBrowser.contentPrincipal.isNullPrincipal, "Should have null principal");
|
||||
|
||||
let countBefore = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
|
||||
ok(countBefore == 3, "There must be three rows");
|
||||
|
||||
let objectCellCount = yield getElementCount(
|
||||
".jsonPanelBox .treeTable .objectCell");
|
||||
ok(objectCellCount == 1, "There must be one object cell");
|
||||
|
||||
let objectCellText = yield getElementText(
|
||||
".jsonPanelBox .treeTable .objectCell");
|
||||
ok(objectCellText == "", "The summary is hidden when object is expanded");
|
||||
|
||||
// Collapsed auto-expanded node.
|
||||
yield clickJsonNode(".jsonPanelBox .treeTable .treeLabel");
|
||||
|
||||
let countAfter = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
|
||||
ok(countAfter == 1, "There must be one row");
|
||||
});
|
||||
98
devtools/client/jsonview/test/doc_frame_script.js
Normal file
98
devtools/client/jsonview/test/doc_frame_script.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* globals Services, sendAsyncMessage, addMessageListener */
|
||||
|
||||
// XXX Some helper API could go to:
|
||||
// testing/mochitest/tests/SimpleTest/AsyncContentUtils.js
|
||||
// (or at least to share test API in devtools)
|
||||
|
||||
// Set up a dummy environment so that EventUtils works. We need to be careful to
|
||||
// pass a window object into each EventUtils method we call rather than having
|
||||
// it rely on the |window| global.
|
||||
let EventUtils = {};
|
||||
EventUtils.window = content;
|
||||
EventUtils.parent = EventUtils.window;
|
||||
EventUtils._EU_Ci = Components.interfaces; // eslint-disable-line
|
||||
EventUtils._EU_Cc = Components.classes; // eslint-disable-line
|
||||
EventUtils.navigator = content.navigator;
|
||||
EventUtils.KeyboardEvent = content.KeyboardEvent;
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
|
||||
|
||||
/**
|
||||
* When the JSON View is done rendering it triggers custom event
|
||||
* "JSONViewInitialized", then the Test:TestPageProcessingDone message
|
||||
* will be sent to the parent process for tests to wait for this event
|
||||
* if needed.
|
||||
*/
|
||||
content.addEventListener("JSONViewInitialized", () => {
|
||||
sendAsyncMessage("Test:JsonView:JSONViewInitialized");
|
||||
}, false);
|
||||
|
||||
addMessageListener("Test:JsonView:GetElementCount", function (msg) {
|
||||
let {selector} = msg.data;
|
||||
let nodeList = content.document.querySelectorAll(selector);
|
||||
sendAsyncMessage(msg.name, {count: nodeList.length});
|
||||
});
|
||||
|
||||
addMessageListener("Test:JsonView:GetElementText", function (msg) {
|
||||
let {selector} = msg.data;
|
||||
let element = content.document.querySelector(selector);
|
||||
let text = element ? element.textContent : null;
|
||||
sendAsyncMessage(msg.name, {text: text});
|
||||
});
|
||||
|
||||
addMessageListener("Test:JsonView:FocusElement", function (msg) {
|
||||
let {selector} = msg.data;
|
||||
let element = content.document.querySelector(selector);
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
sendAsyncMessage(msg.name);
|
||||
});
|
||||
|
||||
addMessageListener("Test:JsonView:SendString", function (msg) {
|
||||
let {selector, str} = msg.data;
|
||||
if (selector) {
|
||||
let element = content.document.querySelector(selector);
|
||||
if (element) {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.sendString(str, content);
|
||||
|
||||
sendAsyncMessage(msg.name);
|
||||
});
|
||||
|
||||
addMessageListener("Test:JsonView:WaitForFilter", function (msg) {
|
||||
let firstRow = content.document.querySelector(
|
||||
".jsonPanelBox .treeTable .treeRow");
|
||||
|
||||
// Check if the filter is already set.
|
||||
if (firstRow.classList.contains("hidden")) {
|
||||
sendAsyncMessage(msg.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait till the first row has 'hidden' class set.
|
||||
let observer = new content.MutationObserver(function (mutations) {
|
||||
for (let i = 0; i < mutations.length; i++) {
|
||||
let mutation = mutations[i];
|
||||
if (mutation.attributeName == "class") {
|
||||
if (firstRow.classList.contains("hidden")) {
|
||||
observer.disconnect();
|
||||
sendAsyncMessage(msg.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(firstRow, { attributes: true });
|
||||
});
|
||||
145
devtools/client/jsonview/test/head.js
Normal file
145
devtools/client/jsonview/test/head.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
|
||||
/* import-globals-from ../../framework/test/shared-head.js */
|
||||
/* import-globals-from ../../framework/test/head.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
// shared-head.js handles imports, constants, and utility functions
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/framework/test/head.js", this);
|
||||
|
||||
const JSON_VIEW_PREF = "devtools.jsonview.enabled";
|
||||
|
||||
// Enable JSON View for the test
|
||||
Services.prefs.setBoolPref(JSON_VIEW_PREF, true);
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(JSON_VIEW_PREF);
|
||||
});
|
||||
|
||||
// XXX move some API into devtools/framework/test/shared-head.js
|
||||
|
||||
/**
|
||||
* Add a new test tab in the browser and load the given url.
|
||||
* @param {String} url The url to be loaded in the new tab
|
||||
* @return a promise that resolves to the tab object when the url is loaded
|
||||
*/
|
||||
function addJsonViewTab(url) {
|
||||
info("Adding a new JSON tab with URL: '" + url + "'");
|
||||
|
||||
let deferred = promise.defer();
|
||||
addTab(url).then(tab => {
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
// Load devtools/shared/frame-script-utils.js
|
||||
getFrameScript();
|
||||
|
||||
// Load frame script with helpers for JSON View tests.
|
||||
let rootDir = getRootDirectory(gTestPath);
|
||||
let frameScriptUrl = rootDir + "doc_frame_script.js";
|
||||
browser.messageManager.loadFrameScript(frameScriptUrl, false);
|
||||
|
||||
// Resolve if the JSONView is fully loaded or wait
|
||||
// for an initialization event.
|
||||
if (content.window.wrappedJSObject.jsonViewInitialized) {
|
||||
deferred.resolve(tab);
|
||||
} else {
|
||||
waitForContentMessage("Test:JsonView:JSONViewInitialized").then(() => {
|
||||
deferred.resolve(tab);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expanding a node in the JSON tree
|
||||
*/
|
||||
function clickJsonNode(selector) {
|
||||
info("Expanding node: '" + selector + "'");
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
return BrowserTestUtils.synthesizeMouseAtCenter(selector, {}, browser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select JSON View tab (in the content).
|
||||
*/
|
||||
function selectJsonViewContentTab(name) {
|
||||
info("Selecting tab: '" + name + "'");
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let selector = ".tabs-menu .tabs-menu-item." + name + " a";
|
||||
return BrowserTestUtils.synthesizeMouseAtCenter(selector, {}, browser);
|
||||
}
|
||||
|
||||
function getElementCount(selector) {
|
||||
info("Get element count: '" + selector + "'");
|
||||
|
||||
let data = {
|
||||
selector: selector
|
||||
};
|
||||
|
||||
return executeInContent("Test:JsonView:GetElementCount", data)
|
||||
.then(result => {
|
||||
return result.count;
|
||||
});
|
||||
}
|
||||
|
||||
function getElementText(selector) {
|
||||
info("Get element text: '" + selector + "'");
|
||||
|
||||
let data = {
|
||||
selector: selector
|
||||
};
|
||||
|
||||
return executeInContent("Test:JsonView:GetElementText", data)
|
||||
.then(result => {
|
||||
return result.text;
|
||||
});
|
||||
}
|
||||
|
||||
function focusElement(selector) {
|
||||
info("Focus element: '" + selector + "'");
|
||||
|
||||
let data = {
|
||||
selector: selector
|
||||
};
|
||||
|
||||
return executeInContent("Test:JsonView:FocusElement", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the string aStr to the focused element.
|
||||
*
|
||||
* For now this method only works for ASCII characters and emulates the shift
|
||||
* key state on US keyboard layout.
|
||||
*/
|
||||
function sendString(str, selector) {
|
||||
info("Send string: '" + str + "'");
|
||||
|
||||
let data = {
|
||||
selector: selector,
|
||||
str: str
|
||||
};
|
||||
|
||||
return executeInContent("Test:JsonView:SendString", data);
|
||||
}
|
||||
|
||||
function waitForTime(delay) {
|
||||
let deferred = promise.defer();
|
||||
setTimeout(deferred.resolve, delay);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function waitForFilter() {
|
||||
return executeInContent("Test:JsonView:WaitForFilter");
|
||||
}
|
||||
|
||||
function normalizeNewLines(value) {
|
||||
return value.replace("(\r\n|\n)", "\n");
|
||||
}
|
||||
1
devtools/client/jsonview/test/invalid_json.json
Normal file
1
devtools/client/jsonview/test/invalid_json.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{,}
|
||||
1
devtools/client/jsonview/test/invalid_json.json^headers^
Normal file
1
devtools/client/jsonview/test/invalid_json.json^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/json; charset=utf-8
|
||||
1
devtools/client/jsonview/test/simple_json.json
Normal file
1
devtools/client/jsonview/test/simple_json.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"name": "value"}
|
||||
1
devtools/client/jsonview/test/simple_json.json^headers^
Normal file
1
devtools/client/jsonview/test/simple_json.json^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/json; charset=utf-8
|
||||
6
devtools/client/jsonview/test/valid_json.json
Normal file
6
devtools/client/jsonview/test/valid_json.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"family": {
|
||||
"father": "John Doe",
|
||||
"mother": "Alice Doe"
|
||||
}
|
||||
}
|
||||
1
devtools/client/jsonview/test/valid_json.json^headers^
Normal file
1
devtools/client/jsonview/test/valid_json.json^headers^
Normal file
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/json; charset=utf-8
|
||||
101
devtools/client/jsonview/utils.js
Normal file
101
devtools/client/jsonview/utils.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Cu, Cc, Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
|
||||
|
||||
const OPEN_FLAGS = {
|
||||
RDONLY: parseInt("0x01", 16),
|
||||
WRONLY: parseInt("0x02", 16),
|
||||
CREATE_FILE: parseInt("0x08", 16),
|
||||
APPEND: parseInt("0x10", 16),
|
||||
TRUNCATE: parseInt("0x20", 16),
|
||||
EXCL: parseInt("0x80", 16)
|
||||
};
|
||||
|
||||
/**
|
||||
* Open File Save As dialog and let the user to pick proper file location.
|
||||
*/
|
||||
exports.getTargetFile = function () {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
|
||||
let win = getMostRecentBrowserWindow();
|
||||
fp.init(win, null, Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilter("JSON Files", "*.json; *.jsonp;");
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterText);
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterAll);
|
||||
fp.filterIndex = 0;
|
||||
|
||||
let rv = fp.show();
|
||||
if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) {
|
||||
return fp.file;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Save JSON to a file
|
||||
*/
|
||||
exports.saveToFile = function (file, jsonString) {
|
||||
let foStream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
|
||||
// write, create, truncate
|
||||
let openFlags = OPEN_FLAGS.WRONLY | OPEN_FLAGS.CREATE_FILE |
|
||||
OPEN_FLAGS.TRUNCATE;
|
||||
|
||||
let permFlags = parseInt("0666", 8);
|
||||
foStream.init(file, openFlags, permFlags, 0);
|
||||
|
||||
let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Ci.nsIConverterOutputStream);
|
||||
|
||||
converter.init(foStream, "UTF-8", 0, 0);
|
||||
|
||||
// The entire jsonString can be huge so, write the data in chunks.
|
||||
let chunkLength = 1024 * 1204;
|
||||
for (let i = 0; i <= jsonString.length; i++) {
|
||||
let data = jsonString.substr(i, chunkLength + 1);
|
||||
if (data) {
|
||||
converter.writeString(data);
|
||||
}
|
||||
i = i + chunkLength;
|
||||
}
|
||||
|
||||
// this closes foStream
|
||||
converter.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current theme from preferences.
|
||||
*/
|
||||
exports.getCurrentTheme = function () {
|
||||
return Services.prefs.getCharPref("devtools.theme");
|
||||
};
|
||||
|
||||
/**
|
||||
* Export given object into the target window scope.
|
||||
*/
|
||||
exports.exportIntoContentScope = function (win, obj, defineAs) {
|
||||
let clone = Cu.createObjectIn(win, {
|
||||
defineAs: defineAs
|
||||
});
|
||||
|
||||
let props = Object.getOwnPropertyNames(obj);
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
let propName = props[i];
|
||||
let propValue = obj[propName];
|
||||
if (typeof propValue == "function") {
|
||||
Cu.exportFunction(propValue, clone, {
|
||||
defineAs: propName
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
39
devtools/client/jsonview/viewer-config.js
Normal file
39
devtools/client/jsonview/viewer-config.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* global requirejs */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* RequireJS configuration for JSON Viewer.
|
||||
*
|
||||
* ReactJS library is shared among DevTools. Both, the minified (production)
|
||||
* version and developer versions of the library are available.
|
||||
*
|
||||
* In order to use the developer version you need to specify the following
|
||||
* in your .mozconfig (see also bug 1181646):
|
||||
* ac_add_options --enable-debug-js-modules
|
||||
*
|
||||
* The path mapping uses paths fallback (a feature supported by RequireJS)
|
||||
* See also: http://requirejs.org/docs/api.html#pathsfallbacks
|
||||
*
|
||||
* React module ID is using exactly the same (relative) path as the rest
|
||||
* of the code base, so it's consistent and modules can be easily reused.
|
||||
*/
|
||||
require.config({
|
||||
baseUrl: ".",
|
||||
paths: {
|
||||
"devtools/client/shared": "resource://devtools/client/shared",
|
||||
"devtools/shared": "resource://devtools/shared",
|
||||
"devtools/client/shared/vendor/react": [
|
||||
"resource://devtools/client/shared/vendor/react-dev",
|
||||
"resource://devtools/client/shared/vendor/react"
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
// Load the main panel module
|
||||
requirejs(["json-viewer"]);
|
||||
Loading…
Add table
Add a link
Reference in a new issue