Commit 0a5174ad authored by Oriol's avatar Oriol
Browse files

Bug 1368899 - Refactor the JSON Viewer stream converter to avoid quirks mode. r=Honza

parent 6b1641fe
Loading
Loading
Loading
Loading
+142 −119
Original line number Diff line number Diff line
@@ -51,8 +51,7 @@ Converter.prototype = {
   * 2. onStartRequest fires, initializes stuff, modifies the listener
   *    to match our output type
   * 3. onDataAvailable spits it back to the listener
   * 4. onStopRequest spits it back to the listener and initializes
        the JSON Viewer
   * 4. onStopRequest spits it back to the listener
   * 5. convert does nothing, it's just the synchronous version
   *    of asyncConvertData
   */
@@ -69,11 +68,51 @@ Converter.prototype = {
  },

  onStartRequest: function (request, context) {
    this.channel = request;
    // Set the content type to HTML in order to parse the doctype, styles
    // and scripts, but later a <plaintext> element will switch the tokenizer
    // to the plaintext state in order to parse the JSON.
    request.QueryInterface(Ci.nsIChannel);
    request.contentType = "text/html";

    // JSON enforces UTF-8 charset (see bug 741776).
    request.contentCharset = "UTF-8";

    // Changing the content type breaks saving functionality. Fix it.
    fixSave(request);

    // 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.
    request.loadInfo.resetPrincipalToInheritToNullPrincipal();

    // Start the request.
    this.listener.onStartRequest(request, context);

    // Initialize stuff.
    let win = NetworkHelper.getWindowForRequest(request);
    exportData(win, request);
    win.addEventListener("DOMContentLoaded", event => {
      win.addEventListener("contentMessage", onContentMessage, false, true);
    }, {once: true});

    // Insert the initial HTML code.
    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                      .createInstance(Ci.nsIScriptableUnicodeConverter);
    converter.charset = "UTF-8";
    let stream = converter.convertToInputStream(initialHTML(win.document));
    this.listener.onDataAvailable(request, context, stream, 0, stream.available());
  },

  onStopRequest: function (request, context, statusCode) {
    this.listener.onStopRequest(request, context, statusCode);
    this.listener = null;
  }
};

    // Let "save as" save the original JSON, not the viewer.
// Lets "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
function fixSave(request) {
  let originalType;
  if (request instanceof Ci.nsIHttpChannel) {
    try {
@@ -95,22 +134,10 @@ Converter.prototype = {
  }
  request.QueryInterface(Ci.nsIWritablePropertyBag);
  request.setProperty("contentType", originalType);
}

    // 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";

    // 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.
    request.loadInfo.resetPrincipalToInheritToNullPrincipal();

    this.listener.onStartRequest(request, context);
  },

  onStopRequest: function (request, context, statusCode) {
// Exports variables that will be accessed by the non-privileged scripts.
function exportData(win, request) {
  let Locale = {
    $STR: key => {
      try {
@@ -121,6 +148,7 @@ Converter.prototype = {
      }
    }
  };
  JsonViewUtils.exportIntoContentScope(win, Locale, "Locale");

  let headers = {
    response: [],
@@ -140,26 +168,68 @@ Converter.prototype = {
      }
    });
  }

    let win = NetworkHelper.getWindowForRequest(request);
    JsonViewUtils.exportIntoContentScope(win, Locale, "Locale");
  JsonViewUtils.exportIntoContentScope(win, headers, "headers");
}

    win.addEventListener("DOMContentLoaded", event => {
      win.addEventListener("contentMessage",
        onContentMessage.bind(this), false, true);
      loadJsonViewer(win.document);
    }, {once: true});
// Serializes a qualifiedName and an optional set of attributes into an HTML
// start tag. Be aware qualifiedName and attribute names are not validated.
// Attribute values are escaped with escapingString algorithm in attribute mode
// (https://html.spec.whatwg.org/multipage/syntax.html#escapingString).
function startTag(qualifiedName, attributes = {}) {
  return Object.entries(attributes).reduce(function (prev, [attr, value]) {
    return prev + " " + attr + "=\"" +
      value.replace(/&/g, "&amp;")
           .replace(/\u00a0/g, "&nbsp;")
           .replace(/"/g, "&quot;") +
      "\"";
  }, "<" + qualifiedName) + ">";
}

    this.listener.onStopRequest(this.channel, context, statusCode);
    this.listener = null;
// Builds an HTML string that will be used to load stylesheets and scripts,
// and switch the parser to plaintext state.
function initialHTML(doc) {
  let os;
  let platform = Services.appinfo.OS;
  if (platform.startsWith("WINNT")) {
    os = "win";
  } else if (platform.startsWith("Darwin")) {
    os = "mac";
  } else {
    os = "linux";
  }

  let base = doc.createElement("base");
  base.href = "resource://devtools/client/jsonview/";

  let style = doc.createElement("link");
  style.rel = "stylesheet";
  style.type = "text/css";
  style.href = "css/main.css";

  let script = doc.createElement("script");
  script.src = "lib/require.js";
  script.dataset.main = "viewer-config";
  script.defer = true;

  let head = doc.createElement("head");
  head.append(base, style, script);

  return "<!DOCTYPE html>\n" +
    startTag("html", {
      "platform": os,
      "class": "theme-" + JsonViewUtils.getCurrentTheme(),
      "dir": Services.locale.isAppLocaleRTL ? "rtl" : "ltr"
    }) +
    head.outerHTML +
    startTag("body") +
    startTag("div", {"id": "content"}) +
    startTag("plaintext", {"id": "json"});
}
};

// Chrome <-> Content communication
function onContentMessage(e) {
  // Do not handle events from different documents.
  let win = NetworkHelper.getWindowForRequest(this.channel);
  let win = this;
  if (win != e.target) {
    return;
  }
@@ -183,53 +253,6 @@ function onContentMessage(e) {
  }
}

// Loads the JSON Viewer into a text/plain document
function loadJsonViewer(doc) {
  function addStyleSheet(url) {
    let link = doc.createElement("link");
    link.rel = "stylesheet";
    link.type = "text/css";
    link.href = url;
    doc.head.appendChild(link);
  }

  let os;
  let platform = Services.appinfo.OS;
  if (platform.startsWith("WINNT")) {
    os = "win";
  } else if (platform.startsWith("Darwin")) {
    os = "mac";
  } else {
    os = "linux";
  }

  doc.documentElement.setAttribute("platform", os);
  doc.documentElement.dataset.contentType = doc.contentType;
  doc.documentElement.classList.add("theme-" + JsonViewUtils.getCurrentTheme());
  doc.documentElement.dir = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";

  let base = doc.createElement("base");
  base.href = "resource://devtools/client/jsonview/";
  doc.head.appendChild(base);

  addStyleSheet("../themes/variables.css");
  addStyleSheet("../themes/common.css");
  addStyleSheet("../themes/toolbars.css");
  addStyleSheet("css/main.css");

  let json = doc.querySelector("pre");
  json.id = "json";
  let content = doc.createElement("div");
  content.id = "content";
  content.appendChild(json);
  doc.body.appendChild(content);

  let script = doc.createElement("script");
  script.src = "lib/require.js";
  script.dataset.main = "viewer-config";
  doc.body.appendChild(script);
}

function copyHeaders(win, headers) {
  let value = "";
  let eol = (Services.appinfo.OS !== "WINNT") ? "\n" : "\r\n";
+1 −7
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ pre {

#json {
  margin: 8px;
  white-space: pre-wrap;
}

/******************************************************************************/
@@ -46,10 +47,3 @@ body.theme-dark {
.theme-dark pre {
  background-color: var(--theme-body-background);
}

/******************************************************************************/
/* Fixes for quirks mode */

table {
  font: inherit;
}
+4 −2
Original line number Diff line number Diff line
@@ -3,7 +3,10 @@
 * 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/themes/variables.css";
@import "resource://devtools/client/themes/common.css";
@import "resource://devtools/client/themes/toolbars.css";

@import "resource://devtools/client/shared/components/tree/tree-view.css";
@import "resource://devtools/client/shared/components/tabs/tabs.css";

@@ -53,4 +56,3 @@
.theme-firebug .tabs .tabs-navigation {
  font-size: 14px;
}