chromium/chrome/browser/resources/browser_switch/app.ts

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'chrome://resources/cr_elements/cr_icon/cr_icon.js';
import 'chrome://resources/cr_elements/icons_lit.html.js';
import './strings.m.js';

import {I18nMixinLit} from 'chrome://resources/cr_elements/i18n_mixin_lit.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';

import {getCss} from './app.css.js';
import {getHtml} from './app.html.js';
import type {BrowserSwitchProxy} from './browser_switch_proxy.js';
import {BrowserSwitchProxyImpl} from './browser_switch_proxy.js';

const MS_PER_SECOND: number = 1000;

enum LaunchError {
  GENERIC_ERROR = 'genericError',
  PROTOCOL_ERROR = 'protocolError',
}

const BrowserSwitchAppElementBase = I18nMixinLit(CrLitElement);

export class BrowserSwitchAppElement extends BrowserSwitchAppElementBase {
  static get is() {
    return 'browser-switch-app';
  }

  static override get styles() {
    return getCss();
  }

  override render() {
    return getHtml.bind(this)();
  }

  static override get properties() {
    return {
      /**
       * URL to launch in the alternative browser.
       */
      url_: {type: String},

      /**
       * Error message, or empty string if no error has occurred (yet).
       */
      error_: {type: String},

      /**
       * Countdown displayed to the user, number of seconds until launching. If
       * 0 or less, doesn't get displayed at all.
       */
      secondCounter_: {type: Number},
    };
  }

  private url_: string =
      new URLSearchParams(window.location.search).get('url') || '';
  private error_: string = '';
  private secondCounter_: number = 0;

  override connectedCallback() {
    super.connectedCallback();

    // If '?done=...' is specified in the URL, this tab was-reopened, or the
    // entire browser was closed by LBS and re-opened. In that case, go to NTP
    // instead.
    const done = (new URLSearchParams(window.location.search)).has('done');
    if (done) {
      getProxy().gotoNewTabPage();
      return;
    }

    // Sanity check the URL to make sure nothing really funky is going on.
    const anchor = document.createElement('a');
    anchor.href = this.url_;
    if (!/^(file|http|https):$/.test(anchor.protocol)) {
      this.error_ = LaunchError.PROTOCOL_ERROR;
      return;
    }

    const milliseconds = loadTimeData.getInteger('launchDelay');
    setTimeout(this.launchAndCloseTab_.bind(this), milliseconds);
    this.startCountdown_(Math.floor(milliseconds / 1000));
  }

  private launchAndCloseTab_() {
    // Mark this page with '?done=...' so that restoring the tab doesn't
    // immediately re-trigger LBS.
    history.pushState({}, '', '/?done=true');

    getProxy().launchAlternativeBrowserAndCloseTab(this.url_).catch(() => {
      this.error_ = LaunchError.GENERIC_ERROR;
    });
  }

  private startCountdown_(seconds: number) {
    this.secondCounter_ = seconds;
    const intervalId = setInterval(() => {
      this.secondCounter_--;
      if (this.secondCounter_ <= 0) {
        clearInterval(intervalId);
      }
    }, 1 * MS_PER_SECOND);
  }

  protected computeTitle_(): string {
    if (this.error_) {
      return this.i18n('errorTitle', getAltBrowserName());
    }
    if (this.secondCounter_ > 0) {
      return this.i18n(
          'countdownTitle', this.secondCounter_, getAltBrowserName());
    }
    return this.i18n('openingTitle', getAltBrowserName());
  }

  protected computeDescription_(): TrustedHTML {
    if (this.error_) {
      return this.i18nAdvanced(this.error_, {
        substitutions: [getUrlHostname(this.url_), getAltBrowserName()],
      });
    }
    return this.i18nAdvanced('description', {
      substitutions: [getUrlHostname(this.url_), getAltBrowserName()],
    });
  }
}

customElements.define(BrowserSwitchAppElement.is, BrowserSwitchAppElement);

function getAltBrowserName(): string {
  return loadTimeData.getString('altBrowserName');
}

function getUrlHostname(url: string): string {
  const anchor = document.createElement('a');
  anchor.href = url;
  // Return entire url if parsing failed (which means the URL is bogus).
  return anchor.hostname || encodeURI(url);
}

function getProxy(): BrowserSwitchProxy {
  return BrowserSwitchProxyImpl.getInstance();
}