<commit_before>// Copyright 2018 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 'dart:async';
import 'dart:html' as html;

import 'package:pedantic/pedantic.dart';
import 'package:vm_service/vm_service.dart';

import 'config_specific/logger/logger.dart';
import 'core/message_bus.dart';
import 'debugger/html_debugger_screen.dart';
import 'framework/html_framework.dart';
import 'framework_controller.dart';
import 'globals.dart';
import 'info/html_info_screen.dart';
import 'inspector/html_inspector_screen.dart';
import 'logging/html_logging_screen.dart';
import 'memory/html_memory_screen.dart';
import 'model/html_model.dart';
import 'performance/html_performance_screen.dart';
import 'server_api_client.dart';
import 'service_registrations.dart' as registrations;
import 'timeline/html_timeline_controller.dart';
import 'timeline/html_timeline_screen.dart';
import 'ui/analytics.dart' as ga;
import 'ui/analytics_platform.dart' as ga_platform;
import 'ui/html_custom.dart';
import 'ui/html_elements.dart';
import 'ui/icons.dart';
import 'ui/primer.dart';
import 'ui/ui_utils.dart';
import 'utils.dart';

const flutterLibraryUri = 'package:flutter/src/widgets/binding.dart';

class HtmlPerfToolFramework extends HtmlFramework {
  HtmlPerfToolFramework() {
    html.window.onError.listen(_gAReportExceptions);

    initDevToolsServerConnection();
    initGlobalUI();
    initTestingModel();
  }

  void _gAReportExceptions(html.Event e) {
    final html.ErrorEvent errorEvent = e as html.ErrorEvent;

    final message = '${errorEvent.message}\n'
        '${errorEvent.filename}@${errorEvent.lineno}:${errorEvent.colno}\n'
        '${errorEvent.error}';

    // Report exceptions with DevTools to GA.
    ga.error(message, true);

    // Also log them to the console to aid debugging.
    log(message, LogLevel.error);
  }

  HtmlStatusItem isolateSelectStatus;
  PSelect isolateSelect;

  HtmlStatusItem connectionStatus;
  HtmlStatus reloadStatus;

  static const _reloadActionId = 'reload-action';
  static const _restartActionId = 'restart-action';

  DevToolsServerConnection devToolsServer;

  void initGlobalUI() async {
    // Listen for clicks on the 'send feedback' button.
    queryId('send-feedback-button').onClick.listen((_) {
      ga.select(ga.devToolsMain, ga.feedback);
      // TODO(devoncarew): Fill in useful product info here, like the Flutter
      // SDK version and the version of DevTools in use.
      html.window
          .open('https://github.com/flutter/devtools/issues', '_feedback');
    });

    // Listen for clicks on the 'Try DevTools on Flutter Web' button.
    queryId('try-flutter-web-devtools')
      ..hidden = false
      ..onClick.listen((_) {
        var href = '/flutter.html#/';
        // Preserve query parameters when opening the Flutter demo so the
        // user does not need to go through the connect dialog again.
        final flutterQueryParams = Map<String, String>.from(
            Uri.tryParse(html.window.location.href).queryParameters ?? {});
        // Carry over the current page.
        flutterQueryParams['page'] = current?.id;
        if (flutterQueryParams.isNotEmpty) {
          href += Uri(queryParameters: flutterQueryParams).toString();
        }
        html.window.location.href = href;
      });

    await serviceManager.onServiceAvailable;
    await addScreens();
    screensReady.complete();

    final mainNav = CoreElement.from(queryId('main-nav'))..clear();
    final iconNav = CoreElement.from(queryId('icon-nav'))..clear();

    for (HtmlScreen screen in screens) {
      final link = CoreElement('a')
        ..add(<CoreElement>[
          span(c: 'octicon ${screen.iconClass}'),
          span(text: ' ${screen.name}', c: 'optional-1140')
        ]);
      if (screen.disabled) {
        link
          ..onClick.listen((html.MouseEvent e) {
            e.preventDefault();
            toast(link.tooltip);
          })
          ..toggleClass('disabled', true)
          ..tooltip = screen.disabledTooltip;
      } else {
        link
          ..attributes['href'] = screen.ref
          ..onClick.listen((html.MouseEvent e) {
            e.preventDefault();
            navigateTo(screen.id);
          });
      }
      (screen.showTab ? mainNav : iconNav).add(link);
    }

    isolateSelectStatus = HtmlStatusItem();
    globalStatus.add(isolateSelectStatus);
    isolateSelect = PSelect()
      ..small()
      ..change(_handleIsolateSelect);
    isolateSelectStatus.element.add(isolateSelect);
    _rebuildIsolateSelect();

    serviceManager.isolateManager.onIsolateCreated
        .listen(_rebuildIsolateSelect);
    serviceManager.isolateManager.onIsolateExited.listen(_rebuildIsolateSelect);
    serviceManager.isolateManager.onSelectedIsolateChanged
        .listen(_rebuildIsolateSelect);

    _initHotReloadRestartServiceListeners();

    serviceManager.onStateChange.listen((_) {
      _rebuildConnectionStatus();

      if (!serviceManager.hasConnection) {
        toast('Device connection lost.');
      }
    });
  }

  void initTestingModel() {
    final app = HtmlApp.register(this);
    screensReady.future.then(app.devToolsReady);
  }

  Future initDevToolsServerConnection() async {
    // When running the debug DDC Build, the server won't be running so we
    // can't connect to its API (for now at least, the API is optional).
    if (isDebugBuild()) {
      return;
    }

    DevToolsServerConnection devToolsServer;

    try {
      devToolsServer = await DevToolsServerConnection.connect();
    } catch (e) {
      print('Failed to connect to SSE API: $e');
      return;
    }

    // If we showed a notification for DevTools and the user manually clicked
    // into the window instead, we should hide the notification automatically.
    html.window.onFocus.listen((_) => devToolsServer.dismissNotifications());

    // TODO(dantup): As a workaround for not being able to reconnect DevTools to
    // a new VM yet (https://github.com/flutter/devtools/issues/989) we reload
    // the page and pass a querystring variable to know that we need to notify
    // the user.
    final uri = Uri.parse(html.window.location.href);
    if (uri.queryParameters.containsKey('notify')) {
      final newParams = Map.of(uri.queryParameters)..remove('notify');
      html.window.history.pushState(
          null, null, uri.replace(queryParameters: newParams).toString());
      unawaited(devToolsServer.notify());
    }

    // Handle onShowPageId.
    frameworkController.onShowPageId.listen((String pageId) {
      final screen = getScreen(pageId);
      if (screen != null) {
        load(screen);
      }
    });

    // Handle onConnectVmEvent.
    frameworkController.onConnectVmEvent.listen((ConnectVmEvent event) {
      // Reload the page with the new VM service URI in the querystring.
      // TODO(dantup): Remove this code and replace with code that just reconnects
      // (and optionally notifies based on requestParams['notify']) when it's
      // supported better (https://github.com/flutter/devtools/issues/989).
      //
      // This currently doesn't currently work, as the app does not reinitialize
      // correctly:<commit_msg>Protect against NPE caused by null `devtoolsServer` (#1940)<commit_after>// Copyright 2018 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 'dart:async';
import 'dart:html' as html;

import 'package:pedantic/pedantic.dart';
import 'package:vm_service/vm_service.dart';

import 'config_specific/logger/logger.dart';
import 'core/message_bus.dart';
import 'debugger/html_debugger_screen.dart';
import 'framework/html_framework.dart';
import 'framework_controller.dart';
import 'globals.dart';
import 'info/html_info_screen.dart';
import 'inspector/html_inspector_screen.dart';
import 'logging/html_logging_screen.dart';
import 'memory/html_memory_screen.dart';
import 'model/html_model.dart';
import 'performance/html_performance_screen.dart';
import 'server_api_client.dart';
import 'service_registrations.dart' as registrations;
import 'timeline/html_timeline_controller.dart';
import 'timeline/html_timeline_screen.dart';
import 'ui/analytics.dart' as ga;
import 'ui/analytics_platform.dart' as ga_platform;
import 'ui/html_custom.dart';
import 'ui/html_elements.dart';
import 'ui/icons.dart';
import 'ui/primer.dart';
import 'ui/ui_utils.dart';
import 'utils.dart';

const flutterLibraryUri = 'package:flutter/src/widgets/binding.dart';

class HtmlPerfToolFramework extends HtmlFramework {
  HtmlPerfToolFramework() {
    html.window.onError.listen(_gAReportExceptions);

    initDevToolsServerConnection();
    initGlobalUI();
    initTestingModel();
  }

  void _gAReportExceptions(html.Event e) {
    final html.ErrorEvent errorEvent = e as html.ErrorEvent;

    final message = '${errorEvent.message}\n'
        '${errorEvent.filename}@${errorEvent.lineno}:${errorEvent.colno}\n'
        '${errorEvent.error}';

    // Report exceptions with DevTools to GA.
    ga.error(message, true);

    // Also log them to the console to aid debugging.
    log(message, LogLevel.error);
  }

  HtmlStatusItem isolateSelectStatus;
  PSelect isolateSelect;

  HtmlStatusItem connectionStatus;
  HtmlStatus reloadStatus;

  static const _reloadActionId = 'reload-action';
  static const _restartActionId = 'restart-action';

  DevToolsServerConnection devToolsServer;

  void initGlobalUI() async {
    // Listen for clicks on the 'send feedback' button.
    queryId('send-feedback-button').onClick.listen((_) {
      ga.select(ga.devToolsMain, ga.feedback);
      // TODO(devoncarew): Fill in useful product info here, like the Flutter
      // SDK version and the version of DevTools in use.
      html.window
          .open('https://github.com/flutter/devtools/issues', '_feedback');
    });

    // Listen for clicks on the 'Try DevTools on Flutter Web' button.
    queryId('try-flutter-web-devtools')
      ..hidden = false
      ..onClick.listen((_) {
        var href = '/flutter.html#/';
        // Preserve query parameters when opening the Flutter demo so the
        // user does not need to go through the connect dialog again.
        final flutterQueryParams = Map<String, String>.from(
            Uri.tryParse(html.window.location.href).queryParameters ?? {});
        // Carry over the current page.
        flutterQueryParams['page'] = current?.id;
        if (flutterQueryParams.isNotEmpty) {
          href += Uri(queryParameters: flutterQueryParams).toString();
        }
        html.window.location.href = href;
      });

    await serviceManager.onServiceAvailable;
    await addScreens();
    screensReady.complete();

    final mainNav = CoreElement.from(queryId('main-nav'))..clear();
    final iconNav = CoreElement.from(queryId('icon-nav'))..clear();

    for (HtmlScreen screen in screens) {
      final link = CoreElement('a')
        ..add(<CoreElement>[
          span(c: 'octicon ${screen.iconClass}'),
          span(text: ' ${screen.name}', c: 'optional-1140')
        ]);
      if (screen.disabled) {
        link
          ..onClick.listen((html.MouseEvent e) {
            e.preventDefault();
            toast(link.tooltip);
          })
          ..toggleClass('disabled', true)
          ..tooltip = screen.disabledTooltip;
      } else {
        link
          ..attributes['href'] = screen.ref
          ..onClick.listen((html.MouseEvent e) {
            e.preventDefault();
            navigateTo(screen.id);
          });
      }
      (screen.showTab ? mainNav : iconNav).add(link);
    }

    isolateSelectStatus = HtmlStatusItem();
    globalStatus.add(isolateSelectStatus);
    isolateSelect = PSelect()
      ..small()
      ..change(_handleIsolateSelect);
    isolateSelectStatus.element.add(isolateSelect);
    _rebuildIsolateSelect();

    serviceManager.isolateManager.onIsolateCreated
        .listen(_rebuildIsolateSelect);
    serviceManager.isolateManager.onIsolateExited.listen(_rebuildIsolateSelect);
    serviceManager.isolateManager.onSelectedIsolateChanged
        .listen(_rebuildIsolateSelect);

    _initHotReloadRestartServiceListeners();

    serviceManager.onStateChange.listen((_) {
      _rebuildConnectionStatus();

      if (!serviceManager.hasConnection) {
        toast('Device connection lost.');
      }
    });
  }

  void initTestingModel() {
    final app = HtmlApp.register(this);
    screensReady.future.then(app.devToolsReady);
  }

  Future initDevToolsServerConnection() async {
    // When running the debug DDC Build, the server won't be running so we
    // can't connect to its API (for now at least, the API is optional).
    if (isDebugBuild()) {
      return;
    }

    DevToolsServerConnection devToolsServer;

    try {
      devToolsServer = await DevToolsServerConnection.connect();
    } catch (e) {
      print('Failed to connect to SSE API: $e');
      return;
    }

    // If we showed a notification for DevTools and the user manually clicked
    // into the window instead, we should hide the notification automatically.
    html.window.onFocus.listen((_) => devToolsServer?.dismissNotifications());

    // TODO(dantup): As a workaround for not being able to reconnect DevTools to
    // a new VM yet (https://github.com/flutter/devtools/issues/989) we reload
    // the page and pass a querystring variable to know that we need to notify
    // the user.
    final uri = Uri.parse(html.window.location.href);
    if (uri.queryParameters.containsKey('notify')) {
      final newParams = Map.of(uri.queryParameters)..remove('notify');
      html.window.history.pushState(
          null, null, uri.replace(queryParameters: newParams).toString());
      unawaited(devToolsServer?.notify());
    }

    // Handle onShowPageId.
    frameworkController.onShowPageId.listen((String pageId) {
      final screen = getScreen(pageId);
      if (screen != null) {
        load(screen);
      }
    });

    // Handle onConnectVmEvent.
    frameworkController.onConnectVmEvent.listen((ConnectVmEvent event) {
      // Reload the page with the new VM service URI in the querystring.
      // TODO(dantup): Remove this code and replace with code that just reconnects
      // (and optionally notifies based on requestParams['notify']) when it's
      // supported better (https://github.com/flutter/devtools/issues/989).
      //
      // This currently doesn't currently work, as the app does not reinitialize
      // correctly:
{'fraction_non_alphanumeric': 0.11556377585970681, 'fraction_numerical': 0.002192081107000959, 'mean_word_length': 3.2218045112781954, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2751488', 'n_tokens_mistral': 4324, 'n_tokens_neox': 4202, 'n_words': 1022}