Bug 1502494 - Update Debugger Frontend to v97 r=jdescottes
authorDavid Walsh <dwalsh@mozilla.com>
Fri, 26 Oct 2018 14:56:21 -0500
changeset 499999 328902d1791517de1af37d40909e5e84c5076f86
parent 499998 fff609caefb955bfd31b8c3973405c4f2a1eb884
child 500000 755f66b7a73a7d75f87c7a3a71581a7ec09f9147
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1502494
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1502494 - Update Debugger Frontend to v97 r=jdescottes
devtools/client/debugger/new/README.mozilla
devtools/client/debugger/new/dist/debugger.css
devtools/client/debugger/new/dist/vendors.js
devtools/client/debugger/new/src/client/firefox/commands.js
devtools/client/debugger/new/src/client/firefox/events.js
devtools/client/debugger/new/src/components/Editor/SearchBar.js
devtools/client/debugger/new/src/components/SecondaryPanes/Frames/Frame.js
devtools/client/debugger/new/src/components/SecondaryPanes/Frames/Group.js
devtools/client/debugger/new/src/utils/source-maps.js
devtools/client/debugger/new/src/utils/text.js
devtools/client/debugger/new/src/utils/utils.js
devtools/client/jar.mn
devtools/client/shared/components/reps/reps.js
devtools/client/themes/images/debugger/column-marker.svg
--- a/devtools/client/debugger/new/README.mozilla
+++ b/devtools/client/debugger/new/README.mozilla
@@ -1,13 +1,13 @@
 This is the debugger.html project output.
 See https://github.com/devtools-html/debugger.html
 
-Version 96
+Version 97
 
-Comparison: https://github.com/devtools-html/debugger.html/compare/release-95...release-96
+Comparison: https://github.com/devtools-html/debugger.html/compare/release-96...release-97
 
 Packages:
 - babel-plugin-transform-es2015-modules-commonjs @6.26.2
 - babel-preset-react @6.24.1
 - react @16.4.1
 - react-dom @16.4.1
 - webpack @3.12.0
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -2661,59 +2661,64 @@ menuseparator {
   font-size: 14px;
   color: var(--theme-comment);
 }
 /* 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/>. */
 
 .call-site {
-  background: #f0f9ff;
   position: relative;
+  border-bottom: 2px solid lightgrey;
+  cursor: pointer;
 }
 
 .call-site::before {
   content: "";
-  position: absolute;
-  width: 100%;
-  height: calc(100% - 2px);
-  border-bottom: 2px solid #aed3ef;
+  mask: url("chrome://devtools/skin/images/debugger/column-marker.svg") no-repeat 100% 100%;
+  mask-size: contain;
+  display: inline-block;
+  background-color: var(--blue-55);
+  opacity: 0.5;
+  width: 9px;
+  height: 12px;
 }
 
 .call-site-bp {
   position: relative;
-}
-
-.debug-expression.call-site-bp,
-.call-site-bp {
-  background-color: #fce7e7;
+  border-bottom: 2px solid #aed3ef;
+  cursor: pointer;
 }
 
 .call-site-bp::before {
   content: "";
-  position: absolute;
-  width: 100%;
-  height: calc(100% - 2px);
-  border-bottom: 2px solid red;
+  mask: url("chrome://devtools/skin/images/debugger/column-marker.svg") no-repeat 100% 100%;
+  mask-size: contain;
+  display: inline-block;
+  background-color: var(--blue-55);
+  width: 9px;
+  height: 12px;
 }
 
 .theme-dark .call-site {
-  background-color: #4b5462;
+  border-bottom: none;
+}
+
+.theme-dark .call-site-bp {
+  border-bottom: none;
 }
 
 .theme-dark .call-site::before {
-  border-bottom-color: #5f78a4;
-}
-
-.theme-dark .call-site-bp {
-  background-color: #4b3f3f;
+  background-color: var(--blue-60);
+  opacity: 1;
 }
 
 .theme-dark .call-site-bp::before {
-  border-bottom-color: #dd4d4d;
+  background-color: var(--blue-50);
+  opacity: 0.5;
 }
 /* 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/>. */
 
 .conditional-breakpoint-panel {
   cursor: initial;
   margin: 1em 0;
@@ -3413,16 +3418,22 @@ html[dir="rtl"] .breakpoints-list .break
 /* 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/>. */
 
 .frames ul .frames-group .group,
 .frames ul .frames-group .group .location {
   font-weight: 500;
   cursor: default;
+  /*
+   * direction:rtl is set in Frames.css to overflow the location text from the
+   * start. Here we need to reset it in order to display the framework icon
+   * after the framework name.
+   */
+  direction: ltr;
 }
 
 .frames ul .frames-group.expanded .group,
 .frames ul .frames-group.expanded .group .location {
   color: var(--theme-highlight-blue);
 }
 
 .frames ul .frames-group.expanded .react path {
@@ -3495,58 +3506,63 @@ html[dir="rtl"] .breakpoints-list .break
 
 .frames ul {
   list-style: none;
   margin: 0;
   padding: 0;
 }
 
 .frames ul li {
-  padding: 0 10px 0 21px;
+  padding: 7px 10px 7px 21px;
   overflow: hidden;
   display: flex;
   justify-content: space-between;
+  column-gap: 0.5em;
   flex-direction: row;
   align-items: center;
   margin: 0;
+  max-width: 100%;
+  flex-wrap: wrap;
 }
 
 .frames ul li * {
   -moz-user-select: none;
   user-select: none;
 }
 
 .frames .badge {
   flex-shrink: 0;
   margin-right: 4px;
 }
 
 .frames .location {
   font-weight: normal;
-  display: flex;
-  justify-content: space-between;
-  flex-direction: row;
-  align-items: center;
   margin: 0;
-  flex-shrink: 0;
+  flex-grow: 1;
+  max-width: 100%;
+  overflow: hidden;
+  white-space: nowrap;
+  /* Trick to get the ellipsis at the start of the string */
+  text-overflow: ellipsis;
+  direction: rtl;
+  text-align: right;
 }
 
 .theme-light .frames .location {
   color: var(--theme-comment);
 }
 
 :root.theme-dark .frames .location {
   color: var(--theme-body-color);
   opacity: 0.6;
 }
 
 .frames .title {
   text-overflow: ellipsis;
   overflow: hidden;
-  margin: 7px 0.5em 7px 0;
 }
 
 .frames ul li:hover,
 .frames ul li:focus {
   background-color: var(--theme-toolbar-background-alt);
 }
 
 .theme-dark .frames ul li:focus {
@@ -3582,19 +3598,19 @@ html[dir="rtl"] .breakpoints-list .break
   color: inherit;
 }
 
 .show-more:hover {
   background-color: var(--theme-toolbar-background-hover);
 }
 
 .annotation-logo {
+  display: inline-block;
   width: 12px;
-  margin-left: 3px;
-  line-height: 8px;
+  margin-inline-start: 4px;
 }
 
 :root.theme-dark .annotation-logo svg path {
   fill: var(--theme-highlight-blue);
 }
 /* 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/>. */
--- a/devtools/client/debugger/new/dist/vendors.js
+++ b/devtools/client/debugger/new/dist/vendors.js
@@ -2196,16 +2196,17 @@ const svg = {
   angular: __webpack_require__(247),
   arrow: __webpack_require__(348),
   babel: __webpack_require__(3595),
   backbone: __webpack_require__(997),
   blackBox: __webpack_require__(349),
   breadcrumb: __webpack_require__(3603),
   breakpoint: __webpack_require__(350),
   "column-breakpoint": __webpack_require__(998),
+  "column-marker": __webpack_require__(3801),
   "case-match": __webpack_require__(351),
   choo: __webpack_require__(1290),
   close: __webpack_require__(352),
   coffeescript: __webpack_require__(2250),
   dojo: __webpack_require__(806),
   domain: __webpack_require__(353),
   extension: __webpack_require__(3632),
   file: __webpack_require__(354),
@@ -7523,16 +7524,23 @@ function createStructuredSelector(select
       composition[objectKeys[index]] = value;
       return composition;
     }, {});
   });
 }
 
 /***/ }),
 
+/***/ 3801:
+/***/ (function(module, exports) {
+
+module.exports = "<svg viewBox=\"0 0 9 12\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><g id=\"columnmarkergroup\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\"><polygon id=\"columnmarker\" fill=\"#1B1B1D\" points=\"0 0 4 0 9 6 4 12 0 12\"></polygon></g></svg>"
+
+/***/ }),
+
 /***/ 4:
 /***/ (function(module, exports) {
 
 module.exports = __WEBPACK_EXTERNAL_MODULE_4__;
 
 /***/ }),
 
 /***/ 52:
--- a/devtools/client/debugger/new/src/client/firefox/commands.js
+++ b/devtools/client/debugger/new/src/client/firefox/commands.js
@@ -250,17 +250,19 @@ function debuggeeCommand(script) {
   request.emit("json-reply", {});
 
   debuggerClient._activeRequests.delete(consoleActor);
 
   return Promise.resolve();
 }
 
 function navigate(url) {
-  return tabTarget.activeTab.navigateTo({ url });
+  return tabTarget.activeTab.navigateTo({
+    url
+  });
 }
 
 function reload() {
   return tabTarget.activeTab.reload();
 }
 
 function getProperties(grip) {
   const objClient = threadClient.pauseGrip(grip);
@@ -460,9 +462,9 @@ const clientCommands = {
   disablePrettyPrint,
   fetchSources,
   fetchWorkers,
   sendPacket,
   setPausePoints,
   setSkipPausing
 };
 exports.setupCommands = setupCommands;
-exports.clientCommands = clientCommands;
+exports.clientCommands = clientCommands;
\ No newline at end of file
--- a/devtools/client/debugger/new/src/client/firefox/events.js
+++ b/devtools/client/debugger/new/src/client/firefox/events.js
@@ -36,18 +36,18 @@ function setupEvents(dependencies) {
   });
 
   if (threadClient) {
     Object.keys(clientEvents).forEach(eventName => {
       threadClient.addListener(eventName, clientEvents[eventName]);
     });
 
     if (threadClient._parent) {
-      // Parent may be BrowsingContextTargetFront/WorkerTargetFront and be protocol.js.
-      // Or DebuggerClient and still be old fashion actor.
+      // Parent may be BrowsingContextTargetFront/WorkerTargetFront and
+      // be protocol.js.  Or DebuggerClient and still be old fashion actor.
       if (threadClient._parent.on) {
         threadClient._parent.on("workerListChanged", workerListChanged);
       } else {
         threadClient._parent.addListener("workerListChanged", workerListChanged);
       }
     }
   }
 }
@@ -114,9 +114,9 @@ function workerListChanged() {
 }
 
 const clientEvents = {
   paused,
   resumed,
   newSource
 };
 exports.setupEvents = setupEvents;
-exports.clientEvents = clientEvents;
+exports.clientEvents = clientEvents;
\ No newline at end of file
--- a/devtools/client/debugger/new/src/components/Editor/SearchBar.js
+++ b/devtools/client/debugger/new/src/components/Editor/SearchBar.js
@@ -218,20 +218,26 @@ class SearchBar extends _react.Component
         svgName,
         tooltip
       }) {
         const preppedClass = (0, _classnames2.default)(className, {
           active: modifiers && modifiers.get(modVal)
         });
         return _react2.default.createElement("button", {
           className: preppedClass,
-          onClick: () => {
+          onMouseDown: () => {
             toggleFileSearchModifier(modVal);
             doSearch(query);
           },
+          onKeyDown: e => {
+            if (e.key === "Enter") {
+              toggleFileSearchModifier(modVal);
+              doSearch(query);
+            }
+          },
           title: tooltip
         }, _react2.default.createElement(_Svg2.default, {
           name: svgName
         }));
       }
 
       return _react2.default.createElement("div", {
         className: "search-modifiers"
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/Frame.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/Frame.js
@@ -26,19 +26,21 @@ var _FrameMenu2 = _interopRequireDefault
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* 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/>. */
 function FrameTitle({
   frame,
-  options
+  options = {}
 }) {
-  const displayName = (0, _frames.formatDisplayName)(frame, options);
+  const displayName = (0, _frames.formatDisplayName)(frame, { ...options,
+    maxLength: null
+  });
   return _react2.default.createElement("div", {
     className: "title"
   }, displayName);
 }
 
 function FrameLocation({
   frame,
   displayFullUrl = false
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/Group.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/Group.js
@@ -120,17 +120,19 @@ class Group extends _react.Component {
       toggleFrameworkGrouping: toggleFrameworkGrouping,
       displayFullUrl: displayFullUrl,
       getFrameTitle: getFrameTitle
     })));
   }
 
   renderDescription() {
     const frame = this.props.group[0];
-    const displayName = (0, _frames.formatDisplayName)(frame);
+    const displayName = (0, _frames.formatDisplayName)(frame, {
+      maxLength: null
+    });
     return _react2.default.createElement("li", {
       key: frame.id,
       className: (0, _classnames2.default)("group"),
       onClick: this.toggleFrames,
       tabIndex: 0
     }, _react2.default.createElement("div", {
       className: "d-flex align-items-center min-width-0"
     }, _react2.default.createElement("div", {
--- a/devtools/client/debugger/new/src/utils/source-maps.js
+++ b/devtools/client/debugger/new/src/utils/source-maps.js
@@ -35,14 +35,18 @@ async function getGeneratedLocation(stat
     column: column === 0 ? undefined : column,
     sourceUrl: generatedSource.url
   };
 }
 
 async function getMappedLocation(state, sourceMaps, location) {
   const source = (0, _selectors.getSource)(state, location.sourceId);
 
+  if (!source) {
+    throw new Error("Unknown source for location");
+  }
+
   if ((0, _devtoolsSourceMap.isOriginalId)(location.sourceId)) {
     return getGeneratedLocation(state, source, location, sourceMaps);
   }
 
   return sourceMaps.getOriginalLocation(location, source);
 }
\ No newline at end of file
--- a/devtools/client/debugger/new/src/utils/text.js
+++ b/devtools/client/debugger/new/src/utils/text.js
@@ -55,13 +55,13 @@ function formatKeyShortcut(shortcut) {
  * @static
  */
 
 
 function truncateMiddleText(sourceText, maxLength) {
   let truncatedText = sourceText;
 
   if (sourceText.length > maxLength) {
-    truncatedText = `${sourceText.substring(0, Math.round(maxLength / 2) - 2)}...${sourceText.substring(sourceText.length - Math.round(maxLength / 2 - 1))}`;
+    truncatedText = `${sourceText.substring(0, Math.round(maxLength / 2) - 2)}…${sourceText.substring(sourceText.length - Math.round(maxLength / 2 - 1))}`;
   }
 
   return truncatedText;
 }
\ No newline at end of file
--- a/devtools/client/debugger/new/src/utils/utils.js
+++ b/devtools/client/debugger/new/src/utils/utils.js
@@ -45,17 +45,17 @@ function promisify(context, method, ...a
 /**
  * @memberof utils/utils
  * @static
  */
 
 
 function endTruncateStr(str, size) {
   if (str.length > size) {
-    return `...${str.slice(str.length - size)}`;
+    return `…${str.slice(str.length - size)}`;
   }
 
   return str;
 }
 
 function waitForMs(ms) {
   return new Promise(resolve => setTimeout(resolve, ms));
 }
\ No newline at end of file
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -250,16 +250,17 @@ devtools.jar:
 
     # Debugger
     skin/images/debugger/angular.svg (themes/images/debugger/angular.svg)
     skin/images/debugger/arrow.svg (themes/images/debugger/arrow.svg)
     skin/images/debugger/blackBox.svg (themes/images/debugger/blackBox.svg)
     skin/images/debugger/breakpoint.svg (themes/images/debugger/breakpoint.svg)
     skin/images/debugger/close.svg (themes/images/debugger/close.svg)
     skin/images/debugger/coffeescript.svg (themes/images/debugger/coffeescript.svg)
+    skin/images/debugger/column-marker.svg (themes/images/debugger/column-marker.svg)
     skin/images/debugger/disable-pausing.svg (themes/images/debugger/disable-pausing.svg)
     skin/images/debugger/domain.svg (themes/images/debugger/domain.svg)
     skin/images/debugger/extension.svg (themes/images/debugger/extension.svg)
     skin/images/debugger/file.svg (themes/images/debugger/file.svg)
     skin/images/debugger/folder.svg (themes/images/debugger/folder.svg)
     skin/images/debugger/javascript.svg (themes/images/debugger/javascript.svg)
     skin/images/debugger/pause.svg (themes/images/debugger/pause.svg)
     skin/images/debugger/prettyPrint.svg (themes/images/debugger/prettyPrint.svg)
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -1872,17 +1872,19 @@ const { span } = dom;
 const IGNORED_SOURCE_URLS = ["debugger eval code"];
 
 /**
  * Renders Error objects.
  */
 ErrorRep.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values when supported in Node's version of V8
-  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
+  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  // An optional function that will be used to render the Error stacktrace.
+  renderStacktrace: PropTypes.func
 };
 
 function ErrorRep(props) {
   const object = props.object;
   const preview = object.preview;
 
   let name;
   if (preview && preview.name && preview.kind) {
@@ -1904,17 +1906,18 @@ function ErrorRep(props) {
 
   if (props.mode === MODE.TINY) {
     content.push(name);
   } else {
     content.push(`${name}: "${preview.message}"`);
   }
 
   if (preview.stack && props.mode !== MODE.TINY) {
-    content.push("\n", getStacktraceElements(props, preview));
+    const stacktrace = props.renderStacktrace ? props.renderStacktrace(parseStackString(preview.stack)) : getStacktraceElements(props, preview);
+    content.push("\n", stacktrace);
   }
 
   return span({
     "data-link-actor-id": object.actor,
     className: "objectBox-stackTrace"
   }, content);
 }
 
@@ -1935,18 +1938,75 @@ function ErrorRep(props) {
  *            (<anonymous>:11:1)
  */
 function getStacktraceElements(props, preview) {
   const stack = [];
   if (!preview.stack) {
     return stack;
   }
 
-  const isStacktraceALongString = isLongString(preview.stack);
-  const stackString = isStacktraceALongString ? preview.stack.initial : preview.stack;
+  parseStackString(preview.stack).forEach((frame, index, frames) => {
+    let onLocationClick;
+    const {
+      filename,
+      lineNumber,
+      columnNumber,
+      functionName,
+      location
+    } = frame;
+
+    if (props.onViewSourceInDebugger && !IGNORED_SOURCE_URLS.includes(filename)) {
+      onLocationClick = e => {
+        // Don't trigger ObjectInspector expand/collapse.
+        e.stopPropagation();
+        props.onViewSourceInDebugger({
+          url: filename,
+          line: lineNumber,
+          column: columnNumber
+        });
+      };
+    }
+
+    stack.push("\t", span({
+      key: `fn${index}`,
+      className: "objectBox-stackTrace-fn"
+    }, cleanFunctionName(functionName)), " ", span({
+      key: `location${index}`,
+      className: "objectBox-stackTrace-location",
+      onClick: onLocationClick,
+      title: onLocationClick ? `View source in debugger → ${location}` : undefined
+    }, location), "\n");
+  });
+
+  return span({
+    key: "stack",
+    className: "objectBox-stackTrace-grid"
+  }, stack);
+}
+
+/**
+ * Parse a string that should represent a stack trace and returns an array of
+ * the frames. The shape of the frames are extremely important as they can then
+ * be processed here or in the toolbox by other components.
+ * @param {String} stack
+ * @returns {Array} Array of frames, which are object with the following shape:
+ *                  - {String} filename
+ *                  - {String} functionName
+ *                  - {String} location
+ *                  - {Number} columnNumber
+ *                  - {Number} lineNumber
+ */
+function parseStackString(stack) {
+  const res = [];
+  if (!stack) {
+    return res;
+  }
+
+  const isStacktraceALongString = isLongString(stack);
+  const stackString = isStacktraceALongString ? stack.initial : stack;
 
   stackString.split("\n").forEach((frame, index, frames) => {
     if (!frame) {
       // Skip any blank lines
       return;
     }
 
     // If the stacktrace is a longString, don't include the last frame in the
@@ -1975,50 +2035,34 @@ function getStacktraceElements(props, pr
       // What's needed is only the last part after " -> ".
       location = result[2].split(" -> ").pop();
     }
 
     if (!functionName) {
       functionName = "<anonymous>";
     }
 
-    let onLocationClick;
     // Given the input: "scriptLocation:2:100"
     // Result:
     // ["scriptLocation:2:100", "scriptLocation", "2", "100"]
     const locationParts = location.match(/^(.*):(\d+):(\d+)$/);
 
-    if (props.onViewSourceInDebugger && location && locationParts && !IGNORED_SOURCE_URLS.includes(locationParts[1])) {
-      const [, url, line, column] = locationParts;
-      onLocationClick = e => {
-        // Don't trigger ObjectInspector expand/collapse.
-        e.stopPropagation();
-        props.onViewSourceInDebugger({
-          url,
-          line: Number(line),
-          column: Number(column)
-        });
-      };
+    if (location && locationParts) {
+      const [, filename, line, column] = locationParts;
+      res.push({
+        filename,
+        functionName,
+        location,
+        columnNumber: Number(column),
+        lineNumber: Number(line)
+      });
     }
-
-    stack.push("\t", span({
-      key: `fn${index}`,
-      className: "objectBox-stackTrace-fn"
-    }, cleanFunctionName(functionName)), " ", span({
-      key: `location${index}`,
-      className: "objectBox-stackTrace-location",
-      onClick: onLocationClick,
-      title: onLocationClick ? `View source in debugger → ${location}` : undefined
-    }, location), "\n");
   });
 
-  return span({
-    key: "stack",
-    className: "objectBox-stackTrace-grid"
-  }, stack);
+  return res;
 }
 
 // Registration
 function supportsObject(object, noGrip = false) {
   if (noGrip === true || !isGrip(object)) {
     return false;
   }
   return object.preview && getGripType(object, noGrip) === "Error" || object.class === "DOMException";
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/debugger/column-marker.svg
@@ -0,0 +1,5 @@
+<svg width="9px" height="12px" viewBox="0 0 9 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g id="columnmarkergroup" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <polygon id="columnmarker" fill="#1B1B1D" points="0 0 4 0 9 6 4 12 0 12"></polygon>
+    </g>
+</svg>
\ No newline at end of file