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