/* -*- 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, ">") : ""; }, 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 "\n" + "" + "