mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-09-30 17:27:22 +08:00
The unrestricted annotation is Closure-specific and can be removed. This patch removes all comments with only the unrestricted annotation in them. R=szuend@chromium.org No-Presubmit: True Bug: 1011811 Change-Id: Iba0723644f53f5307c3077d341271af86becc32a Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2569758 Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Auto-Submit: Tim van der Lippe <tvanderlippe@chromium.org>
360 lines
11 KiB
JavaScript
360 lines
11 KiB
JavaScript
/*
|
|
* Copyright (C) 2011 Google Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
import * as Common from '../common/common.js';
|
|
import * as ObjectUI from '../object_ui/object_ui.js';
|
|
import * as Platform from '../platform/platform.js';
|
|
import * as SDK from '../sdk/sdk.js';
|
|
import * as UI from '../ui/ui.js';
|
|
|
|
/**
|
|
* @implements {UI.SearchableView.Searchable}
|
|
*/
|
|
export class JSONView extends UI.Widget.VBox {
|
|
/**
|
|
* @param {!ParsedJSON} parsedJSON
|
|
* @param {boolean=} startCollapsed
|
|
*/
|
|
constructor(parsedJSON, startCollapsed) {
|
|
super();
|
|
this._initialized = false;
|
|
this.registerRequiredCSS('source_frame/jsonView.css', {enableLegacyPatching: false});
|
|
this._parsedJSON = parsedJSON;
|
|
this._startCollapsed = !!startCollapsed;
|
|
this.element.classList.add('json-view');
|
|
|
|
/** @type {?UI.SearchableView.SearchableView} */
|
|
this._searchableView;
|
|
/** @type {!ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection} */
|
|
this._treeOutline;
|
|
/** @type {number} */
|
|
this._currentSearchFocusIndex = 0;
|
|
/** @type {!Array.<!ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement>} */
|
|
this._currentSearchTreeElements = [];
|
|
/** @type {?RegExp} */
|
|
this._searchRegex = null;
|
|
}
|
|
|
|
/**
|
|
* @param {string} content
|
|
* @return {!Promise<?UI.SearchableView.SearchableView>}
|
|
*/
|
|
static async createView(content) {
|
|
// We support non-strict JSON parsing by parsing an AST tree which is why we offload it to a worker.
|
|
const parsedJSON = await JSONView._parseJSON(content);
|
|
if (!parsedJSON || typeof parsedJSON.data !== 'object') {
|
|
return null;
|
|
}
|
|
|
|
const jsonView = new JSONView(parsedJSON);
|
|
const searchableView = new UI.SearchableView.SearchableView(jsonView, null);
|
|
searchableView.setPlaceholder(Common.UIString.UIString('Find'));
|
|
jsonView._searchableView = searchableView;
|
|
jsonView.show(searchableView.element);
|
|
return searchableView;
|
|
}
|
|
|
|
/**
|
|
* @param {?Object} obj
|
|
* @return {!UI.SearchableView.SearchableView}
|
|
*/
|
|
static createViewSync(obj) {
|
|
const jsonView = new JSONView(new ParsedJSON(obj, '', ''));
|
|
const searchableView = new UI.SearchableView.SearchableView(jsonView, null);
|
|
searchableView.setPlaceholder(Common.UIString.UIString('Find'));
|
|
jsonView._searchableView = searchableView;
|
|
jsonView.show(searchableView.element);
|
|
jsonView.element.tabIndex = 0;
|
|
return searchableView;
|
|
}
|
|
|
|
/**
|
|
* @param {?string} text
|
|
* @return {!Promise<?ParsedJSON>}
|
|
*/
|
|
static _parseJSON(text) {
|
|
let returnObj = null;
|
|
if (text) {
|
|
returnObj = JSONView._extractJSON(/** @type {string} */ (text));
|
|
}
|
|
if (!returnObj) {
|
|
return Promise.resolve(null);
|
|
}
|
|
try {
|
|
const json = JSON.parse(returnObj.data);
|
|
if (!json) {
|
|
return Promise.resolve(null);
|
|
}
|
|
returnObj.data = json;
|
|
} catch (e) {
|
|
returnObj = null;
|
|
}
|
|
|
|
return Promise.resolve(returnObj);
|
|
}
|
|
|
|
/**
|
|
* @param {string} text
|
|
* @return {?ParsedJSON}
|
|
*/
|
|
static _extractJSON(text) {
|
|
// Do not treat HTML as JSON.
|
|
if (text.startsWith('<')) {
|
|
return null;
|
|
}
|
|
let inner = JSONView._findBrackets(text, '{', '}');
|
|
const inner2 = JSONView._findBrackets(text, '[', ']');
|
|
inner = inner2.length > inner.length ? inner2 : inner;
|
|
|
|
// Return on blank payloads or on payloads significantly smaller than original text.
|
|
if (inner.length === -1 || text.length - inner.length > 80) {
|
|
return null;
|
|
}
|
|
|
|
const prefix = text.substring(0, inner.start);
|
|
const suffix = text.substring(inner.end + 1);
|
|
text = text.substring(inner.start, inner.end + 1);
|
|
|
|
// Only process valid JSONP.
|
|
if (suffix.trim().length && !(suffix.trim().startsWith(')') && prefix.trim().endsWith('('))) {
|
|
return null;
|
|
}
|
|
|
|
return new ParsedJSON(text, prefix, suffix);
|
|
}
|
|
|
|
/**
|
|
* @param {string} text
|
|
* @param {string} open
|
|
* @param {string} close
|
|
* @return {{start: number, end: number, length: number}}
|
|
*/
|
|
static _findBrackets(text, open, close) {
|
|
const start = text.indexOf(open);
|
|
const end = text.lastIndexOf(close);
|
|
let length = end - start - 1;
|
|
if (start === -1 || end === -1 || end < start) {
|
|
length = -1;
|
|
}
|
|
return {start: start, end: end, length: length};
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
wasShown() {
|
|
this._initialize();
|
|
}
|
|
|
|
_initialize() {
|
|
if (this._initialized) {
|
|
return;
|
|
}
|
|
this._initialized = true;
|
|
|
|
const obj = SDK.RemoteObject.RemoteObject.fromLocalObject(this._parsedJSON.data);
|
|
const title = this._parsedJSON.prefix + obj.description + this._parsedJSON.suffix;
|
|
this._treeOutline = new ObjectUI.ObjectPropertiesSection.ObjectPropertiesSection(
|
|
obj, title, undefined, undefined, undefined, undefined, true /* showOverflow */);
|
|
this._treeOutline.enableContextMenu();
|
|
this._treeOutline.setEditable(false);
|
|
if (!this._startCollapsed) {
|
|
this._treeOutline.expand();
|
|
}
|
|
this.element.appendChild(this._treeOutline.element);
|
|
const firstChild = this._treeOutline.firstChild();
|
|
if (firstChild) {
|
|
firstChild.select(true /* omitFocus */, false /* selectedByUser */);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} index
|
|
*/
|
|
_jumpToMatch(index) {
|
|
if (!this._searchRegex) {
|
|
return;
|
|
}
|
|
const previousFocusElement = this._currentSearchTreeElements[this._currentSearchFocusIndex];
|
|
if (previousFocusElement) {
|
|
previousFocusElement.setSearchRegex(this._searchRegex);
|
|
}
|
|
|
|
const newFocusElement = this._currentSearchTreeElements[index];
|
|
if (newFocusElement) {
|
|
this._updateSearchIndex(index);
|
|
newFocusElement.setSearchRegex(this._searchRegex, UI.UIUtils.highlightedCurrentSearchResultClassName);
|
|
newFocusElement.reveal();
|
|
} else {
|
|
this._updateSearchIndex(0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} count
|
|
*/
|
|
_updateSearchCount(count) {
|
|
if (!this._searchableView) {
|
|
return;
|
|
}
|
|
this._searchableView.updateSearchMatchesCount(count);
|
|
}
|
|
|
|
/**
|
|
* @param {number} index
|
|
*/
|
|
_updateSearchIndex(index) {
|
|
this._currentSearchFocusIndex = index;
|
|
if (!this._searchableView) {
|
|
return;
|
|
}
|
|
this._searchableView.updateCurrentMatchIndex(index);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
searchCanceled() {
|
|
this._searchRegex = null;
|
|
this._currentSearchTreeElements = [];
|
|
|
|
/**
|
|
* @type {?UI.TreeOutline.TreeElement}
|
|
*/
|
|
let element;
|
|
for (element = this._treeOutline.rootElement(); element; element = element.traverseNextTreeElement(false)) {
|
|
if (!(element instanceof ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement)) {
|
|
continue;
|
|
}
|
|
element.revertHighlightChanges();
|
|
}
|
|
this._updateSearchCount(0);
|
|
this._updateSearchIndex(0);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {!UI.SearchableView.SearchConfig} searchConfig
|
|
* @param {boolean} shouldJump
|
|
* @param {boolean=} jumpBackwards
|
|
*/
|
|
performSearch(searchConfig, shouldJump, jumpBackwards) {
|
|
let newIndex = this._currentSearchFocusIndex;
|
|
const previousSearchFocusElement = this._currentSearchTreeElements[newIndex];
|
|
this.searchCanceled();
|
|
this._searchRegex = searchConfig.toSearchRegex(true);
|
|
|
|
/**
|
|
* @type {?UI.TreeOutline.TreeElement}
|
|
*/
|
|
let element;
|
|
for (element = this._treeOutline.rootElement(); element; element = element.traverseNextTreeElement(false)) {
|
|
if (!(element instanceof ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement)) {
|
|
continue;
|
|
}
|
|
const hasMatch = element.setSearchRegex(this._searchRegex);
|
|
if (hasMatch) {
|
|
this._currentSearchTreeElements.push(element);
|
|
}
|
|
if (previousSearchFocusElement === element) {
|
|
const currentIndex = this._currentSearchTreeElements.length - 1;
|
|
if (hasMatch || jumpBackwards) {
|
|
newIndex = currentIndex;
|
|
} else {
|
|
newIndex = currentIndex + 1;
|
|
}
|
|
}
|
|
}
|
|
this._updateSearchCount(this._currentSearchTreeElements.length);
|
|
|
|
if (!this._currentSearchTreeElements.length) {
|
|
this._updateSearchIndex(-1);
|
|
return;
|
|
}
|
|
newIndex = Platform.NumberUtilities.mod(newIndex, this._currentSearchTreeElements.length);
|
|
|
|
this._jumpToMatch(newIndex);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
jumpToNextSearchResult() {
|
|
if (!this._currentSearchTreeElements.length) {
|
|
return;
|
|
}
|
|
const newIndex =
|
|
Platform.NumberUtilities.mod(this._currentSearchFocusIndex + 1, this._currentSearchTreeElements.length);
|
|
this._jumpToMatch(newIndex);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
jumpToPreviousSearchResult() {
|
|
if (!this._currentSearchTreeElements.length) {
|
|
return;
|
|
}
|
|
const newIndex =
|
|
Platform.NumberUtilities.mod(this._currentSearchFocusIndex - 1, this._currentSearchTreeElements.length);
|
|
this._jumpToMatch(newIndex);
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {boolean}
|
|
*/
|
|
supportsCaseSensitiveSearch() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @return {boolean}
|
|
*/
|
|
supportsRegexSearch() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
export class ParsedJSON {
|
|
/**
|
|
* @param {*} data
|
|
* @param {string} prefix
|
|
* @param {string} suffix
|
|
*/
|
|
constructor(data, prefix, suffix) {
|
|
this.data = data;
|
|
this.prefix = prefix;
|
|
this.suffix = suffix;
|
|
}
|
|
}
|