Skip to content

Commit

Permalink
[DevTools] JSONView parsing smarter
Browse files Browse the repository at this point in the history
[Copy of  issue 1838873002 due to bug with bad patch.]

JSONView should now be a little more smart in the way it parses data.
Users may send many different kinds of JSON and it should still display
it to the users. If the JSON is much to far gone to be considered JSON
the parser will exit (for example if the user sends things like:
{a:5*5} it will not parse it. ie: no arithmics... static content only)

BUG=598390
R=lushnikov

Review URL: https://codereview.chromium.org/1912973002

Cr-Commit-Position: refs/heads/master@{#389321}
  • Loading branch information
allada authored and Commit bot committed Apr 23, 2016
1 parent 738ee02 commit 35e1747
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 134 deletions.
1 change: 1 addition & 0 deletions devtools.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@
'front_end/formatter_worker/IdentityFormatter.js',
'front_end/formatter_worker/JavaScriptOutline.js',
'front_end/formatter_worker/FormatterWorker.js',
'front_end/formatter_worker/RelaxedJSONParser.js',
],
'devtools_settings_js_files': [
'front_end/settings/editFileSystemView.css',
Expand Down
8 changes: 8 additions & 0 deletions front_end/components/ObjectPropertiesSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ WebInspector.ObjectPropertiesSection.prototype = {
this._objectTreeElement.expand();
},

/**
* @param {boolean} value
*/
setEditable: function(value)
{
this._editable = value;
},

/**
* @return {!TreeElement}
*/
Expand Down
20 changes: 19 additions & 1 deletion front_end/es_tree/ESTreeWalker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@

/**
* @constructor
* @param {function(!ESTree.Node)} beforeVisit
* @param {function(!ESTree.Node):(!Object|undefined)} beforeVisit
* @param {function(!ESTree.Node)=} afterVisit
*/
WebInspector.ESTreeWalker = function(beforeVisit, afterVisit)
{
this._beforeVisit = beforeVisit;
this._afterVisit = afterVisit || new Function();
this._walkNulls = false;
}

/** @typedef {!Object} WebInspector.ESTreeWalker.SkipSubtree */
WebInspector.ESTreeWalker.SkipSubtree = {};

WebInspector.ESTreeWalker.prototype = {
/**
* @param {boolean} value
*/
setWalkNulls: function(value)
{
this._walkNulls = value;
},

/**
* @param {!ESTree.Node} ast
*/
Expand All @@ -30,6 +40,14 @@ WebInspector.ESTreeWalker.prototype = {
*/
_innerWalk: function(node, parent)
{
if (!node && parent && this._walkNulls) {
node = /** @type {!ESTree.Node} */ ({
type: "Literal",
raw: "null",
value: null
});
}

if (!node)
return;
node.parent = parent;
Expand Down
4 changes: 4 additions & 0 deletions front_end/externs.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,10 @@ ESTree.Node = function()
this.id;
/** @type {(number|undefined)} */
this.length;
/** @type {(?ESTree.Node|undefined)} */
this.argument;
/** @type {(string|undefined)} */
this.operator;
}

/**
Expand Down
11 changes: 11 additions & 0 deletions front_end/formatter_worker/FormatterWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,22 @@ self.onmessage = function(event) {
case "evaluatableJavaScriptSubstring":
WebInspector.evaluatableJavaScriptSubstring(params.content);
break;
case "relaxedJSONParser":
WebInspector.relaxedJSONParser(params.content);
break;
default:
console.error("Unsupport method name: " + method);
}
};

/**
* @param {string} content
*/
WebInspector.relaxedJSONParser = function(content)
{
postMessage(WebInspector.RelaxedJSONParser.parse(content));
}

/**
* @param {string} content
*/
Expand Down
201 changes: 201 additions & 0 deletions front_end/formatter_worker/RelaxedJSONParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

WebInspector.RelaxedJSONParser = {};

/** @enum {string} */
WebInspector.RelaxedJSONParser.States = {
ExpectKey: "ExpectKey",
ExpectValue: "ExpectValue"
};

/** @enum {*} */
WebInspector.RelaxedJSONParser.Keywords = {
"NaN": NaN,
"true": true,
"false": false,
"Infinity": Infinity,
"undefined": undefined,
"null": null
};

/**
* @param {string} content
* @return {*}
*/
WebInspector.RelaxedJSONParser.parse = function(content)
{
var Keywords = WebInspector.RelaxedJSONParser.Keywords;
var States = WebInspector.RelaxedJSONParser.States;
content = "(" + content + ")";

try {
var root = acorn.parse(content, {});
} catch (e) {
return null;
}

var walker = new WebInspector.ESTreeWalker(beforeVisit, afterVisit);

var rootTip = [];

/** @type {!Array.<!WebInspector.RelaxedJSONParser.Context>} */
var stack = [];

var stackData = /** @type {!WebInspector.RelaxedJSONParser.Context} */ ({
key: 0,
tip: rootTip,
state: States.ExpectValue,
parentIsArray: true
});

walker.setWalkNulls(true);
var hasExpression = false;

walker.walk(root);

if (hasExpression)
return null;
return rootTip.length ? rootTip[0] : null;

/**
* @param {!WebInspector.RelaxedJSONParser.Context} newStack
*/
function pushStack(newStack)
{
stack.push(stackData);
stackData = newStack;
}

function popStack()
{
stackData = stack.pop();
}

/**
* @param {*} value
*/
function applyValue(value)
{
stackData.tip[stackData.key] = value;
if (stackData.parentIsArray)
stackData.key++;
else
stackData.state = null;
}

/**
* @param {!ESTree.Node} node
* @return {!Object|undefined}
*/
function beforeVisit(node)
{
switch (node.type) {
case "ObjectExpression":
var newTip = {};
applyValue(newTip);

pushStack(/** @type {!WebInspector.RelaxedJSONParser.Context} */ ({
key: null,
tip: newTip,
state: null,
parentIsArray: false
}));
break;
case "ArrayExpression":
var newTip = [];
applyValue(newTip);

pushStack(/** @type {!WebInspector.RelaxedJSONParser.Context} */ ({
key: 0,
tip: newTip,
state: States.ExpectValue,
parentIsArray: true
}));
break;
case "Property":
stackData.state = States.ExpectKey;
break;
case "Literal":
if (stackData.state === States.ExpectKey) {
stackData.key = node.value;
stackData.state = States.ExpectValue;
} else if (stackData.state === States.ExpectValue) {
applyValue(extractValue(node));
return WebInspector.ESTreeWalker.SkipSubtree;
}
break;
case "Identifier":
if (stackData.state === States.ExpectKey) {
stackData.key = /** @type {string} */ (node.name);
stackData.state = States.ExpectValue;
} else if (stackData.state === States.ExpectValue) {
applyValue(extractValue(node));
return WebInspector.ESTreeWalker.SkipSubtree;
}
break;
case "UnaryExpression":
if (stackData.state === States.ExpectValue) {
applyValue(extractValue(node));
return WebInspector.ESTreeWalker.SkipSubtree;
}
break;
case "Program":
case "ExpressionStatement":
break;
default:
if (stackData.state === States.ExpectValue)
applyValue(extractValue(node));
return WebInspector.ESTreeWalker.SkipSubtree;
}
}

/**
* @param {!ESTree.Node} node
*/
function afterVisit(node)
{
if (node.type === "ObjectExpression" || node.type === "ArrayExpression")
popStack();
}

/**
* @param {!ESTree.Node} node
* @return {*}
*/
function extractValue(node)
{
var isNegative = false;
var originalNode = node;
var value;
if (node.type === "UnaryExpression" && (node.operator === "-" || node.operator === "+")) {
if (node.operator === "-")
isNegative = true;
node = /** @type {!ESTree.Node} */ (node.argument);
}

if (node.type === "Literal") {
value = node.value;
} else if (node.type === "Identifier" && Keywords.hasOwnProperty(node.name)) {
value = Keywords[node.name];
} else {
hasExpression = true;
return content.substring(originalNode.start, originalNode.end);
}

if (isNegative) {
if (typeof value !== "number") {
hasExpression = true;
return content.substring(originalNode.start, originalNode.end);
}
value = -(value);
}
return value;
}
}

/**
* @typedef {!{key: (number|string), tip: (!Array|!Object), state: ?WebInspector.RelaxedJSONParser.States, parentIsArray: boolean}}
*/
WebInspector.RelaxedJSONParser.Context;
3 changes: 2 additions & 1 deletion front_end/formatter_worker/module.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"CSSRuleParser.js",
"HTMLFormatter.js",
"IdentityFormatter.js",
"JavaScriptOutline.js"
"JavaScriptOutline.js",
"RelaxedJSONParser.js"
],
"skip_compilation": [
"../acorn/acorn.js",
Expand Down
Loading

0 comments on commit 35e1747

Please sign in to comment.