blob: d8628e0966e524655e08b8ea481f9be0d803dfea [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This module implements experimental API for <webview>.
// See web_view.js for details.
//
// <webview> Experimental API is only available on canary and dev channels of
// Chrome.
var ContextMenusSchema =
requireNative('schema_registry').GetSchema('contextMenus');
var CreateEvent = require('webViewEvents').CreateEvent;
var EventBindings = require('event_bindings');
var MessagingNatives = requireNative('messaging_natives');
var WebView = require('webViewInternal').WebView;
var WebViewInternal = require('webView').WebViewInternal;
var WebViewSchema =
requireNative('schema_registry').GetSchema('webViewInternal');
var idGeneratorNatives = requireNative('id_generator');
var utils = require('utils');
// WEB_VIEW_EXPERIMENTAL_EVENTS is a map of experimental <webview> DOM event
// names to their associated extension event descriptor objects.
// An event listener will be attached to the extension event |evt| specified in
// the descriptor.
// |fields| specifies the public-facing fields in the DOM event that are
// accessible to <webview> developers.
// |customHandler| allows a handler function to be called each time an extension
// event is caught by its event listener. The DOM event should be dispatched
// within this handler function. With no handler function, the DOM event
// will be dispatched by default each time the extension event is caught.
// |cancelable| (default: false) specifies whether the event's default
// behavior can be canceled. If the default action associated with the event
// is prevented, then its dispatch function will return false in its event
// handler. The event must have a custom handler for this to be meaningful.
var WEB_VIEW_EXPERIMENTAL_EVENTS = {
'findupdate': {
evt: CreateEvent('webViewInternal.onFindReply'),
fields: [
'searchText',
'numberOfMatches',
'activeMatchOrdinal',
'selectionRect',
'canceled',
'finalUpdate'
]
},
'zoomchange': {
evt: CreateEvent('webViewInternal.onZoomChange'),
fields: ['oldZoomFactor', 'newZoomFactor']
}
};
function GetUniqueSubEventName(eventName) {
return eventName + "/" + idGeneratorNatives.GetNextId();
}
// 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('webViewInternal.onClicked');
/**
* This event is exposed as <webview>.contextMenus.onClicked.
*
* @constructor
*/
function ContextMenusOnClickedEvent(opt_eventName,
opt_argSchemas,
opt_eventOptions,
opt_webViewInstanceId) {
var subEventName = GetUniqueSubEventName(opt_eventName);
EventBindings.Event.call(this, subEventName, opt_argSchemas, opt_eventOptions,
opt_webViewInstanceId);
var self = this;
// TODO(lazyboy): When do we dispose this listener?
ContextMenusEvent.addListener(function() {
// Re-dispatch to subEvent's listeners.
$Function.apply(self.dispatch, self, $Array.slice(arguments));
}, {instanceId: opt_webViewInstanceId || 0});
}
ContextMenusOnClickedEvent.prototype = {
__proto__: EventBindings.Event.prototype
};
/**
* An instance of this class is exposed as <webview>.contextMenus.
* @constructor
*/
function WebViewContextMenusImpl(viewInstanceId) {
this.viewInstanceId_ = viewInstanceId;
};
WebViewContextMenusImpl.prototype.create = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(WebView.contextMenusCreate, null, args);
};
WebViewContextMenusImpl.prototype.remove = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(WebView.contextMenusRemove, null, args);
};
WebViewContextMenusImpl.prototype.removeAll = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(WebView.contextMenusRemoveAll, null, args);
};
WebViewContextMenusImpl.prototype.update = function() {
var args = $Array.concat([this.viewInstanceId_], $Array.slice(arguments));
return $Function.apply(WebView.contextMenusUpdate, null, args);
};
var WebViewContextMenus = utils.expose(
'WebViewContextMenus', WebViewContextMenusImpl,
{ functions: ['create', 'remove', 'removeAll', 'update'] });
/** @private */
WebViewInternal.prototype.maybeHandleContextMenu = function(e, webViewEvent) {
var requestId = e.requestId;
var self = this;
// Construct the event.menu object.
var actionTaken = false;
var validateCall = function() {
var ERROR_MSG_CONTEXT_MENU_ACTION_ALREADY_TAKEN = '<webview>: ' +
'An action has already been taken for this "contextmenu" event.';
if (actionTaken) {
throw new Error(ERROR_MSG_CONTEXT_MENU_ACTION_ALREADY_TAKEN);
}
actionTaken = true;
};
var menu = {
show: function(items) {
validateCall();
// TODO(lazyboy): WebViewShowContextFunction doesn't do anything useful
// with |items|, implement.
WebView.showContextMenu(self.instanceId, requestId, items);
}
};
webViewEvent.menu = menu;
var webviewNode = this.webviewNode;
var defaultPrevented = !webviewNode.dispatchEvent(webViewEvent);
if (actionTaken) {
return;
}
if (!defaultPrevented) {
actionTaken = true;
// The default action is equivalent to just showing the context menu as is.
WebView.showContextMenu(self.instanceId, requestId, undefined);
// TODO(lazyboy): Figure out a way to show warning message only when
// listeners are registered for this event.
} // else we will ignore showing the context menu completely.
};
/**
* @private
*/
WebViewInternal.prototype.setZoom = function(zoomFactor) {
if (!this.instanceId) {
return;
}
WebView.setZoom(this.instanceId, zoomFactor);
};
WebViewInternal.prototype.maybeGetExperimentalEvents = function() {
return WEB_VIEW_EXPERIMENTAL_EVENTS;
};
/** @private */
WebViewInternal.prototype.maybeGetExperimentalPermissions = function() {
return [];
};
/** @private */
WebViewInternal.prototype.maybeSetCurrentZoomFactor =
function(zoomFactor) {
this.currentZoomFactor = zoomFactor;
};
/** @private */
WebViewInternal.prototype.setZoom = function(zoomFactor, callback) {
if (!this.instanceId) {
return;
}
WebView.setZoom(this.instanceId, zoomFactor, callback);
};
WebViewInternal.prototype.getZoom = function(callback) {
if (!this.instanceId) {
return;
}
WebView.getZoom(this.instanceId, callback);
};
/** @private */
WebViewInternal.prototype.captureVisibleRegion = function(spec, callback) {
WebView.captureVisibleRegion(this.instanceId, spec, callback);
};
/** @private */
WebViewInternal.prototype.find = function(search_text, options, callback) {
if (!this.instanceId) {
return;
}
WebView.find(this.instanceId, search_text, options, callback);
};
/** @private */
WebViewInternal.prototype.stopFinding = function(action) {
if (!this.instanceId) {
return;
}
WebView.stopFinding(this.instanceId, action);
};
WebViewInternal.maybeRegisterExperimentalAPIs = function(proto) {
proto.setZoom = function(zoomFactor, callback) {
privates(this).internal.setZoom(zoomFactor, callback);
};
proto.getZoom = function(callback) {
return privates(this).internal.getZoom(callback);
};
proto.captureVisibleRegion = function(spec, callback) {
privates(this).internal.captureVisibleRegion(spec, callback);
};
proto.find = function(search_text, options, callback) {
privates(this).internal.find(search_text, options, callback);
};
proto.stopFinding = function(action) {
privates(this).internal.stopFinding(action);
};
};
/** @private */
WebViewInternal.prototype.setupExperimentalContextMenus = function() {
var self = this;
var createContextMenus = function() {
return function() {
if (self.contextMenus_) {
return self.contextMenus_;
}
self.contextMenus_ = new WebViewContextMenus(self.viewInstanceId);
// Define 'onClicked' event property on |self.contextMenus_|.
var getOnClickedEvent = function() {
return function() {
if (!self.contextMenusOnClickedEvent_) {
var eventName = 'webViewInternal.onClicked';
// TODO(lazyboy): Find event by name instead of events[0].
var eventSchema = WebViewSchema.events[0];
var eventOptions = {supportsListeners: true};
var onClickedEvent = new ContextMenusOnClickedEvent(
eventName, eventSchema, eventOptions, self.viewInstanceId);
self.contextMenusOnClickedEvent_ = onClickedEvent;
return onClickedEvent;
}
return self.contextMenusOnClickedEvent_;
}
};
Object.defineProperty(
self.contextMenus_,
'onClicked',
{get: getOnClickedEvent(), enumerable: true});
return self.contextMenus_;
};
};
// Expose <webview>.contextMenus object.
Object.defineProperty(
this.webviewNode,
'contextMenus',
{
get: createContextMenus(),
enumerable: true
});
};