1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// This module implements experimental API for <webview>. 6// See web_view.js for details. 7// 8// <webview> Chrome Experimental API is only available on canary and dev 9// channels of Chrome. 10 11var ContextMenusSchema = 12 requireNative('schema_registry').GetSchema('contextMenus'); 13var CreateEvent = require('webViewEvents').CreateEvent; 14var EventBindings = require('event_bindings'); 15var MessagingNatives = requireNative('messaging_natives'); 16//var WebView = require('webViewInternal').WebView; 17var ChromeWebView = require('chromeWebViewInternal').ChromeWebView; 18var WebViewInternal = require('webView').WebViewInternal; 19var ChromeWebViewSchema = 20 requireNative('schema_registry').GetSchema('chromeWebViewInternal'); 21var idGeneratorNatives = requireNative('id_generator'); 22var utils = require('utils'); 23 24function GetUniqueSubEventName(eventName) { 25 return eventName + "/" + idGeneratorNatives.GetNextId(); 26} 27 28// This is the only "webViewInternal.onClicked" named event for this renderer. 29// 30// Since we need an event per <webview>, we define events with suffix 31// (subEventName) in each of the <webview>. Behind the scenes, this event is 32// registered as a ContextMenusEvent, with filter set to the webview's 33// |viewInstanceId|. Any time a ContextMenusEvent is dispatched, we re-dispatch 34// it to the subEvent's listeners. This way 35// <webview>.contextMenus.onClicked behave as a regular chrome Event type. 36var ContextMenusEvent = CreateEvent('chromeWebViewInternal.onClicked'); 37 38/** 39 * This event is exposed as <webview>.contextMenus.onClicked. 40 * 41 * @constructor 42 */ 43function ContextMenusOnClickedEvent(opt_eventName, 44 opt_argSchemas, 45 opt_eventOptions, 46 opt_webViewInstanceId) { 47 var subEventName = GetUniqueSubEventName(opt_eventName); 48 EventBindings.Event.call(this, subEventName, opt_argSchemas, opt_eventOptions, 49 opt_webViewInstanceId); 50 51 // TODO(lazyboy): When do we dispose this listener? 52 ContextMenusEvent.addListener(function() { 53 // Re-dispatch to subEvent's listeners. 54 $Function.apply(this.dispatch, this, $Array.slice(arguments)); 55 }.bind(this), {instanceId: opt_webViewInstanceId || 0}); 56} 57 58ContextMenusOnClickedEvent.prototype = { 59 __proto__: EventBindings.Event.prototype 60}; 61 62/** 63 * An instance of this class is exposed as <webview>.contextMenus. 64 * @constructor 65 */ 66function WebViewContextMenusImpl(viewInstanceId) { 67 this.viewInstanceId_ = viewInstanceId; 68}; 69 70WebViewContextMenusImpl.prototype.create = function() { 71 var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments)); 72 return $Function.apply(ChromeWebView.contextMenusCreate, null, args); 73}; 74 75WebViewContextMenusImpl.prototype.remove = function() { 76 var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments)); 77 return $Function.apply(ChromeWebView.contextMenusRemove, null, args); 78}; 79 80WebViewContextMenusImpl.prototype.removeAll = function() { 81 var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments)); 82 return $Function.apply(ChromeWebView.contextMenusRemoveAll, null, args); 83}; 84 85WebViewContextMenusImpl.prototype.update = function() { 86 var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments)); 87 return $Function.apply(ChromeWebView.contextMenusUpdate, null, args); 88}; 89 90var WebViewContextMenus = utils.expose( 91 'WebViewContextMenus', WebViewContextMenusImpl, 92 { functions: ['create', 'remove', 'removeAll', 'update'] }); 93 94/** @private */ 95WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) { 96 var requestId = e.requestId; 97 // Construct the event.menu object. 98 var actionTaken = false; 99 var validateCall = function() { 100 var ERROR_MSG_CONTEXT_MENU_ACTION_ALREADY_TAKEN = '<webview>: ' + 101 'An action has already been taken for this "contextmenu" event.'; 102 103 if (actionTaken) { 104 throw new Error(ERROR_MSG_CONTEXT_MENU_ACTION_ALREADY_TAKEN); 105 } 106 actionTaken = true; 107 }; 108 var menu = { 109 show: function(items) { 110 validateCall(); 111 // TODO(lazyboy): WebViewShowContextFunction doesn't do anything useful 112 // with |items|, implement. 113 ChromeWebView.showContextMenu(this.guestInstanceId, requestId, items); 114 }.bind(this) 115 }; 116 webViewEvent.menu = menu; 117 var webviewNode = this.webviewNode; 118 var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent); 119 if (actionTaken) { 120 return; 121 } 122 if (!defaultPrevented) { 123 actionTaken = true; 124 // The default action is equivalent to just showing the context menu as is. 125 ChromeWebView.showContextMenu(this.guestInstanceId, requestId, undefined); 126 127 // TODO(lazyboy): Figure out a way to show warning message only when 128 // listeners are registered for this event. 129 } // else we will ignore showing the context menu completely. 130}; 131 132/** @private */ 133WebViewInternal.prototype.setupExperimentalContextMenus = function() { 134 var createContextMenus = function() { 135 return function() { 136 if (this.contextMenus_) { 137 return this.contextMenus_; 138 } 139 140 this.contextMenus_ = new WebViewContextMenus(this.viewInstanceId); 141 142 // Define 'onClicked' event property on |this.contextMenus_|. 143 var getOnClickedEvent = function() { 144 return function() { 145 if (!this.contextMenusOnClickedEvent_) { 146 var eventName = 'chromeWebViewInternal.onClicked'; 147 // TODO(lazyboy): Find event by name instead of events[0]. 148 var eventSchema = ChromeWebViewSchema.events[0]; 149 var eventOptions = {supportsListeners: true}; 150 var onClickedEvent = new ContextMenusOnClickedEvent( 151 eventName, eventSchema, eventOptions, this.viewInstanceId); 152 this.contextMenusOnClickedEvent_ = onClickedEvent; 153 return onClickedEvent; 154 } 155 return this.contextMenusOnClickedEvent_; 156 }.bind(this); 157 }.bind(this); 158 Object.defineProperty( 159 this.contextMenus_, 160 'onClicked', 161 {get: getOnClickedEvent(), enumerable: true}); 162 163 return this.contextMenus_; 164 }.bind(this); 165 }.bind(this); 166 167 // Expose <webview>.contextMenus object. 168 Object.defineProperty( 169 this.webviewNode, 170 'contextMenus', 171 { 172 get: createContextMenus(), 173 enumerable: true 174 }); 175}; 176