chromium/chrome/browser/resources/extensions/drag_and_drop_handler.ts

// Copyright 2016 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 {DragWrapperDelegate} from 'chrome://resources/js/drag_wrapper.js';

import {Service} from './service.js';


declare global {
  interface HTMLElementEventMap {
    'drag-and-drop-load-error':
        CustomEvent<Error|chrome.developerPrivate.LoadError>;
  }
}

export class DragAndDropHandler implements DragWrapperDelegate {
  dragEnabled: boolean;
  private eventTarget_: EventTarget;

  constructor(dragEnabled: boolean, target: EventTarget) {
    this.dragEnabled = dragEnabled;
    this.eventTarget_ = target;
  }

  shouldAcceptDrag(e: DragEvent): boolean {
    // External Extension installation can be disabled globally, e.g. while a
    // different overlay is already showing.
    if (!this.dragEnabled) {
      return false;
    }

    // We can't access filenames during the 'dragenter' event, so we have to
    // wait until 'drop' to decide whether to do something with the file or
    // not.
    // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
    return !!e.dataTransfer!.types &&
        e.dataTransfer!.types.indexOf('Files') > -1;
  }

  doDragEnter() {
    Service.getInstance().notifyDragInstallInProgress();
    this.eventTarget_.dispatchEvent(new CustomEvent('extension-drag-started'));
  }

  doDragLeave() {
    this.fireDragEnded_();
  }

  doDragOver(e: DragEvent) {
    e.preventDefault();
  }

  doDrop(e: DragEvent) {
    this.fireDragEnded_();
    if (e.dataTransfer!.files.length !== 1) {
      return;
    }

    let handled = false;

    // Files lack a check if they're a directory, but we can find out through
    // its item entry.
    const item = e.dataTransfer!.items[0];
    if (item.kind === 'file' && item.webkitGetAsEntry()!.isDirectory) {
      handled = true;
      this.handleDirectoryDrop_();
    } else if (/\.(crx|user\.js|zip)$/i.test(e.dataTransfer!.files[0].name)) {
      // Only process files that look like extensions. Other files should
      // navigate the browser normally.
      handled = true;
      this.handleFileDrop_();
    }

    if (handled) {
      e.preventDefault();
    }
  }

  /**
   * Handles a dropped file.
   */
  private handleFileDrop_() {
    Service.getInstance().installDroppedFile();
  }

  /**
   * Handles a dropped directory.
   */
  private handleDirectoryDrop_() {
    Service.getInstance().loadUnpackedFromDrag().catch(loadError => {
      this.eventTarget_.dispatchEvent(
          new CustomEvent('drag-and-drop-load-error', {detail: loadError}));
    });
  }

  private fireDragEnded_() {
    this.eventTarget_.dispatchEvent(new CustomEvent('extension-drag-ended'));
  }
}