Bug 1880488 [wpt PR 44609] - Update wpt metadata, a=testonly
[gecko.git] / devtools / client / jsonview / json-viewer.js
blob131307302e0f041d708051bd7db7e6dfc9d291ed
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 define(function (require, exports, module) {
8   const { render } = require("devtools/client/shared/vendor/react-dom");
9   const { createFactories } = require("devtools/client/shared/react-utils");
10   const { MainTabbedArea } = createFactories(
11     require("devtools/client/jsonview/components/MainTabbedArea")
12   );
13   const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
15   const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
16   const AUTO_EXPAND_MAX_LEVEL = 7;
17   const TABS = {
18     JSON: 0,
19     RAW_DATA: 1,
20     HEADERS: 2,
21   };
23   let prettyURL;
24   let theApp;
26   // Application state object.
27   const input = {
28     jsonText: JSONView.json,
29     jsonPretty: null,
30     headers: JSONView.headers,
31     activeTab: 0,
32     prettified: false,
33     expandedNodes: new Set(),
34   };
36   /**
37    * Application actions/commands. This list implements all commands
38    * available for the JSON viewer.
39    */
40   input.actions = {
41     onCopyJson() {
42       const text = input.prettified ? input.jsonPretty : input.jsonText;
43       copyString(text.textContent);
44     },
46     onSaveJson() {
47       if (input.prettified && !prettyURL) {
48         prettyURL = URL.createObjectURL(
49           new window.Blob([input.jsonPretty.textContent])
50         );
51       }
52       dispatchEvent("save", input.prettified ? prettyURL : null);
53     },
55     onCopyHeaders() {
56       let value = "";
57       const isWinNT =
58         document.documentElement.getAttribute("platform") === "win";
59       const eol = isWinNT ? "\r\n" : "\n";
61       const responseHeaders = input.headers.response;
62       for (let i = 0; i < responseHeaders.length; i++) {
63         const header = responseHeaders[i];
64         value += header.name + ": " + header.value + eol;
65       }
67       value += eol;
69       const requestHeaders = input.headers.request;
70       for (let i = 0; i < requestHeaders.length; i++) {
71         const header = requestHeaders[i];
72         value += header.name + ": " + header.value + eol;
73       }
75       copyString(value);
76     },
78     onSearch(value) {
79       theApp.setState({ searchFilter: value });
80     },
82     onPrettify(data) {
83       if (input.json instanceof Error) {
84         // Cannot prettify invalid JSON
85         return;
86       }
87       if (input.prettified) {
88         theApp.setState({ jsonText: input.jsonText });
89       } else {
90         if (!input.jsonPretty) {
91           input.jsonPretty = new Text(JSON.stringify(input.json, null, "  "));
92         }
93         theApp.setState({ jsonText: input.jsonPretty });
94       }
96       input.prettified = !input.prettified;
97     },
99     onCollapse(data) {
100       input.expandedNodes.clear();
101       theApp.forceUpdate();
102     },
104     onExpand(data) {
105       input.expandedNodes = TreeViewClass.getExpandedNodes(input.json);
106       theApp.setState({ expandedNodes: input.expandedNodes });
107     },
108   };
110   /**
111    * Helper for copying a string to the clipboard.
112    *
113    * @param {String} string The text to be copied.
114    */
115   function copyString(string) {
116     document.addEventListener(
117       "copy",
118       event => {
119         event.clipboardData.setData("text/plain", string);
120         event.preventDefault();
121       },
122       { once: true }
123     );
125     document.execCommand("copy", false, null);
126   }
128   /**
129    * Helper for dispatching an event. It's handled in chrome scope.
130    *
131    * @param {String} type Event detail type
132    * @param {Object} value Event detail value
133    */
134   function dispatchEvent(type, value) {
135     const data = {
136       detail: {
137         type,
138         value,
139       },
140     };
142     const contentMessageEvent = new CustomEvent("contentMessage", data);
143     window.dispatchEvent(contentMessageEvent);
144   }
146   /**
147    * Render the main application component. It's the main tab bar displayed
148    * at the top of the window. This component also represents ReacJS root.
149    */
150   const content = document.getElementById("content");
151   const promise = (async function parseJSON() {
152     if (document.readyState == "loading") {
153       // If the JSON has not been loaded yet, render the Raw Data tab first.
154       input.json = {};
155       input.activeTab = TABS.RAW_DATA;
156       return new Promise(resolve => {
157         document.addEventListener("DOMContentLoaded", resolve, { once: true });
158       })
159         .then(parseJSON)
160         .then(async () => {
161           // Now update the state and switch to the JSON tab.
162           await appIsReady;
163           theApp.setState({
164             activeTab: TABS.JSON,
165             json: input.json,
166             expandedNodes: input.expandedNodes,
167           });
168         });
169     }
171     // If the JSON has been loaded, parse it immediately before loading the app.
172     const jsonString = input.jsonText.textContent;
173     try {
174       input.json = JSON.parse(jsonString);
175     } catch (err) {
176       input.json = err;
177       // Display the raw data tab for invalid json
178       input.activeTab = TABS.RAW_DATA;
179     }
181     // Expand the document by default if its size isn't bigger than 100KB.
182     if (
183       !(input.json instanceof Error) &&
184       jsonString.length <= AUTO_EXPAND_MAX_SIZE
185     ) {
186       input.expandedNodes = TreeViewClass.getExpandedNodes(input.json, {
187         maxLevel: AUTO_EXPAND_MAX_LEVEL,
188       });
189     }
190     return undefined;
191   })();
193   const appIsReady = new Promise(resolve => {
194     render(MainTabbedArea(input), content, function () {
195       theApp = this;
196       resolve();
198       // Send readyState change notification event to the window. Can be useful for
199       // tests as well as extensions.
200       JSONView.readyState = "interactive";
201       window.dispatchEvent(new CustomEvent("AppReadyStateChange"));
203       promise.then(() => {
204         // Another readyState change notification event.
205         JSONView.readyState = "complete";
206         window.dispatchEvent(new CustomEvent("AppReadyStateChange"));
207       });
208     });
209   });