Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / extensions / renderer / resources / guest_view / guest_view_container.js
blob3e471d4af01b2aa8cccd3dd3dd0148fb5ca816cb
1 // Copyright 2014 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 // This module implements the shared functionality for different guestview
6 // containers, such as web_view, app_view, etc.
8 var DocumentNatives = requireNative('document_natives');
9 var GuestView = require('guestView').GuestView;
10 var GuestViewInternalNatives = requireNative('guest_view_internal');
11 var IdGenerator = requireNative('id_generator');
12 var MessagingNatives = requireNative('messaging_natives');
14 function GuestViewContainer(element, viewType) {
15   privates(element).internal = this;
16   this.attributes = {};
17   this.element = element;
18   this.elementAttached = false;
19   this.viewInstanceId = IdGenerator.GetNextId();
20   this.viewType = viewType;
22   this.setupGuestProperty();
23   this.guest = new GuestView(viewType);
24   this.setupAttributes();
26   privates(this).internalElement = this.createInternalElement$();
27   this.setupFocusPropagation();
28   var shadowRoot = this.element.createShadowRoot();
29   shadowRoot.appendChild(privates(this).internalElement);
31   GuestViewInternalNatives.RegisterView(this.viewInstanceId, this, viewType);
34 // Forward public API methods from |proto| to their internal implementations.
35 GuestViewContainer.forwardApiMethods = function(proto, apiMethods) {
36   var createProtoHandler = function(m) {
37     return function(var_args) {
38       var internal = privates(this).internal;
39       return $Function.apply(internal[m], internal, arguments);
40     };
41   };
42   for (var i = 0; apiMethods[i]; ++i) {
43     proto[apiMethods[i]] = createProtoHandler(apiMethods[i]);
44   }
47 // Registers the browserplugin and guestview as custom elements once the
48 // document has loaded.
49 GuestViewContainer.registerElement = function(guestViewContainerType) {
50   var useCapture = true;
51   window.addEventListener('readystatechange', function listener(event) {
52     if (document.readyState == 'loading')
53       return;
55     registerInternalElement(guestViewContainerType.VIEW_TYPE.toLowerCase());
56     registerGuestViewElement(guestViewContainerType);
57     window.removeEventListener(event.type, listener, useCapture);
58   }, useCapture);
61 // Create the 'guest' property to track new GuestViews and always listen for
62 // their resizes.
63 GuestViewContainer.prototype.setupGuestProperty = function() {
64   $Object.defineProperty(this, 'guest', {
65     get: function() {
66       return privates(this).guest;
67     }.bind(this),
68     set: function(value) {
69       privates(this).guest = value;
70       if (!value) {
71         return;
72       }
73       privates(this).guest.onresize = function(e) {
74         // Dispatch the 'contentresize' event.
75         var contentResizeEvent = new Event('contentresize', { bubbles: true });
76         contentResizeEvent.oldWidth = e.oldWidth;
77         contentResizeEvent.oldHeight = e.oldHeight;
78         contentResizeEvent.newWidth = e.newWidth;
79         contentResizeEvent.newHeight = e.newHeight;
80         this.dispatchEvent(contentResizeEvent);
81       }.bind(this);
82     }.bind(this),
83     enumerable: true
84   });
87 GuestViewContainer.prototype.createInternalElement$ = function() {
88   // We create BrowserPlugin as a custom element in order to observe changes
89   // to attributes synchronously.
90   var browserPluginElement =
91       new GuestViewContainer[this.viewType + 'BrowserPlugin']();
92   privates(browserPluginElement).internal = this;
93   return browserPluginElement;
96 GuestViewContainer.prototype.setupFocusPropagation = function() {
97   if (!this.element.hasAttribute('tabIndex')) {
98     // GuestViewContainer needs a tabIndex in order to be focusable.
99     // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
100     // to allow GuestViewContainer to be focusable.
101     // See http://crbug.com/231664.
102     this.element.setAttribute('tabIndex', -1);
103   }
104   this.element.addEventListener('focus', this.weakWrapper(function(e) {
105     // Focus the BrowserPlugin when the GuestViewContainer takes focus.
106     privates(this).internalElement.focus();
107   }));
108   this.element.addEventListener('blur', this.weakWrapper(function(e) {
109     // Blur the BrowserPlugin when the GuestViewContainer loses focus.
110     privates(this).internalElement.blur();
111   }));
114 GuestViewContainer.prototype.focus = function() {
115   // Focus the internal element when focus() is called on the GuestView element.
116   privates(this).internalElement.focus();
119 GuestViewContainer.prototype.attachWindow$ = function() {
120   if (!this.internalInstanceId) {
121     return true;
122   }
124   this.guest.attach(this.internalInstanceId,
125                     this.viewInstanceId,
126                     this.buildParams());
127   return true;
130 GuestViewContainer.prototype.makeGCOwnContainer = function(internalInstanceId) {
131   MessagingNatives.BindToGC(this, function() {
132     GuestViewInternalNatives.DestroyContainer(internalInstanceId);
133   }, -1);
136 GuestViewContainer.prototype.onInternalInstanceId = function(
137     internalInstanceId) {
138   this.internalInstanceId = internalInstanceId;
139   this.makeGCOwnContainer(this.internalInstanceId);
141   // Track when the element resizes using the element resize callback.
142   GuestViewInternalNatives.RegisterElementResizeCallback(
143       this.internalInstanceId, this.weakWrapper(this.onElementResize));
145   if (!this.guest.getId()) {
146     return;
147   }
148   this.guest.attach(this.internalInstanceId,
149                     this.viewInstanceId,
150                     this.buildParams());
153 GuestViewContainer.prototype.handleInternalElementAttributeMutation =
154     function(name, oldValue, newValue) {
155   if (name == 'internalinstanceid' && !oldValue && !!newValue) {
156     privates(this).internalElement.removeAttribute('internalinstanceid');
157     this.onInternalInstanceId(parseInt(newValue));
158   }
161 GuestViewContainer.prototype.onElementResize = function(newWidth, newHeight) {
162   if (!this.guest.getId())
163     return;
164   this.guest.setSize({normal: {width: newWidth, height: newHeight}});
167 GuestViewContainer.prototype.buildParams = function() {
168   var params = this.buildContainerParams();
169   params['instanceId'] = this.viewInstanceId;
170   // When the GuestViewContainer is not participating in layout (display:none)
171   // then getBoundingClientRect() would report a width and height of 0.
172   // However, in the case where the GuestViewContainer has a fixed size we can
173   // use that value to initially size the guest so as to avoid a relayout of the
174   // on display:block.
175   var css = window.getComputedStyle(this.element, null);
176   var elementRect = this.element.getBoundingClientRect();
177   params['elementWidth'] = parseInt(elementRect.width) ||
178       parseInt(css.getPropertyValue('width'));
179   params['elementHeight'] = parseInt(elementRect.height) ||
180       parseInt(css.getPropertyValue('height'));
181   return params;
184 GuestViewContainer.prototype.dispatchEvent = function(event) {
185   return this.element.dispatchEvent(event);
188 // Returns a wrapper function for |func| with a weak reference to |this|.
189 GuestViewContainer.prototype.weakWrapper = function(func) {
190   var viewInstanceId = this.viewInstanceId;
191   return function() {
192     var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
193     if (view) {
194       return $Function.apply(func, view, $Array.slice(arguments));
195     }
196   };
199 // Implemented by the specific view type, if needed.
200 GuestViewContainer.prototype.buildContainerParams = function() { return {}; };
201 GuestViewContainer.prototype.onElementAttached = function() {};
202 GuestViewContainer.prototype.onElementDetached = function() {};
203 GuestViewContainer.prototype.setupAttributes = function() {};
205 // Registers the browser plugin <object> custom element. |viewType| is the
206 // name of the specific guestview container (e.g. 'webview').
207 function registerInternalElement(viewType) {
208   var proto = $Object.create(HTMLElement.prototype);
210   proto.createdCallback = function() {
211     this.setAttribute('type', 'application/browser-plugin');
212     this.setAttribute('id', 'browser-plugin-' + IdGenerator.GetNextId());
213     this.style.width = '100%';
214     this.style.height = '100%';
215   };
217   proto.attachedCallback = function() {
218     // Load the plugin immediately.
219     var unused = this.nonExistentAttribute;
220   };
222   proto.attributeChangedCallback = function(name, oldValue, newValue) {
223     var internal = privates(this).internal;
224     if (!internal) {
225       return;
226     }
227     internal.handleInternalElementAttributeMutation(name, oldValue, newValue);
228   };
230   GuestViewContainer[viewType + 'BrowserPlugin'] =
231       DocumentNatives.RegisterElement(viewType + 'browserplugin',
232                                       {extends: 'object', prototype: proto});
234   delete proto.createdCallback;
235   delete proto.attachedCallback;
236   delete proto.detachedCallback;
237   delete proto.attributeChangedCallback;
240 // Registers the guestview container as a custom element.
241 // |guestViewContainerType| is the type of guestview container
242 // (e.g. WebViewImpl).
243 function registerGuestViewElement(guestViewContainerType) {
244   var proto = $Object.create(HTMLElement.prototype);
246   proto.createdCallback = function() {
247     new guestViewContainerType(this);
248   };
250   proto.attachedCallback = function() {
251     var internal = privates(this).internal;
252     if (!internal) {
253       return;
254     }
255     internal.elementAttached = true;
256     internal.onElementAttached();
257   };
259   proto.attributeChangedCallback = function(name, oldValue, newValue) {
260     var internal = privates(this).internal;
261     if (!internal || !internal.attributes[name]) {
262       return;
263     }
265     // Let the changed attribute handle its own mutation.
266     internal.attributes[name].maybeHandleMutation(oldValue, newValue);
267   };
269   proto.detachedCallback = function() {
270     var internal = privates(this).internal;
271     if (!internal) {
272       return;
273     }
274     internal.elementAttached = false;
275     internal.internalInstanceId = 0;
276     internal.guest.destroy();
277     internal.onElementDetached();
278   };
280   // Override |focus| to let |internal| handle it.
281   proto.focus = function() {
282     var internal = privates(this).internal;
283     if (!internal) {
284       return;
285     }
286     internal.focus();
287   };
289   // Let the specific view type add extra functionality to its custom element
290   // through |proto|.
291   if (guestViewContainerType.setupElement) {
292     guestViewContainerType.setupElement(proto);
293   }
295   window[guestViewContainerType.VIEW_TYPE] =
296       DocumentNatives.RegisterElement(
297           guestViewContainerType.VIEW_TYPE.toLowerCase(),
298           {prototype: proto});
300   // Delete the callbacks so developers cannot call them and produce unexpected
301   // behavior.
302   delete proto.createdCallback;
303   delete proto.attachedCallback;
304   delete proto.detachedCallback;
305   delete proto.attributeChangedCallback;
308 // Exports.
309 exports.GuestViewContainer = GuestViewContainer;