1beddd0666885142d36d82e8935fd114c4b7f729
[platform/framework/web/crosswalk.git] / src / chrome / renderer / resources / extensions / web_view_experimental.js
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> Experimental API is only available on canary and dev channels of
9 // Chrome.
10
11 var ContextMenusSchema =
12     requireNative('schema_registry').GetSchema('contextMenus');
13 var CreateEvent = require('webView').CreateEvent;
14 var EventBindings = require('event_bindings');
15 var MessagingNatives = requireNative('messaging_natives');
16 var WebView = require('webView').WebView;
17 var WebViewInternal = require('webView').WebViewInternal;
18 var WebViewSchema = requireNative('schema_registry').GetSchema('webview');
19 var idGeneratorNatives = requireNative('id_generator');
20 var utils = require('utils');
21
22 // WEB_VIEW_EXPERIMENTAL_EVENTS is a map of experimental <webview> DOM event
23 //     names to their associated extension event descriptor objects.
24 // An event listener will be attached to the extension event |evt| specified in
25 //     the descriptor.
26 // |fields| specifies the public-facing fields in the DOM event that are
27 //     accessible to <webview> developers.
28 // |customHandler| allows a handler function to be called each time an extension
29 //     event is caught by its event listener. The DOM event should be dispatched
30 //     within this handler function. With no handler function, the DOM event
31 //     will be dispatched by default each time the extension event is caught.
32 // |cancelable| (default: false) specifies whether the event's default
33 //     behavior can be canceled. If the default action associated with the event
34 //     is prevented, then its dispatch function will return false in its event
35 //     handler. The event must have a custom handler for this to be meaningful.
36 var WEB_VIEW_EXPERIMENTAL_EVENTS = {
37   'findupdate': {
38     evt: CreateEvent('webview.onFindReply'),
39     fields: [
40       'searchText',
41       'numberOfMatches',
42       'activeMatchOrdinal',
43       'selectionRect',
44       'canceled',
45       'finalUpdate'
46     ]
47   },
48   'zoomchange': {
49     evt: CreateEvent('webview.onZoomChange'),
50     fields: ['oldZoomFactor', 'newZoomFactor']
51   }
52 };
53
54 function GetUniqueSubEventName(eventName) {
55   return eventName + "/" + idGeneratorNatives.GetNextId();
56 }
57
58 // This is the only "webview.onClicked" named event for this renderer.
59 //
60 // Since we need an event per <webview>, we define events with suffix
61 // (subEventName) in each of the <webview>. Behind the scenes, this event is
62 // registered as a ContextMenusEvent, with filter set to the webview's
63 // |viewInstanceId|. Any time a ContextMenusEvent is dispatched, we re-dispatch
64 // it to the subEvent's listeners. This way
65 // <webview>.contextMenus.onClicked behave as a regular chrome Event type.
66 var ContextMenusEvent = CreateEvent('webview.onClicked');
67
68 /**
69  * This event is exposed as <webview>.contextMenus.onClicked.
70  *
71  * @constructor
72  */
73 function ContextMenusOnClickedEvent(opt_eventName,
74                                     opt_argSchemas,
75                                     opt_eventOptions,
76                                     opt_webViewInstanceId) {
77   var subEventName = GetUniqueSubEventName(opt_eventName);
78   EventBindings.Event.call(this, subEventName, opt_argSchemas, opt_eventOptions,
79       opt_webViewInstanceId);
80
81   var self = this;
82   // TODO(lazyboy): When do we dispose this listener?
83   ContextMenusEvent.addListener(function() {
84     // Re-dispatch to subEvent's listeners.
85     $Function.apply(self.dispatch, self, $Array.slice(arguments));
86   }, {instanceId: opt_webViewInstanceId || 0});
87 }
88
89 ContextMenusOnClickedEvent.prototype = {
90   __proto__: EventBindings.Event.prototype
91 };
92
93 /**
94  * An instance of this class is exposed as <webview>.contextMenus.
95  * @constructor
96  */
97 function WebViewContextMenusImpl(viewInstanceId) {
98   this.viewInstanceId_ = viewInstanceId;
99 };
100
101 WebViewContextMenusImpl.prototype.create = function() {
102   var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
103   return $Function.apply(WebView.contextMenusCreate, null, args);
104 };
105
106 WebViewContextMenusImpl.prototype.remove = function() {
107   var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
108   return $Function.apply(WebView.contextMenusRemove, null, args);
109 };
110
111 WebViewContextMenusImpl.prototype.removeAll = function() {
112   var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
113   return $Function.apply(WebView.contextMenusRemoveAll, null, args);
114 };
115
116 WebViewContextMenusImpl.prototype.update = function() {
117   var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
118   return $Function.apply(WebView.contextMenusUpdate, null, args);
119 };
120
121 var WebViewContextMenus = utils.expose(
122     'WebViewContextMenus', WebViewContextMenusImpl,
123     ['create', 'remove', 'removeAll', 'update']);
124
125 /**
126  * @private
127  */
128 WebViewInternal.prototype.maybeAttachWebRequestEventToObject =
129     function(obj, eventName, webRequestEvent) {
130   Object.defineProperty(
131       obj,
132       eventName,
133       {
134         get: webRequestEvent,
135         enumerable: true
136       }
137   );
138 };
139
140 /**
141  * @private
142  */
143 WebViewInternal.prototype.setZoom = function(zoomFactor) {
144   if (!this.instanceId) {
145     return;
146   }
147   WebView.setZoom(this.instanceId, zoomFactor);
148 };
149
150 WebViewInternal.prototype.maybeGetExperimentalEvents = function() {
151   return WEB_VIEW_EXPERIMENTAL_EVENTS;
152 };
153
154 /** @private */
155 WebViewInternal.prototype.maybeGetExperimentalPermissions = function() {
156   return [];
157 };
158
159 /** @private */
160 WebViewInternal.prototype.maybeSetCurrentZoomFactor =
161     function(zoomFactor) {
162   this.currentZoomFactor = zoomFactor;
163 };
164
165 /** @private */
166 WebViewInternal.prototype.clearData = function() {
167   if (!this.instanceId) {
168     return;
169   }
170   var args = $Array.concat([this.instanceId], $Array.slice(arguments));
171   $Function.apply(WebView.clearData, null, args);
172 };
173
174 /** @private */
175 WebViewInternal.prototype.setZoom = function(zoomFactor, callback) {
176   if (!this.instanceId) {
177     return;
178   }
179   WebView.setZoom(this.instanceId, zoomFactor, callback);
180 };
181
182 WebViewInternal.prototype.getZoom = function(callback) {
183   if (!this.instanceId) {
184     return;
185   }
186   WebView.getZoom(this.instanceId, callback);
187 };
188
189 /** @private */
190 WebViewInternal.prototype.captureVisibleRegion = function(spec, callback) {
191   WebView.captureVisibleRegion(this.instanceId, spec, callback);
192 };
193
194 /** @private */
195 WebViewInternal.prototype.find = function(search_text, options, callback) {
196   if (!this.instanceId) {
197     return;
198   }
199   WebView.find(this.instanceId, search_text, options, callback);
200 };
201
202 /** @private */
203 WebViewInternal.prototype.stopFinding = function(action) {
204   if (!this.instanceId) {
205     return;
206   }
207   WebView.stopFinding(this.instanceId, action);
208 };
209
210 WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {
211   proto.clearData = function() {
212     var internal = privates(this).internal;
213     $Function.apply(internal.clearData, internal, arguments);
214   };
215
216   proto.setZoom = function(zoomFactor, callback) {
217     privates(this).internal.setZoom(zoomFactor, callback);
218   };
219
220   proto.getZoom = function(callback) {
221     return privates(this).internal.getZoom(callback);
222   };
223
224   proto.captureVisibleRegion = function(spec, callback) {
225     privates(this).internal.captureVisibleRegion(spec, callback);
226   };
227
228   proto.find = function(search_text, options, callback) {
229     privates(this).internal.find(search_text, options, callback);
230   };
231
232   proto.stopFinding = function(action) {
233     privates(this).internal.stopFinding(action);
234   };
235 };
236
237 /** @private */
238 WebViewInternal.prototype.setupExperimentalContextMenus_ = function() {
239   var self = this;
240   var createContextMenus = function() {
241     return function() {
242       if (self.contextMenus_) {
243         return self.contextMenus_;
244       }
245
246       self.contextMenus_ = new WebViewContextMenus(self.viewInstanceId);
247
248       // Define 'onClicked' event property on |self.contextMenus_|.
249       var getOnClickedEvent = function() {
250         return function() {
251           if (!self.contextMenusOnClickedEvent_) {
252             var eventName = 'webview.onClicked';
253             // TODO(lazyboy): Find event by name instead of events[0].
254             var eventSchema = WebViewSchema.events[0];
255             var eventOptions = {supportsListeners: true};
256             var onClickedEvent = new ContextMenusOnClickedEvent(
257                 eventName, eventSchema, eventOptions, self.viewInstanceId);
258             self.contextMenusOnClickedEvent_ = onClickedEvent;
259             return onClickedEvent;
260           }
261           return self.contextMenusOnClickedEvent_;
262         }
263       };
264       Object.defineProperty(
265           self.contextMenus_,
266           'onClicked',
267           {get: getOnClickedEvent(), enumerable: true});
268
269       return self.contextMenus_;
270     };
271   };
272
273   // Expose <webview>.contextMenus object.
274   Object.defineProperty(
275       this.webviewNode,
276       'contextMenus',
277       {
278         get: createContextMenus(),
279         enumerable: true
280       });
281 };