12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// This module implements experimental API for <webview>.
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// See web_view.js for details.
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)//
81320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci// <webview> Chrome Experimental API is only available on canary and dev
91320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci// channels of Chrome.
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)var ContextMenusSchema =
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    requireNative('schema_registry').GetSchema('contextMenus');
136d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)var CreateEvent = require('webViewEvents').CreateEvent;
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)var EventBindings = require('event_bindings');
15424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)var MessagingNatives = requireNative('messaging_natives');
161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci//var WebView = require('webViewInternal').WebView;
171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivar ChromeWebView = require('chromeWebViewInternal').ChromeWebView;
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)var WebViewInternal = require('webView').WebViewInternal;
191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivar ChromeWebViewSchema =
201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    requireNative('schema_registry').GetSchema('chromeWebViewInternal');
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)var idGeneratorNatives = requireNative('id_generator');
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)var utils = require('utils');
23868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)function GetUniqueSubEventName(eventName) {
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return eventName + "/" + idGeneratorNatives.GetNextId();
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
27a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
28116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch// This is the only "webViewInternal.onClicked" named event for this renderer.
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)//
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// Since we need an event per <webview>, we define events with suffix
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// (subEventName) in each of the <webview>. Behind the scenes, this event is
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// registered as a ContextMenusEvent, with filter set to the webview's
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// |viewInstanceId|. Any time a ContextMenusEvent is dispatched, we re-dispatch
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// it to the subEvent's listeners. This way
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)// <webview>.contextMenus.onClicked behave as a regular chrome Event type.
361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivar ContextMenusEvent = CreateEvent('chromeWebViewInternal.onClicked');
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)/**
39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * This event is exposed as <webview>.contextMenus.onClicked.
40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) *
41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @constructor
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)function ContextMenusOnClickedEvent(opt_eventName,
44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    opt_argSchemas,
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    opt_eventOptions,
46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                                    opt_webViewInstanceId) {
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  var subEventName = GetUniqueSubEventName(opt_eventName);
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  EventBindings.Event.call(this, subEventName, opt_argSchemas, opt_eventOptions,
49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      opt_webViewInstanceId);
50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // TODO(lazyboy): When do we dispose this listener?
52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  ContextMenusEvent.addListener(function() {
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    // Re-dispatch to subEvent's listeners.
541320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    $Function.apply(this.dispatch, this, $Array.slice(arguments));
551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }.bind(this), {instanceId: opt_webViewInstanceId || 0});
56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)ContextMenusOnClickedEvent.prototype = {
59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  __proto__: EventBindings.Event.prototype
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)/**
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * An instance of this class is exposed as <webview>.contextMenus.
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) * @constructor
65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) */
66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)function WebViewContextMenusImpl(viewInstanceId) {
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  this.viewInstanceId_ = viewInstanceId;
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)WebViewContextMenusImpl.prototype.create = function() {
71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
721320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return $Function.apply(ChromeWebView.contextMenusCreate, null, args);
73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)WebViewContextMenusImpl.prototype.remove = function() {
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
771320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return $Function.apply(ChromeWebView.contextMenusRemove, null, args);
78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
80a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)WebViewContextMenusImpl.prototype.removeAll = function() {
81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return $Function.apply(ChromeWebView.contextMenusRemoveAll, null, args);
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
85a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)WebViewContextMenusImpl.prototype.update = function() {
86a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
871320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return $Function.apply(ChromeWebView.contextMenusUpdate, null, args);
88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)var WebViewContextMenus = utils.expose(
91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    'WebViewContextMenus', WebViewContextMenusImpl,
920529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    { functions: ['create', 'remove', 'removeAll', 'update'] });
93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
94010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)/** @private */
95010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) {
96010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  var requestId = e.requestId;
97010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  // Construct the event.menu object.
98010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  var actionTaken = false;
99010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  var validateCall = function() {
100010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    var ERROR_MSG_CONTEXT_MENU_ACTION_ALREADY_TAKEN = '<webview>: ' +
101010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        'An action has already been taken for this "contextmenu" event.';
102010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
103010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    if (actionTaken) {
104010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      throw new Error(ERROR_MSG_CONTEXT_MENU_ACTION_ALREADY_TAKEN);
105010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    }
106010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    actionTaken = true;
107010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  };
108010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  var menu = {
109010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    show: function(items) {
110010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      validateCall();
111010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      // TODO(lazyboy): WebViewShowContextFunction doesn't do anything useful
112010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      // with |items|, implement.
1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      ChromeWebView.showContextMenu(this.guestInstanceId, requestId, items);
1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }.bind(this)
115010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  };
116010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  webViewEvent.menu = menu;
117010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  var webviewNode = this.webviewNode;
118010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent);
119010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  if (actionTaken) {
120010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    return;
121010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  }
122010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  if (!defaultPrevented) {
123010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    actionTaken = true;
124010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    // The default action is equivalent to just showing the context menu as is.
1251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    ChromeWebView.showContextMenu(this.guestInstanceId, requestId, undefined);
126010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
127010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    // TODO(lazyboy): Figure out a way to show warning message only when
128010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    // listeners are registered for this event.
129010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  } //  else we will ignore showing the context menu completely.
130010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)};
131010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)/** @private */
1336d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)WebViewInternal.prototype.setupExperimentalContextMenus = function() {
134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  var createContextMenus = function() {
135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return function() {
1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if (this.contextMenus_) {
1371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        return this.contextMenus_;
138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      }
139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      this.contextMenus_ = new WebViewContextMenus(this.viewInstanceId);
141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      // Define 'onClicked' event property on |this.contextMenus_|.
143a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      var getOnClickedEvent = function() {
144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return function() {
1451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          if (!this.contextMenusOnClickedEvent_) {
1461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            var eventName = 'chromeWebViewInternal.onClicked';
147a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            // TODO(lazyboy): Find event by name instead of events[0].
1481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            var eventSchema = ChromeWebViewSchema.events[0];
149a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            var eventOptions = {supportsListeners: true};
150a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            var onClickedEvent = new ContextMenusOnClickedEvent(
1511320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                eventName, eventSchema, eventOptions, this.viewInstanceId);
1521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci            this.contextMenusOnClickedEvent_ = onClickedEvent;
153a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            return onClickedEvent;
154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          }
1551320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          return this.contextMenusOnClickedEvent_;
1561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        }.bind(this);
1571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      }.bind(this);
158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      Object.defineProperty(
1591320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci          this.contextMenus_,
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          'onClicked',
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          {get: getOnClickedEvent(), enumerable: true});
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
1631320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      return this.contextMenus_;
1641320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    }.bind(this);
1651320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }.bind(this);
166a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
167a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // Expose <webview>.contextMenus object.
168a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  Object.defineProperty(
169a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      this.webviewNode,
170a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      'contextMenus',
171a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      {
172a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        get: createContextMenus(),
173a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        enumerable: true
174a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      });
1753551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)};
176