commit a0a1d82648223e9659299307eb4d8a438edf501f
parent 8e3e96c53183d48fc2391176e0a9e0efc2347c7c
Author: Florian Quèze <florian@queze.net>
Date: Fri, 31 Oct 2025 20:40:10 +0000
Bug 1997087 - Make the parsed JSON object accessible from the devtools console as $json.data, r=devtools-reviewers,ochameau,nchevobbe.
Differential Revision: https://phabricator.services.mozilla.com/D270495
Diffstat:
4 files changed, 202 insertions(+), 0 deletions(-)
diff --git a/devtools/client/jsonview/json-viewer.mjs b/devtools/client/jsonview/json-viewer.mjs
@@ -194,6 +194,44 @@ const promise = (async function parseJSON() {
const jsonString = input.jsonText.textContent;
try {
input.json = parseJsonLossless(jsonString);
+
+ // Expose a clean public API for accessing JSON data from the console
+ // This is not tied to internal implementation details
+ window.$json = {
+ // The parsed JSON data
+ get data() {
+ return input.json;
+ },
+ // The original JSON text
+ get text() {
+ return jsonString;
+ },
+ // HTTP headers
+ get headers() {
+ return JSONView.headers;
+ },
+ };
+
+ // Log a welcome message to the console
+ const intro = "font-size: 130%;";
+ const bold = "font-family: monospace; font-weight: bold;";
+ const reset = "";
+ console.log(
+ "%cData available from the console:%c\n\n" +
+ "%c$json.data%c - The parsed JSON object\n" +
+ "%c$json.text%c - The original JSON text\n" +
+ "%c$json.headers%c - HTTP request and response headers\n\n" +
+ "The JSON Viewer is documented here:\n" +
+ "https://firefox-source-docs.mozilla.org/devtools-user/json_viewer/",
+ intro,
+ reset,
+ bold,
+ reset,
+ bold,
+ reset,
+ bold,
+ reset
+ );
} catch (err) {
input.json = err;
// Display the raw data tab for invalid json
diff --git a/devtools/client/jsonview/test/browser.toml b/devtools/client/jsonview/test/browser.toml
@@ -25,6 +25,8 @@ support-files = [
["browser_json_refresh.js"]
+["browser_jsonview_access_data.js"]
+
["browser_jsonview_bug_1380828.js"]
["browser_jsonview_chunked_json.js"]
diff --git a/devtools/client/jsonview/test/browser_jsonview_access_data.js b/devtools/client/jsonview/test/browser_jsonview_access_data.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that $json.data is accessible from the console and contains
+ * the parsed JSON object.
+ */
+
+/**
+ * Helper function to get console messages from the content page.
+ */
+function getConsoleMessages() {
+ const consoleStorageInner = Cc["@mozilla.org/consoleAPI-storage;1"];
+ const storageInner = consoleStorageInner.getService(Ci.nsIConsoleAPIStorage);
+ const events = storageInner.getEvents();
+
+ // Find messages from the JSON viewer
+ return events
+ .filter(e => e.arguments && !!e.arguments.length)
+ .map(e => {
+ // Get the formatted message text
+ return e.arguments.map(arg => String(arg)).join(" ");
+ });
+}
+
+add_task(async function test_console_welcome_message() {
+ // Clear any existing console messages
+ const consoleStorage = Cc["@mozilla.org/consoleAPI-storage;1"];
+ const storage = consoleStorage.getService(Ci.nsIConsoleAPIStorage);
+ storage.clearEvents();
+
+ await addJsonViewTab("data:application/json," + JSON.stringify({ test: 1 }));
+
+ // Get console messages
+ const messages = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ getConsoleMessages
+ );
+
+ // Check that the welcome message was logged
+ const welcomeMessage = messages.find(msg =>
+ msg.includes("Data available from the console")
+ );
+ ok(welcomeMessage, "Console welcome message was logged");
+
+ // Verify the message documents the available fields
+ ok(welcomeMessage.includes("$json.data"), "Message documents $json.data");
+ ok(welcomeMessage.includes("$json.text"), "Message documents $json.text");
+ ok(
+ welcomeMessage.includes("$json.headers"),
+ "Message documents $json.headers"
+ );
+});
+
+/**
+ * Load the JSON viewer for the given json string and get $json.data from it.
+ */
+async function getJSONViewData(json) {
+ await addJsonViewTab("data:application/json," + json);
+
+ return await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return content.wrappedJSObject.$json?.data;
+ });
+}
+
+/**
+ * Helper function to test that $json.data matches the original object.
+ * @param {*} obj - The object to stringify, load, and verify
+ * @param {string} dataDescription - Description of the data being tested
+ */
+async function testJSONViewData(obj, dataDescription) {
+ const json = JSON.stringify(obj);
+ const data = await getJSONViewData(json);
+
+ Assert.deepEqual(data, obj, `$json.data matches ${dataDescription}`);
+}
+
+add_task(async function test_jsonview_data_object() {
+ await testJSONViewData(
+ {
+ name: "test",
+ values: [1, 2, 3],
+ nested: { foo: "bar" },
+ },
+ "original object"
+ );
+});
+
+add_task(async function test_jsonview_data_array() {
+ await testJSONViewData([10, 20, 30, { key: "value" }], "original array");
+});
+
+add_task(async function test_jsonview_data_primitive() {
+ await testJSONViewData(42, "original number");
+});
+
+add_task(async function test_jsonview_data_string() {
+ await testJSONViewData("hello world", "original string");
+});
+
+add_task(async function test_jsonview_data_null() {
+ await testJSONViewData(null, "original null");
+});
+
+add_task(async function test_jsonview_data_invalid_json() {
+ const invalidJson = "{this is not valid json}";
+ const data = await getJSONViewData(invalidJson);
+
+ is(data, undefined, "$json.data is undefined for invalid JSON");
+});
+
+add_task(async function test_jsonview_data_large_array() {
+ const largeArray = Array(1000)
+ .fill(null)
+ .map((_, i) => i * 2);
+
+ await testJSONViewData(largeArray, "large array");
+});
+
+add_task(async function test_jsonview_text() {
+ const obj = { name: "test", value: 42 };
+ const json = JSON.stringify(obj);
+ await addJsonViewTab("data:application/json," + json);
+
+ const text = await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return content.wrappedJSObject.$json?.text;
+ });
+
+ is(text, json, "$json.text matches the original JSON text");
+});
+
+add_task(async function test_jsonview_headers() {
+ await addJsonViewTab(
+ "data:application/json," + JSON.stringify({ foo: "bar" })
+ );
+
+ const headers = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ () => {
+ return content.wrappedJSObject.$json?.headers;
+ }
+ );
+
+ ok(headers, "$json.headers is defined");
+ ok(Array.isArray(headers.response), "$json.headers.response is an array");
+ ok(Array.isArray(headers.request), "$json.headers.request is an array");
+});
diff --git a/devtools/docs/user/json_viewer/index.rst b/devtools/docs/user/json_viewer/index.rst
@@ -9,3 +9,14 @@ The JSON viewer provides a search box that you can use to filter the JSON.
You can also view the raw JSON and pretty-print it.
Finally, if the document was the result of a network request, the viewer displays the request and response headers.
+
+Console API
+===========
+
+The JSON viewer exposes data through a console API, making it easy to access the parsed JSON and other information from the Console:
+
+- **$json.data** - The parsed JSON object
+- **$json.text** - The original JSON text
+- **$json.headers** - HTTP request and response headers
+
+To access these from the console, :ref:`open the Web Console <keyboard-shortcuts-opening-and-closing-tools>`, and type ``$json.data`` to explore the parsed JSON object.