Source: js/browser_context_menu.js

/* global MozActivity, IconsHelper, LazyLoader */
/* global applications, BaseModule */

(function(window) {
  'use strict';

  var _ = navigator.mozL10n.get;
  /**
   * The ContextMenu of the AppWindow.
   *
   * @class BrowserContextMenu
   * @param {AppWindow} app The app window instance
   *                        where this dialog should popup.
   */
  function BrowserContextMenu(app) {
    this.app = app;
    this.containerElement = app.element;
    this.app.element.addEventListener('mozbrowsercontextmenu', this);
    return this;
  }

  BrowserContextMenu.SUB_MODULES = [
    'ContextMenuView'
  ];

  BaseModule.create(BrowserContextMenu, {
    name: 'BrowserContextMenu',

    handleEvent: function(evt) {
      switch (evt.type) {
        case 'mozbrowsercontextmenu':
          this.show(evt);
          break;
      }
    },

    show: function(evt) {
      var detail = evt.detail;

      var hasContextMenu = detail.contextmenu &&
        detail.contextmenu.items.length > 0;
      var hasSystemTargets = detail.systemTargets &&
        detail.systemTargets.length > 0;

      // Nothing to show
      if (!hasSystemTargets && !hasContextMenu) {
        return;
      }

      // context menus in certified apps that only have system targets are
      // currently disabled. https://bugzil.la/1010160 is tracking reenabling
      if (!hasContextMenu && hasSystemTargets && this.app.isCertified()) {
        return;
      }

      var items = this._listItems(detail);

      if (!items.length) {
        return;
      }

      // Notify the embedder we are handling the context menu
      evt.preventDefault();
      evt.stopPropagation();
      this.contextMenuView.show(items);
    },

    _listItems: function(detail) {

      var items = [];

      // contextmenu.items are specified by the web content via html5
      // context menu api
      if (detail.contextmenu && detail.contextmenu.items.length) {
        detail.contextmenu.items.forEach(function(choice, index) {
          items.push({
            label: choice.label,
            icon: choice.icon,
            callback: function() {
              detail.contextMenuItemSelected(choice.id);
            }
          });
        }, this);
      }

      if (detail.systemTargets) {
        detail.systemTargets.forEach(function(item) {
          this.generateSystemMenuItem(item).forEach(function(menuItem) {
            items.push(menuItem);
          });
        }, this);
      }

      return items;
    },

    isShown: function() {
      return this.contextMenuView.isShown();
    },

    hide: function() {
      if (!this.contextMenuView.isShown()) {
        return;
      }

      this.contextMenuView.hide();
    },

    openUrl: function(url, isPrivate) {
      /* jshint nonew: false */
      new MozActivity({
        name: 'view',
        data: {
          type: 'url',
          url: url,
          isPrivate: isPrivate
        }
      });
    },

    shareUrl: function(url) {
      /* jshint nonew: false */
      new MozActivity({
        name: 'share',
        data: {
          type: 'url',
          url: url
        }
      });
    },

    bookmarkUrl: function(url, name) {
      var favicons = this.app.favicons;

      /* jshint nonew: false */
      var data = {
        type: 'url',
        url: url,
        name: name,
        iconable: false
      };

      if (this.app.webManifestURL) {
        data.manifestURL = this.app.webManifestURL;
      }

      LazyLoader.load('shared/js/icons_helper.js', (() => {
        IconsHelper.getIcon(url, null, {icons: favicons}).then(icon => {
          if (icon) {
            data.icon = icon;
          }
          new MozActivity({
            name: 'save-bookmark',
            data: data
          });
        });
      }));
    },

    newWindow: function(manifest, isPrivate) {
      // For private windows we create an empty private app window.
      if (isPrivate) {
        window.dispatchEvent(new CustomEvent('new-private-window'));
        return;
      }

      // Else we open up the browser.
      var newTabApp = applications.getByManifestURL(manifest);
      newTabApp.launch();
    },

    showWindows: function(manifest) {
      window.dispatchEvent(
        new CustomEvent('taskmanagershow',
                        { detail: { filter: 'browser-only' }})
      );
    },

    generateSystemMenuItem: function(item) {
      var nodeName = item.nodeName.toUpperCase();
      var documentURI = item.data.documentURI;
      var uri = item.data.uri;
      var text = item.data.text;

      switch (nodeName) {
        case 'A':
          return [{
            id: 'open-in-new-window',
            label: _('open-in-new-window'),
            callback: this.openUrl.bind(this, uri)
          }, {
            id: 'open-in-new-private-window',
            label: _('open-in-new-private-window'),
            callback: this.openUrl.bind(this, uri, true)
          }, {
            id: 'bookmark-link',
            label: _('add-link-to-home-screen'),
            callback: this.bookmarkUrl.bind(this, uri, text)
          }, {
            id: 'save-link',
            label: _('save-link'),
            callback: this.app.browser.element.download.bind(
              this.app.browser.element, uri, { referrer: documentURI })
          }, {
            id: 'share-link',
            label: _('share-link'),
            callback: this.shareUrl.bind(this, uri)
          }];

        case 'IMG':
        case 'VIDEO':
        case 'AUDIO':
          var typeMap = {
            'IMG': 'image',
            'VIDEO': 'video',
            'AUDIO': 'audio'
          };
          var type = typeMap[nodeName];
          if (nodeName === 'VIDEO' && !item.data.hasVideo) {
            type = 'audio';
          }

          return [{
            id: 'save-' + type,
            label: _('save-' + type),
            callback: this.app.browser.element.download.bind(
              this.app.browser.element, uri, { referrer: documentURI })
          }, {
            id: 'share-' + type,
            label: _('share-' + type),
            callback: this.shareUrl.bind(this, uri)
          }];

        default:
          return [];
      }
    },

    showDefaultMenu: function(manifest, name) {
      return new Promise((resolve) => {
        var config = this.app.config;
        var menuData = [];

        var finish = () => {
          this.contextMenuView.show(menuData);
          resolve();
        };

        menuData.push({
          id: 'new-window',
          label: _('new-window'),
          callback: this.newWindow.bind(this, manifest)
        });

        menuData.push({
          id: 'new-private-window',
          label: _('new-private-window'),
          callback: this.newWindow.bind(this, manifest, true)
        });

        menuData.push({
          id: 'show-windows',
          label: _('show-windows'),
          callback: this.showWindows.bind(this)
        });

        // Do not show the bookmark/share buttons if the url starts with app.
        // This is because in some cases we use the app chrome to view system
        // pages. E.g., private browsing.
        if (config.url.startsWith('app')) {
          finish();
          return;
        }

        menuData.push({
          id: 'add-to-homescreen',
          label: _('add-to-home-screen'),
          callback: this.bookmarkUrl.bind(this, config.url, name)
        });

        menuData.push({
          id: 'share',
          label: _('share'),
          callback: this.shareUrl.bind(this, config.url)
        });

        finish();
      });
    }
  });

  BrowserContextMenu.prototype.focus = function() {
    this.contextMenuView.focus();
  };

}(this));