Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / extensions / renderer / resources / guest_view / guest_view_events.js
blobfda0db1adc60a6486b537a39c25590db91a31d08
1 // Copyright 2015 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.
5 // Event management for GuestViewContainers.
7 var EventBindings = require('event_bindings');
8 var GuestViewInternalNatives = requireNative('guest_view_internal');
9 var MessagingNatives = requireNative('messaging_natives');
11 var CreateEvent = function(name) {
12   var eventOpts = {supportsListeners: true, supportsFilters: true};
13   return new EventBindings.Event(name, undefined, eventOpts);
16 function GuestViewEvents(view) {
17   view.events = this;
19   this.view = view;
20   this.on = {};
22   // |setupEventProperty| is normally called automatically, but these events are
23   // are registered here because they are dispatched from GuestViewContainer
24   // instead of in response to extension events.
25   this.setupEventProperty('contentresize');
26   this.setupEventProperty('resize');
27   this.setupEvents();
30 // |GuestViewEvents.EVENTS| is a dictionary of extension events to be listened
31 //     for, which specifies how each event should be handled. The events are
32 //     organized by name, and by default will be dispatched as DOM events with
33 //     the same name.
34 // |cancelable| (default: false) specifies whether the DOM event's default
35 //     behavior can be canceled. If the default action associated with the event
36 //     is prevented, then its dispatch function will return false in its event
37 //     handler. The event must have a specified |handler| for this to be
38 //     meaningful.
39 // |evt| specifies a descriptor object for the extension event. An event
40 //     listener will be attached to this descriptor.
41 // |fields| (default: none) specifies the public-facing fields in the DOM event
42 //     that are accessible to developers.
43 // |handler| specifies the name of a handler function to be called each time
44 //     that extension event is caught by its event listener. The DOM event
45 //     should be dispatched within this handler function (if desired). With no
46 //     handler function, the DOM event will be dispatched by default each time
47 //     the extension event is caught.
48 // |internal| (default: false) specifies that the event will not be dispatched
49 //     as a DOM event, and will also not appear as an on* property on the view’s
50 //     element. A |handler| should be specified for all internal events, and
51 //     |fields| and |cancelable| should be left unspecified (as they are only
52 //     meaningful for DOM events).
53 GuestViewEvents.EVENTS = {};
55 // Attaches |listener| onto the event descriptor object |evt|, and registers it
56 // to be removed once this GuestViewEvents object is garbage collected.
57 GuestViewEvents.prototype.addScopedListener = function(
58     evt, listener, listenerOpts) {
59   this.listenersToBeRemoved.push({ 'evt': evt, 'listener': listener });
60   evt.addListener(listener, listenerOpts);
63 // Sets up the handling of events.
64 GuestViewEvents.prototype.setupEvents = function() {
65   // An array of registerd event listeners that should be removed when this
66   // GuestViewEvents is garbage collected.
67   this.listenersToBeRemoved = [];
68   MessagingNatives.BindToGC(this, function(listenersToBeRemoved) {
69     for (var i = 0; i != listenersToBeRemoved.length; ++i) {
70       listenersToBeRemoved[i].evt.removeListener(
71           listenersToBeRemoved[i].listener);
72       listenersToBeRemoved[i] = null;
73     }
74   }.bind(undefined, this.listenersToBeRemoved), -1 /* portId */);
76   // Set up the GuestView events.
77   for (var eventName in GuestViewEvents.EVENTS) {
78     this.setupEvent(eventName, GuestViewEvents.EVENTS[eventName]);
79   }
81   // Set up the derived view's events.
82   var events = this.getEvents();
83   for (var eventName in events) {
84     this.setupEvent(eventName, events[eventName]);
85   }
88 // Sets up the handling of the |eventName| event.
89 GuestViewEvents.prototype.setupEvent = function(eventName, eventInfo) {
90   if (!eventInfo.internal) {
91     this.setupEventProperty(eventName);
92   }
94   var listenerOpts = { instanceId: this.view.viewInstanceId };
95   if (eventInfo.handler) {
96     this.addScopedListener(eventInfo.evt, this.weakWrapper(function(e) {
97       this[eventInfo.handler](e, eventName);
98     }), listenerOpts);
99     return;
100   }
102   // Internal events are not dispatched as DOM events.
103   if (eventInfo.internal) {
104     return;
105   }
107   this.addScopedListener(eventInfo.evt, this.weakWrapper(function(e) {
108     var domEvent = this.makeDomEvent(e, eventName);
109     this.view.dispatchEvent(domEvent);
110   }), listenerOpts);
113 // Constructs a DOM event based on the info for the |eventName| event provided
114 // in either |GuestViewEvents.EVENTS| or getEvents().
115 GuestViewEvents.prototype.makeDomEvent = function(event, eventName) {
116   var eventInfo =
117       GuestViewEvents.EVENTS[eventName] || this.getEvents()[eventName];
119   // Internal events are not dispatched as DOM events.
120   if (eventInfo.internal) {
121     return null;
122   }
124   var details = { bubbles: true };
125   if (eventInfo.cancelable) {
126     details.cancelable = true;
127   }
128   var domEvent = new Event(eventName, details);
129   if (eventInfo.fields) {
130     $Array.forEach(eventInfo.fields, function(field) {
131       if (event[field] !== undefined) {
132         domEvent[field] = event[field];
133       }
134     }.bind(this));
135   }
137   return domEvent;
140 // Adds an 'on<event>' property on the view, which can be used to set/unset
141 // an event handler.
142 GuestViewEvents.prototype.setupEventProperty = function(eventName) {
143   var propertyName = 'on' + eventName.toLowerCase();
144   $Object.defineProperty(this.view.element, propertyName, {
145     get: function() {
146       return this.on[propertyName];
147     }.bind(this),
148     set: function(value) {
149       if (this.on[propertyName]) {
150         this.view.element.removeEventListener(eventName, this.on[propertyName]);
151       }
152       this.on[propertyName] = value;
153       if (value) {
154         this.view.element.addEventListener(eventName, value);
155       }
156     }.bind(this),
157     enumerable: true
158   });
161 // returns a wrapper for |func| with a weak reference to |this|.
162 GuestViewEvents.prototype.weakWrapper = function(func) {
163   var viewInstanceId = this.view.viewInstanceId;
164   return function() {
165     var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
166     if (!view) {
167       return;
168     }
169     return $Function.apply(func, view.events, $Array.slice(arguments));
170   };
173 // Implemented by the derived event manager, if one exists.
174 GuestViewEvents.prototype.getEvents = function() { return {}; };
176 // Exports.
177 exports.GuestViewEvents = GuestViewEvents;
178 exports.CreateEvent = CreateEvent;