Bug 1302982 - Add Rep for LongString; r=Honza
authorNicolas Chevobbe <chevobbe.nicolas@gmail.com>
Thu, 03 Nov 2016 18:08:16 +0100
changeset 348560 5ffa818f1cb34646e58543bc0443f9da3067c246
parent 348559 ea948a05bfa4c2e9294d6f40eae649095c28074b
child 348561 f82d06a8a42a486322dbb1294d117796e99fa252
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1302982, 10000
milestone52.0a1
Bug 1302982 - Add Rep for LongString; r=Honza Display a longString with what is available on the grip. Straight from the server, a longString grip has a "initial" property, which contains the first 10000 chars of the string. The grip can also contains a "fullText" property that should be set when the user want to display the full text of the string. For example, in the DOM Panel, it would be when the user click on the expand arrow. The fullText needs to be computed with the result of the LongStringClient.substring function, but it's not the responsability of the Reps to do such thing. In rep-utils, we extract the logic that replace non-printable characters in its own function and export it, so we can use it outside the cropString function. Add some test to make sure LongString are displayed as expected in the several modes. MozReview-Commit-ID: 3P9fPBixm2v
devtools/client/shared/components/reps/long-string.js
devtools/client/shared/components/reps/moz.build
devtools/client/shared/components/reps/rep-utils.js
devtools/client/shared/components/reps/rep.js
devtools/client/shared/components/test/mochitest/chrome.ini
devtools/client/shared/components/test/mochitest/test_reps_long-string.html
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/long-string.js
@@ -0,0 +1,71 @@
+/* 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";
+
+// Make this available to both AMD and CJS environments
+define(function (require, exports, module) {
+  // Dependencies
+  const React = require("devtools/client/shared/vendor/react");
+  const { sanitizeString, isGrip } = require("./rep-utils");
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a long string grip.
+   */
+  const LongStringRep = React.createClass({
+    displayName: "LongStringRep",
+
+    propTypes: {
+      useQuotes: React.PropTypes.bool,
+      style: React.PropTypes.object,
+    },
+
+    getDefaultProps: function () {
+      return {
+        useQuotes: true,
+      };
+    },
+
+    render: function () {
+      let {
+        cropLimit,
+        member,
+        object,
+        style,
+        useQuotes
+      } = this.props;
+      let {fullText, initial, length} = object;
+
+      let config = {className: "objectBox objectBox-string"};
+      if (style) {
+        config.style = style;
+      }
+
+      let string = member && member.open
+        ? fullText || initial
+        : initial.substring(0, cropLimit);
+
+      if (string.length < length) {
+        string += "\u2026";
+      }
+      let formattedString = useQuotes ? `"${string}"` : string;
+      return span(config, sanitizeString(formattedString));
+    },
+  });
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+    return object.type === "longString";
+  }
+
+  // Exports from this module
+  exports.LongStringRep = {
+    rep: LongStringRep,
+    supportsObject: supportsObject,
+  };
+});
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -13,16 +13,17 @@ DevToolsModules(
     'document.js',
     'element-node.js',
     'event.js',
     'function.js',
     'grip-array.js',
     'grip-map.js',
     'grip.js',
     'infinity.js',
+    'long-string.js',
     'nan.js',
     'null.js',
     'number.js',
     'object-with-text.js',
     'object-with-url.js',
     'object.js',
     'promise.js',
     'prop-rep.js',
--- a/devtools/client/shared/components/reps/rep-utils.js
+++ b/devtools/client/shared/components/reps/rep-utils.js
@@ -40,25 +40,18 @@ define(function (require, exports, modul
     return escapeNewLines(cropString(text, limit));
   }
 
   function cropString(text, limit, alternativeText) {
     if (!alternativeText) {
       alternativeText = "\u2026";
     }
 
-    // Make sure it's a string.
-    text = text + "";
-
-    // Replace all non-printable characters, except of
-    // (horizontal) tab (HT: \x09) and newline (LF: \x0A, CR: \x0D),
-    // with unicode replacement character (u+fffd).
-    // eslint-disable-next-line no-control-regex
-    let re = new RegExp("[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]", "g");
-    text = text.replace(re, "\ufffd");
+    // Make sure it's a string and sanitize it.
+    text = sanitizeString(text + "");
 
     // Crop the string only if a limit is actually specified.
     if (!limit || limit <= 0) {
       return text;
     }
 
     // Set the limit at least to the length of the alternative text
     // plus one character of the original text.
@@ -71,16 +64,25 @@ define(function (require, exports, modul
     if (text.length > limit) {
       return text.substr(0, Math.ceil(halfLimit)) + alternativeText +
         text.substr(text.length - Math.floor(halfLimit));
     }
 
     return text;
   }
 
+  function sanitizeString(text) {
+    // Replace all non-printable characters, except of
+    // (horizontal) tab (HT: \x09) and newline (LF: \x0A, CR: \x0D),
+    // with unicode replacement character (u+fffd).
+    // eslint-disable-next-line no-control-regex
+    let re = new RegExp("[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]", "g");
+    return text.replace(re, "\ufffd");
+  }
+
   function parseURLParams(url) {
     url = new URL(url);
     return parseURLEncodedText(url.searchParams);
   }
 
   function parseURLEncodedText(text) {
     let params = [];
 
@@ -149,9 +151,10 @@ define(function (require, exports, modul
   exports.createFactories = createFactories;
   exports.isGrip = isGrip;
   exports.cropString = cropString;
   exports.cropMultipleLines = cropMultipleLines;
   exports.parseURLParams = parseURLParams;
   exports.parseURLEncodedText = parseURLEncodedText;
   exports.getFileName = getFileName;
   exports.getURLDisplayString = getURLDisplayString;
+  exports.sanitizeString = sanitizeString;
 });
--- a/devtools/client/shared/components/reps/rep.js
+++ b/devtools/client/shared/components/reps/rep.js
@@ -12,16 +12,17 @@ define(function (require, exports, modul
   const React = require("devtools/client/shared/vendor/react");
 
   const { isGrip } = require("./rep-utils");
 
   // Load all existing rep templates
   const { Undefined } = require("./undefined");
   const { Null } = require("./null");
   const { StringRep } = require("./string");
+  const { LongStringRep } = require("./long-string");
   const { Number } = require("./number");
   const { ArrayRep } = require("./array");
   const { Obj } = require("./object");
   const { SymbolRep } = require("./symbol");
   const { InfinityRep } = require("./infinity");
   const { NaNRep } = require("./nan");
 
   // DOM types (grips)
@@ -50,16 +51,17 @@ define(function (require, exports, modul
     RegExp,
     StyleSheet,
     Event,
     DateTime,
     CommentNode,
     ElementNode,
     TextNode,
     Attribute,
+    LongStringRep,
     Func,
     PromiseRep,
     ArrayRep,
     Document,
     Window,
     ObjectWithText,
     ObjectWithURL,
     GripArray,
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -14,16 +14,17 @@ support-files =
 [test_reps_document.html]
 [test_reps_element-node.html]
 [test_reps_event.html]
 [test_reps_function.html]
 [test_reps_grip.html]
 [test_reps_grip-array.html]
 [test_reps_grip-map.html]
 [test_reps_infinity.html]
+[test_reps_long-string.html]
 [test_reps_nan.html]
 [test_reps_null.html]
 [test_reps_number.html]
 [test_reps_object.html]
 [test_reps_object-with-text.html]
 [test_reps_object-with-url.html]
 [test_reps_promise.html]
 [test_reps_regexp.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_long-string.html
@@ -0,0 +1,125 @@
+<!-- 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/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test LongString rep
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - LongString</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+  let { LongStringRep } = browserRequire("devtools/client/shared/components/reps/long-string");
+
+  try {
+    // Test that correct rep is chosen
+    const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
+    is(renderedRep.type, LongStringRep.rep,
+      `Rep correctly selects ${LongStringRep.rep.displayName}`);
+
+    // Test rendering
+    yield testMultiline();
+    yield testMultilineOpen();
+    yield testFullText();
+    yield testMultilineLimit();
+    yield testUseQuotes();
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function testMultiline() {
+    const stub = getGripStub("testMultiline");
+    const renderedComponent = renderComponent(
+      LongStringRep.rep, { object: stub });
+
+    is(renderedComponent.textContent, `"${stub.initial}…"`,
+      "LongString rep has expected text content for multiline string");
+  }
+
+  function testMultilineLimit() {
+    const renderedComponent = renderComponent(
+      LongStringRep.rep, { object: getGripStub("testMultiline"), cropLimit: 20 });
+
+    is(
+      renderedComponent.textContent,
+      `"a\naaaaaaaaaaaaaaaaaa…"`,
+      "LongString rep has expected text content for multiline string " +
+      "with specified number of characters");
+  }
+
+  function testMultilineOpen() {
+    const stub = getGripStub("testMultiline");
+    const renderedComponent = renderComponent(
+      LongStringRep.rep, { object: stub, member: {open: true}, cropLimit: 20 });
+
+    is(renderedComponent.textContent, `"${stub.initial}…"`,
+      "LongString rep has expected text content for multiline string when open");
+  }
+
+  function testFullText() {
+    const stub = getGripStub("testFullText");
+    const renderedComponentOpen = renderComponent(
+      LongStringRep.rep, { object: stub, member: {open: true}, cropLimit: 20 });
+
+    is(renderedComponentOpen.textContent, `"${stub.fullText}"`,
+      "LongString rep has expected text content when grip has a fullText " +
+      "property and is open");
+
+    const renderedComponentNotOpen = renderComponent(
+      LongStringRep.rep, { object: stub, cropLimit: 20 });
+
+    is(renderedComponentNotOpen.textContent,
+      `"a\naaaaaaaaaaaaaaaaaa…"`,
+      "LongString rep has expected text content when grip has a fullText " +
+      "property and is not open");
+  }
+
+  function testUseQuotes() {
+    const renderedComponent = renderComponent(LongStringRep.rep,
+      { object: getGripStub("testMultiline"), cropLimit: 20, useQuotes: false });
+
+    is(renderedComponent.textContent,
+      "a\naaaaaaaaaaaaaaaaaa…",
+      "LongString rep was expected to omit quotes");
+  }
+
+  function getGripStub(name) {
+    const multilineFullText = "a\n" + Array(20000).fill("a").join("");
+    const fullTextLength = multilineFullText.length;
+    const initialText = multilineFullText.substring(0, 10000);
+
+    switch (name) {
+      case "testMultiline":
+        return {
+          "type": "longString",
+          "initial": initialText,
+          "length": fullTextLength,
+          "actor": "server1.conn1.child1/longString58"
+        };
+      case "testFullText":
+        return {
+          "type": "longString",
+          "fullText": multilineFullText,
+          "initial": initialText,
+          "length": fullTextLength,
+          "actor": "server1.conn1.child1/longString58"
+        };
+    }
+    return null;
+  }
+});
+</script>
+</pre>
+</body>
+</html>