All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.dirigible.dev-tools.source_frame.JSONView.js Maven / Gradle / Ivy

There is a newer version: 10.6.27
Show newest version
/*
 * 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 Formatter from '../formatter/formatter.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');
    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.} */
    this._currentSearchTreeElements = [];
    /** @type {?RegExp} */
    this._searchRegex = null;
  }

  /**
   * @param {string} content
   * @return {!Promise}
   */
  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);
    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);
    searchableView.setPlaceholder(Common.UIString.UIString('Find'));
    jsonView._searchableView = searchableView;
    jsonView.show(searchableView.element);
    jsonView.element.setAttribute('tabIndex', 0);
    return searchableView;
  }

  /**
   * @param {?string} text
   * @return {!Promise}
   */
  static _parseJSON(text) {
    let returnObj = null;
    if (text) {
      returnObj = JSONView._extractJSON(/** @type {string} */ (text));
    }
    if (!returnObj) {
      return Promise.resolve(/** @type {?ParsedJSON} */ (null));
    }
    return Formatter.FormatterWorkerPool.formatterWorkerPool()
        .parseJSONRelaxed(returnObj.data)
        .then(handleReturnedJSON);

    /**
     * @param {*} data
     * @return {?ParsedJSON}
     */
    function handleReturnedJSON(data) {
      if (!data) {
        return null;
      }
      returnObj.data = data;
      return 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);
    this._treeOutline.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 = [];

    for (let 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);

    for (let 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(0);
      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;
  }
}


/**
 * @unrestricted
 */
export class ParsedJSON {
  /**
   * @param {*} data
   * @param {string} prefix
   * @param {string} suffix
   */
  constructor(data, prefix, suffix) {
    this.data = data;
    this.prefix = prefix;
    this.suffix = suffix;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy