chromium/chrome/test/data/webui/print_preview/native_layer_stub.ts

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

import type {CapabilitiesResponse, ExtensionDestinationInfo, LocalDestinationInfo, NativeInitialSettings, NativeLayer, PageLayoutInfo} from 'chrome://print/print_preview.js';
import {GooglePromotedDestinationId, PrinterType} from 'chrome://print/print_preview.js';
import {assert} from 'chrome://resources/js/assert.js';
import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';

import {getCddTemplate, getPdfPrinter} from './print_preview_test_utils.js';

/**
 * Test version of the native layer.
 */
export class NativeLayerStub extends TestBrowserProxy implements NativeLayer {
  /**
   * The initial settings to be used for the response to a |getInitialSettings|
   * call.
   */
  private initialSettings_: NativeInitialSettings|null = null;

  /**
   * Local destination list to be used for the response to |getPrinters|.
   */
  private localDestinationInfos_: LocalDestinationInfo[] = [];

  /**
   * Extension destination lists to be used for the response to |getPrinters|.
   */
  private extensionDestinationInfos_: ExtensionDestinationInfo[][] = [];

  /**
   *     A map from destination IDs to the responses to be sent when
   *     |getPrinterCapabilities| is called for the ID.
   */
  private localDestinationCapabilities_:
      Map<string, Promise<CapabilitiesResponse>> = new Map();

  private multipleCapabilitiesPromise_: PromiseResolver<void>|null = null;

  private multipleCapabilitiesCount_: number = 0;

  private multipleGetPrintersPromise_: PromiseResolver<void>|null = null;

  private multipleGetPrintersCount_: number = 0;

  /** The ID of a printer with a simulated bad driver. */
  private badPrinterId_: string = '';

  /** The number of total pages in the document. */
  private pageCount_: number = 1;

  private pageLayoutInfo_: PageLayoutInfo|null = null;

  /**
   * Rejects the promise for getPrinters() to simulate getting no response or a
   * a slow response from the backend.
   */
  private simulateNoResponseForGetPrinters_: boolean = false;

  constructor() {
    super([
      'dialogClose',
      'doPrint',
      'getInitialSettings',
      'getPrinters',
      'getPreview',
      'getPrinterCapabilities',
      'hidePreview',
      'managePrinters',
      'recordInHistogram',
      'saveAppState',
      'showSystemDialog',
    ]);
  }

  setPageCount(pageCount: number) {
    this.pageCount_ = pageCount;
  }

  dialogClose(isCancel: boolean) {
    this.methodCalled('dialogClose', isCancel);
  }

  getInitialSettings() {
    this.methodCalled('getInitialSettings');
    assert(this.initialSettings_);
    return Promise.resolve(this.initialSettings_);
  }

  getPrinters(type: PrinterType) {
    if (this.simulateNoResponseForGetPrinters_) {
      return Promise.reject();
    }

    this.methodCalled('getPrinters', type);
    if (this.multipleGetPrintersPromise_) {
      this.multipleGetPrintersCount_--;
      if (this.multipleGetPrintersCount_ === 0) {
        this.multipleGetPrintersPromise_.resolve();
        this.multipleGetPrintersPromise_ = null;
      }
    }

    if (type === PrinterType.LOCAL_PRINTER &&
        this.localDestinationInfos_.length > 0) {
      webUIListenerCallback(
          'printers-added', type, this.localDestinationInfos_);
    } else if (
        type === PrinterType.EXTENSION_PRINTER &&
        this.extensionDestinationInfos_.length > 0) {
      this.extensionDestinationInfos_.forEach(infoList => {
        webUIListenerCallback('printers-added', type, infoList);
      });
    }
    return Promise.resolve();
  }

  getPreview(printTicket: string) {
    this.methodCalled('getPreview', {printTicket: printTicket});
    const printTicketParsed = JSON.parse(printTicket);
    if (printTicketParsed.deviceName === this.badPrinterId_) {
      return Promise.reject('SETTINGS_INVALID');
    }
    const pageRanges = printTicketParsed.pageRange;
    const requestId = printTicketParsed.requestID;
    if (this.pageLayoutInfo_) {
      webUIListenerCallback(
          'page-layout-ready', this.pageLayoutInfo_, false, false);
    }
    if (pageRanges.length === 0) {  // assume full length document, 1 page.
      webUIListenerCallback(
          'page-count-ready', this.pageCount_, requestId, 100);
      for (let i = 0; i < this.pageCount_; i++) {
        webUIListenerCallback('page-preview-ready', i, 0, requestId);
      }
    } else {
      const pages = pageRanges.reduce(
          function(soFar: number[], range: {from: number, to: number}) {
            for (let page = range.from; page <= range.to; page++) {
              soFar.push(page);
            }
            return soFar;
          },
          []);
      webUIListenerCallback(
          'page-count-ready', this.pageCount_, requestId, 100);
      pages.forEach(function(page: number) {
        webUIListenerCallback('page-preview-ready', page - 1, 0, requestId);
      });
    }
    return Promise.resolve(requestId);
  }

  getPrinterCapabilities(printerId: string, type: PrinterType) {
    this.methodCalled(
        'getPrinterCapabilities',
        {destinationId: printerId, printerType: type});
    if (this.multipleCapabilitiesPromise_) {
      this.multipleCapabilitiesCount_--;
      if (this.multipleCapabilitiesCount_ === 0) {
        this.multipleCapabilitiesPromise_.resolve();
        this.multipleCapabilitiesPromise_ = null;
      }
    }
    if (printerId === GooglePromotedDestinationId.SAVE_AS_PDF) {
      return Promise.resolve(getPdfPrinter());
    }
    // <if expr="is_chromeos">
    if (printerId === GooglePromotedDestinationId.SAVE_TO_DRIVE_CROS) {
      return Promise.resolve(getPdfPrinter());
    }
    // </if>
    if (type !== PrinterType.LOCAL_PRINTER) {
      return Promise.reject();
    }
    return this.localDestinationCapabilities_.get(printerId) ||
        Promise.reject();
  }

  doPrint(printTicket: string) {
    this.methodCalled('doPrint', printTicket);
    return Promise.resolve(undefined);
  }

  hidePreview() {
    this.methodCalled('hidePreview');
  }

  showSystemDialog() {
    this.methodCalled('showSystemDialog');
  }

  recordInHistogram(histogram: string, bucket: number) {
    this.methodCalled('recordInHistogram', histogram, bucket);
  }

  recordBooleanHistogram() {}

  saveAppState(appState: string) {
    this.methodCalled('saveAppState', appState);
  }

  cancelPendingPrintRequest() {}

  managePrinters() {
    this.methodCalled('managePrinters');
  }

  /**
   * settings The settings to return as a response to |getInitialSettings|.
   */
  setInitialSettings(settings: NativeInitialSettings) {
    this.initialSettings_ = settings;
  }

  /**
   * @param localDestinations The local destinations to return as a response to
   *     |getPrinters|.
   */
  setLocalDestinations(localDestinations: LocalDestinationInfo[]) {
    this.localDestinationInfos_ = localDestinations;
    this.localDestinationCapabilities_ = new Map();
    this.localDestinationInfos_.forEach(info => {
      this.setLocalDestinationCapabilities({
        printer: info,
        capabilities:
            getCddTemplate(info.deviceName, info.printerName).capabilities,
      });
    });
  }

  /**
   * @param extensionDestinations The extension destinations to return as a
   *     response to |getPrinters|.
   */
  setExtensionDestinations(extensionDestinations:
                               ExtensionDestinationInfo[][]) {
    this.extensionDestinationInfos_ = extensionDestinations;
  }

  /**
   * @param response The response to send for the destination whose ID is in the
   *     response.
   * @param reject Whether to reject the callback for this destination.
   *     Defaults to false (will resolve callback) if not provided.
   */
  setLocalDestinationCapabilities(
      response: CapabilitiesResponse, reject?: boolean) {
    this.localDestinationCapabilities_.set(
        response.printer!.deviceName,
        reject ? Promise.reject() : Promise.resolve(response));
  }

  /**
   * @param id The printer ID that should cause an SETTINGS_INVALID error in
   *     response to a preview request. Models a bad printer driver.
   */
  setInvalidPrinterId(id: string) {
    this.badPrinterId_ = id;
  }

  setPageLayoutInfo(pageLayoutInfo: PageLayoutInfo) {
    this.pageLayoutInfo_ = pageLayoutInfo;
  }

  /**
   * @param count The number of capability requests to wait for.
   * @return Promise that resolves after |count| requests.
   */
  waitForMultipleCapabilities(count: number): Promise<void> {
    assert(this.multipleCapabilitiesPromise_ === null);
    this.multipleCapabilitiesCount_ = count;
    this.multipleCapabilitiesPromise_ = new PromiseResolver();
    return this.multipleCapabilitiesPromise_.promise;
  }

  /**
   * @param count The number of getPrinters requests to wait for.
   * @return Promise that resolves after |count| requests.
   */
  waitForGetPrinters(count: number): Promise<void> {
    assert(this.multipleGetPrintersPromise_ === null);
    this.multipleGetPrintersCount_ = count;
    this.multipleGetPrintersPromise_ = new PromiseResolver();
    return this.multipleGetPrintersPromise_.promise;
  }

  setSimulateNoResponseForGetPrinters(simulateNoResponseForGetPrinters:
                                          boolean) {
    this.simulateNoResponseForGetPrinters_ = simulateNoResponseForGetPrinters;
  }
}