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

META-INF.dirigible.dev-tools.input.InputTimeline.js Maven / Gradle / Ivy

There is a newer version: 10.6.27
Show newest version
// Copyright (c) 2019 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.

import * as Bindings from '../bindings/bindings.js';
import * as ProtocolClient from '../protocol_client/protocol_client.js';
import * as SDK from '../sdk/sdk.js';
import * as Timeline from '../timeline/timeline.js';
import * as UI from '../ui/ui.js';

import {InputModel} from './InputModel.js';

/**
 * @implements {Timeline.TimelineLoader.Client}
 * @unrestricted
 */
export class InputTimeline extends UI.Widget.VBox {
  constructor() {
    super(true);
    this.registerRequiredCSS('input/inputTimeline.css');
    this.element.classList.add('inputs-timeline');

    this._tracingClient = null;
    this._tracingModel = null;
    this._inputModel = null;

    this._state = State.Idle;


    this._toggleRecordAction =
        /** @type {!UI.Action.Action }*/ (self.UI.actionRegistry.action('input.toggle-recording'));
    this._startReplayAction =
        /** @type {!UI.Action.Action }*/ (self.UI.actionRegistry.action('input.start-replaying'));
    this._togglePauseAction =
        /** @type {!UI.Action.Action }*/ (self.UI.actionRegistry.action('input.toggle-pause'));

    const toolbarContainer = this.contentElement.createChild('div', 'input-timeline-toolbar-container');
    this._panelToolbar = new UI.Toolbar.Toolbar('input-timeline-toolbar', toolbarContainer);

    this._panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this._toggleRecordAction));
    this._panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this._startReplayAction));
    this._panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this._togglePauseAction));

    this._clearButton = new UI.Toolbar.ToolbarButton(ls`Clear all`, 'largeicon-clear');
    this._clearButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this._reset.bind(this));
    this._panelToolbar.appendToolbarItem(this._clearButton);

    this._panelToolbar.appendSeparator();

    // Load / Save
    this._loadButton = new UI.Toolbar.ToolbarButton(Common.UIString('Load profile…'), 'largeicon-load');
    this._loadButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, () => this._selectFileToLoad());
    this._saveButton = new UI.Toolbar.ToolbarButton(Common.UIString('Save profile…'), 'largeicon-download');
    this._saveButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, event => {
      this._saveToFile();
    });
    this._panelToolbar.appendSeparator();
    this._panelToolbar.appendToolbarItem(this._loadButton);
    this._panelToolbar.appendToolbarItem(this._saveButton);
    this._panelToolbar.appendSeparator();
    this._createFileSelector();

    this._updateControls();
  }

  _reset() {
    this._tracingClient = null;
    this._tracingModel = null;
    this._inputModel = null;
    this._setState(State.Idle);
  }

  _createFileSelector() {
    if (this._fileSelectorElement) {
      this._fileSelectorElement.remove();
    }
    this._fileSelectorElement = UI.UIUtils.createFileSelectorElement(this._loadFromFile.bind(this));
    this.element.appendChild(this._fileSelectorElement);
  }

  /**
   * @override
   */
  wasShown() {
  }

  /**
   * @override
   */
  willHide() {
  }

  /**
   * @param {!State} state
   */
  _setState(state) {
    this._state = state;
    this._updateControls();
  }

  /**
   * @return {boolean}
   */
  _isAvailableState() {
    return this._state === State.Idle || this._state === State.ReplayPaused;
  }

  _updateControls() {
    this._toggleRecordAction.setToggled(this._state === State.Recording);
    this._toggleRecordAction.setEnabled(this._isAvailableState() || this._state === State.Recording);
    this._startReplayAction.setEnabled(this._isAvailableState() && !!this._tracingModel);
    this._togglePauseAction.setEnabled(this._state === State.Replaying || this._state === State.ReplayPaused);
    this._togglePauseAction.setToggled(this._state === State.ReplayPaused);
    this._clearButton.setEnabled(this._isAvailableState());
    this._loadButton.setEnabled(this._isAvailableState());
    this._saveButton.setEnabled(this._isAvailableState() && !!this._tracingModel);
  }

  _toggleRecording() {
    switch (this._state) {
      case State.Recording: {
        this._stopRecording();
        break;
      }
      case State.Idle: {
        this._startRecording();
        break;
      }
    }
  }

  _startReplay() {
    this._replayEvents();
  }

  _toggleReplayPause() {
    switch (this._state) {
      case State.Replaying: {
        this._pauseReplay();
        break;
      }
      case State.ReplayPaused: {
        this._resumeReplay();
        break;
      }
    }
  }

  /**
   * Saves all current events in a file (JSON format).
   */
  async _saveToFile() {
    console.assert(this._state === State.Idle && this._tracingModel);

    const fileName = `InputProfile-${new Date().toISO8601Compact()}.json`;
    const stream = new Bindings.FileUtils.FileOutputStream();

    const accepted = await stream.open(fileName);
    if (!accepted) {
      return;
    }

    const backingStorage =
        /** @type {!Bindings.TempFile.TempFileBackingStorage} */ (this._tracingModel.backingStorage());
    await backingStorage.writeToStream(stream);
    stream.close();
  }


  _selectFileToLoad() {
    this._fileSelectorElement.click();
  }

  /**
   * @param {!File} file
   */
  _loadFromFile(file) {
    console.assert(this._isAvailableState());

    this._setState(State.Loading);
    this._loader = Timeline.TimelineLoader.TimelineLoader.loadFromFile(file, this);

    this._createFileSelector();
  }

  async _startRecording() {
    this._setState(State.StartPending);

    this._tracingClient = new InputTimeline.TracingClient(
        /** @type {!SDK.SDKModel.Target} */ (SDK.SDKModel.TargetManager.instance().mainTarget()), this);

    const response = await this._tracingClient.startRecording();
    if (response[ProtocolClient.InspectorBackend.ProtocolError]) {
      this._recordingFailed(response[ProtocolClient.InspectorBackend.ProtocolError]);
    } else {
      this._setState(State.Recording);
    }
  }

  async _stopRecording() {
    this._setState(State.StopPending);
    await this._tracingClient.stopRecording();
    this._tracingClient = null;
  }

  async _replayEvents() {
    this._setState(State.Replaying);
    await this._inputModel.startReplay(this.replayStopped.bind(this));
  }

  _pauseReplay() {
    this._inputModel.pause();
    this._setState(State.ReplayPaused);
  }

  _resumeReplay() {
    this._inputModel.resume();
    this._setState(State.Replaying);
  }

  /**
   * @override
   */
  loadingStarted() {
  }

  /**
   * @override
   * @param {number=} progress
   */
  loadingProgress(progress) {
  }


  /**
   * @override
   */
  processingStarted() {
  }

  /**
   * @override
   * @param {?SDK.TracingModel.TracingModel} tracingModel
   */
  loadingComplete(tracingModel) {
    if (!tracingModel) {
      this._reset();
      return;
    }
    this._inputModel =
        new InputModel(/** @type {!SDK.SDKModel.Target} */ (SDK.SDKModel.TargetManager.instance().mainTarget()));
    this._tracingModel = tracingModel;
    this._inputModel.setEvents(tracingModel);

    this._setState(State.Idle);
  }

  _recordingFailed(error) {
    this._tracingClient = null;
    this._setState(State.Idle);
  }

  replayStopped() {
    this._setState(State.Idle);
  }
}

/**
 * @enum {symbol}
 */
export const State = {
  Idle: Symbol('Idle'),
  StartPending: Symbol('StartPending'),
  Recording: Symbol('Recording'),
  StopPending: Symbol('StopPending'),
  Replaying: Symbol('Replaying'),
  ReplayPaused: Symbol('ReplayPaused'),
  Loading: Symbol('Loading')
};


/**
 * @implements {UI.ActionDelegate.ActionDelegate}
 */
export class ActionDelegate {
  /**
   * @override
   * @param {!UI.Context.Context} context
   * @param {string} actionId
   * @return {boolean}
   */
  handleAction(context, actionId) {
    const inputViewId = 'Inputs';
    UI.ViewManager.ViewManager.instance()
        .showView(inputViewId)
        .then(() => UI.ViewManager.ViewManager.instance().view(inputViewId).widget())
        .then(widget => this._innerHandleAction(/** @type !InputTimeline} */ (widget), actionId));

    return true;
  }

  /**
   * @param {!InputTimeline} inputTimeline
   * @param {string} actionId
   */
  _innerHandleAction(inputTimeline, actionId) {
    switch (actionId) {
      case 'input.toggle-recording':
        inputTimeline._toggleRecording();
        break;
      case 'input.start-replaying':
        inputTimeline._startReplay();
        break;
      case 'input.toggle-pause':
        inputTimeline._toggleReplayPause();
        break;
      default:
        console.assert(false, `Unknown action: ${actionId}`);
    }
  }
}

/**
 * @implements {SDK.TracingManager.TracingManagerClient}
 */
export class TracingClient {
  /**
   * @param {!SDK.SDKModel.Target} target
   * @param {!InputTimeline} client
   */
  constructor(target, client) {
    this._target = target;
    this._tracingManager = target.model(SDK.TracingManager.TracingManager);
    this._client = client;

    const backingStorage = new Bindings.TempFile.TempFileBackingStorage();
    this._tracingModel = new SDK.TracingModel.TracingModel(backingStorage);

    /** @type {?function()} */
    this._tracingCompleteCallback = null;
  }

  /**
   * @return {!Promise}
   */
  async startRecording() {
    function disabledByDefault(category) {
      return 'disabled-by-default-' + category;
    }

    const categoriesArray = ['devtools.timeline', disabledByDefault('devtools.timeline.inputs')];
    const categories = categoriesArray.join(',');

    const response = await this._tracingManager.start(this, categories, '');
    if (response['Protocol.Error']) {
      await this._waitForTracingToStop(false);
    }
    return response;
  }

  async stopRecording() {
    if (this._tracingManager) {
      this._tracingManager.stop();
    }

    await this._waitForTracingToStop(true);
    await SDK.SDKModel.TargetManager.instance().resumeAllTargets();
    this._tracingModel.tracingComplete();
    this._client.loadingComplete(this._tracingModel);
  }
  /**
   * @param {!Array.} events
   * @override
   */
  traceEventsCollected(events) {
    this._tracingModel.addEvents(events);
  }

  /**
   * @override
   */
  tracingComplete() {
    this._tracingCompleteCallback();
    this._tracingCompleteCallback = null;
  }

  /**
   * @param {number} usage
   * @override
   */
  tracingBufferUsage(usage) {
  }

  /**
   * @param {number} progress
   * @override
   */
  eventsRetrievalProgress(progress) {
  }

  /**
   * @param {boolean} awaitTracingCompleteCallback - Whether to wait for the _tracingCompleteCallback to happen
   * @return {!Promise}
   */
  _waitForTracingToStop(awaitTracingCompleteCallback) {
    return new Promise(resolve => {
      if (this._tracingManager && awaitTracingCompleteCallback) {
        this._tracingCompleteCallback = resolve;
      } else {
        resolve();
      }
    });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy