From 23e68227a2e3f3946fa4fd5589f338e6b36a6e56 Mon Sep 17 00:00:00 2001 From: yami <34216515+kn-yami@users.noreply.github.com> Date: Tue, 30 Jul 2019 15:27:59 +0200 Subject: Issue #1138 - Part 1: refactor the JSON Viewer stream converter Mozilla Bug 1367894 --- devtools/client/jsonview/converter-child.js | 332 ++++++++++++---------------- devtools/client/jsonview/css/general.css | 9 +- devtools/client/jsonview/json-viewer.js | 4 +- devtools/client/jsonview/utils.js | 2 + 4 files changed, 153 insertions(+), 194 deletions(-) diff --git a/devtools/client/jsonview/converter-child.js b/devtools/client/jsonview/converter-child.js index 61aa0c9a3..cf93161af 100644 --- a/devtools/client/jsonview/converter-child.js +++ b/devtools/client/jsonview/converter-child.js @@ -23,10 +23,6 @@ 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=*/*"; @@ -61,9 +57,9 @@ let Converter = Class({ * 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 + * 3. onDataAvailable spits it back to the listener + * 4. onStopRequest spits it back to the listener and initializes + * the JSON Viewer * 5. convert does nothing, it's just the synchronous version * of asyncConvertData */ @@ -76,60 +72,52 @@ let Converter = Class({ }, 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; - } + this.listener.onDataAvailable(...arguments); }, onStartRequest: function (request, context) { - this.data = ""; - this.uri = request.QueryInterface(Ci.nsIChannel).URI.spec; + this.channel = request; + + // Let "save as" save the original JSON, not the viewer. + // To save with the proper extension we need the original content type, + // which has been replaced by application/vnd.mozilla.json.view + let originalType; + if (request instanceof Ci.nsIHttpChannel) { + try { + let header = request.getResponseHeader("Content-Type"); + originalType = header.split(";")[0]; + } catch (err) { + // Handled below + } + } else { + let uri = request.QueryInterface(Ci.nsIChannel).URI.spec; + let match = uri.match(/^data:(.*?)[,;]/); + if (match) { + originalType = match[1]; + } + } + const JSON_TYPES = ["application/json", "application/manifest+json"]; + if (!JSON_TYPES.includes(originalType)) { + originalType = JSON_TYPES[0]; + } + request.QueryInterface(Ci.nsIWritablePropertyBag); + request.setProperty("contentType", originalType); - // 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"; + // Parse source as JSON. This is like text/plain, but enforcing + // UTF-8 charset (see bug 741776). + request.QueryInterface(Ci.nsIChannel); + request.contentType = JSON_TYPES[0]; + this.charset = request.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(); + request.loadInfo.resetPrincipalsToNullPrincipal(); - this.listener.onStartRequest(this.channel, context); + this.listener.onStartRequest(request, 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 { @@ -141,12 +129,10 @@ let Converter = Class({ } }; - JsonViewUtils.exportIntoContentScope(win, Locale, "Locale"); - - Events.once(win, "DOMContentLoaded", event => { - win.addEventListener("contentMessage", - this.onContentMessage.bind(this), false, true); - }); + let headers = { + response: [], + request: [] + }; // The request doesn't have to be always nsIHttpChannel // (e.g. in case of data: URLs) @@ -156,7 +142,6 @@ let Converter = Class({ headers.response.push({name: name, value: value}); } }); - request.visitRequestHeaders({ visitHeader: function (name, value) { headers.request.push({name: name, value: value}); @@ -164,155 +149,124 @@ let Converter = Class({ }); } - 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); + let win = NetworkHelper.getWindowForRequest(request); + JsonViewUtils.exportIntoContentScope(win, Locale, "Locale"); + JsonViewUtils.exportIntoContentScope(win, headers, "headers"); - // Pass the data to the main content listener - this.listener.onDataAvailable(this.channel, context, instream, 0, - instream.available()); + win.addEventListener("DOMContentLoaded", event => { + win.addEventListener("contentMessage", + onContentMessage.bind(this), false, true); + loadJsonViewer(win.document); + }, {once: true}); 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" + - "" + - "