chromium/chrome/renderer/resources/extensions/web_view/chrome_web_view.js

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

// This module implements chrome-specific <webview> API.
// See web_view_api_methods.js for details.

var ChromeWebView = getInternalApi('chromeWebViewInternal');
var ChromeWebViewSchema =
    requireNative('schema_registry').GetSchema('chromeWebViewInternal');
var CreateEvent = require('guestViewEvents').CreateEvent;
var GuestViewInternalNatives = requireNative('guest_view_internal');
var idGeneratorNatives = requireNative('id_generator');
var utils = require('utils');
var WebViewImpl = require('webView').WebViewImpl;

// This is the only "webViewInternal.onClicked" named event for this renderer.
//
// Since we need an event per <webview>, we define events with suffix
// (subEventName) in each of the <webview>. Behind the scenes, this event is
// registered as a ContextMenusEvent, with filter set to the webview's
// |viewInstanceId|. Any time a ContextMenusEvent is dispatched, we re-dispatch
// it to the subEvent's listeners. This way
// <webview>.contextMenus.onClicked behave as a regular chrome Event type.
var ContextMenusEvent = CreateEvent('chromeWebViewInternal.onClicked');
// See comment above.
var ContextMenusHandlerEvent =
    CreateEvent('chromeWebViewInternal.onContextMenuShow');

function GetUniqueSubEventName(eventName) {
  return eventName + '/' + idGeneratorNatives.GetNextId();
}

// This event is exposed as <webview>.contextMenus.onClicked.
function createContextMenusOnClickedEvent(webViewInstanceId,
                                          opt_eventName,
                                          opt_argSchemas,
                                          opt_eventOptions) {
  var subEventName = GetUniqueSubEventName(opt_eventName);
  var newEvent =
      bindingUtil.createCustomEvent(subEventName, false, false);

  var view = GuestViewInternalNatives.GetViewFromID(webViewInstanceId);
  if (view) {
    view.events.addScopedListener(
        ContextMenusEvent,
        $Function.bind(function() {
          // Re-dispatch to subEvent's listeners.
          $Function.apply(newEvent.dispatch, newEvent, $Array.slice(arguments));
        }, newEvent),
        {instanceId: webViewInstanceId});
  }
  return newEvent;
}

// This event is exposed as <webview>.contextMenus.onShow.
function createContextMenusOnContextMenuEvent(webViewInstanceId,
                                              opt_eventName,
                                              opt_argSchemas,
                                              opt_eventOptions) {
  var subEventName = GetUniqueSubEventName(opt_eventName);
  var newEvent =
      bindingUtil.createCustomEvent(subEventName, false, false);

  var view = GuestViewInternalNatives.GetViewFromID(webViewInstanceId);
  if (view) {
    view.events.addScopedListener(
        ContextMenusHandlerEvent,
        $Function.bind(function(e) {
          var defaultPrevented = false;
          var event = {
            preventDefault: function() { defaultPrevented = true; }
          };

          // Re-dispatch to subEvent's listeners.
          $Function.apply(newEvent.dispatch, newEvent, [event]);

          if (!defaultPrevented) {
          // TODO(lazyboy): Remove |items| parameter completely from
          // ChromeWebView.showContextMenu as we don't do anything useful with
          // it currently.
          var items = [];
          var guestInstanceId = GuestViewInternalNatives.
              GetViewFromID(webViewInstanceId).guest.getId();
          ChromeWebView.showContextMenu(guestInstanceId, e.requestId, items);
        }
      }, newEvent),
      {instanceId: webViewInstanceId});
  }

  return newEvent;
}

// -----------------------------------------------------------------------------
// WebViewContextMenusImpl object.

// An instance of this class is exposed as <webview>.contextMenus.
function WebViewContextMenusImpl(viewInstanceId) {
  this.viewInstanceId_ = viewInstanceId;
}
$Object.setPrototypeOf(WebViewContextMenusImpl.prototype, null);

WebViewContextMenusImpl.prototype.create = function() {
  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
  return $Function.apply(ChromeWebView.contextMenusCreate, null, args);
};

WebViewContextMenusImpl.prototype.remove = function() {
  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
  return $Function.apply(ChromeWebView.contextMenusRemove, null, args);
};

WebViewContextMenusImpl.prototype.removeAll = function() {
  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
  return $Function.apply(ChromeWebView.contextMenusRemoveAll, null, args);
};

WebViewContextMenusImpl.prototype.update = function() {
  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
  return $Function.apply(ChromeWebView.contextMenusUpdate, null, args);
};

function WebViewContextMenus() {
  privates(WebViewContextMenus).constructPrivate(this, arguments);
}
utils.expose(WebViewContextMenus, WebViewContextMenusImpl, {
  functions: [
    'create',
    'remove',
    'removeAll',
    'update',
  ],
});

// -----------------------------------------------------------------------------

class ChromeWebViewImpl extends WebViewImpl {
  constructor(webviewElement) {
    super(webviewElement);
    this.setupContextMenus();
  }
}

ChromeWebViewImpl.prototype.createWebViewContextMenus = function () {
  return new WebViewContextMenus(this.viewInstanceId);
}

ChromeWebViewImpl.prototype.setupContextMenus = function() {
  if (!this.contextMenusOnContextMenuEvent_) {
    var eventName = 'chromeWebViewInternal.onContextMenuShow';
    var eventSchema =
        utils.lookup(ChromeWebViewSchema.events, 'name', 'onShow');
    var eventOptions = {supportsListeners: true, supportsLazyListeners: false};
    this.contextMenusOnContextMenuEvent_ = createContextMenusOnContextMenuEvent(
        this.viewInstanceId, eventName, eventSchema, eventOptions);
  }

  var createContextMenus = $Function.bind(function() {
    return this.weakWrapper(function() {
      if (this.contextMenus_) {
        return this.contextMenus_;
      }

      this.contextMenus_ = this.createWebViewContextMenus();

      // Define 'onClicked' event property on |this.contextMenus_|.
      var getOnClickedEvent = $Function.bind(function() {
        return this.weakWrapper(function() {
          if (!this.contextMenusOnClickedEvent_) {
            var eventName = 'chromeWebViewInternal.onClicked';
            var eventSchema =
                utils.lookup(ChromeWebViewSchema.events, 'name', 'onClicked');
            var eventOptions =
                {supportsListeners: true, supportsLazyListeners: false};
            var onClickedEvent = createContextMenusOnClickedEvent(
                this.viewInstanceId, eventName, eventSchema, eventOptions);
            this.contextMenusOnClickedEvent_ = onClickedEvent;
            return onClickedEvent;
          }
          return this.contextMenusOnClickedEvent_;
        });
      }, this);
      $Object.defineProperty(
          this.contextMenus_,
          'onClicked',
          {get: getOnClickedEvent(), enumerable: true});
      $Object.defineProperty(
          this.contextMenus_,
          'onShow',
          {
            get: this.weakWrapper(function() {
              return this.contextMenusOnContextMenuEvent_;
            }),
            enumerable: true
          });
      return this.contextMenus_;
    });
  }, this);

  // Expose <webview>.contextMenus object.
  $Object.defineProperty(
      this.element,
      'contextMenus',
      {
        get: createContextMenus(),
        enumerable: true
      });
};

exports.$set('WebViewContextMenusImpl', WebViewContextMenusImpl);
exports.$set('ChromeWebViewImpl', ChromeWebViewImpl);