Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / extensions / renderer / resources / guest_view / guest_view_container.js
blob5b6a736758ffe04dbec929baf704ec9289618e24
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');
13 function GuestViewContainer(element, viewType) {
14   privates(element).internal = this;
15   this.attributes = {};
16   this.element = element;
17   this.elementAttached = false;
18   this.viewInstanceId = IdGenerator.GetNextId();
19   this.viewType = viewType;
21   this.setupGuestProperty();
22   this.guest = new GuestView(viewType);
23   this.setupAttributes();
25   privates(this).browserPluginElement = this.createBrowserPluginElement();
26   this.setupFocusPropagation();
27   var shadowRoot = this.element.createShadowRoot();
28   shadowRoot.appendChild(privates(this).browserPluginElement);
31 // Forward public API methods from |proto| to their internal implementations.
32 GuestViewContainer.forwardApiMethods = function(proto, apiMethods) {
33   var createProtoHandler = function(m) {
34     return function(var_args) {
35       var internal = privates(this).internal;
36       return $Function.apply(internal[m], internal, arguments);
37     };
38   };
39   for (var i = 0; apiMethods[i]; ++i) {
40     proto[apiMethods[i]] = createProtoHandler(apiMethods[i]);
41   }
44 // Registers the browserplugin and guestview as custom elements once the
45 // document has loaded.
46 GuestViewContainer.registerElement = function(guestViewContainerType) {
47   var useCapture = true;
48   window.addEventListener('readystatechange', function listener(event) {
49     if (document.readyState == 'loading')
50       return;
52     registerBrowserPluginElement(
53         guestViewContainerType.VIEW_TYPE.toLowerCase());
54     registerGuestViewElement(guestViewContainerType);
55     window.removeEventListener(event.type, listener, useCapture);
56   }, useCapture);
59 // Create the 'guest' property to track new GuestViews and always listen for
60 // their resizes.
61 GuestViewContainer.prototype.setupGuestProperty = function() {
62   Object.defineProperty(this, 'guest', {
63     get: function() {
64       return privates(this).guest;
65     }.bind(this),
66     set: function(value) {
67       privates(this).guest = value;
68       if (!value) {
69         return;
70       }
71       privates(this).guest.onresize = function(e) {
72         // Dispatch the 'contentresize' event.
73         var contentResizeEvent = new Event('contentresize', { bubbles: true });
74         contentResizeEvent.oldWidth = e.oldWidth;
75         contentResizeEvent.oldHeight = e.oldHeight;
76         contentResizeEvent.newWidth = e.newWidth;
77         contentResizeEvent.newHeight = e.newHeight;
78         this.dispatchEvent(contentResizeEvent);
79       }.bind(this);
80     }.bind(this),
81     enumerable: true
82   });
85 GuestViewContainer.prototype.createBrowserPluginElement = function() {
86   // We create BrowserPlugin as a custom element in order to observe changes
87   // to attributes synchronously.
88   var browserPluginElement =
89       new GuestViewContainer[this.viewType + 'BrowserPlugin']();
90   privates(browserPluginElement).internal = this;
91   return browserPluginElement;
94 GuestViewContainer.prototype.setupFocusPropagation = function() {
95   if (!this.element.hasAttribute('tabIndex')) {
96     // GuestViewContainer needs a tabIndex in order to be focusable.
97     // TODO(fsamuel): It would be nice to avoid exposing a tabIndex attribute
98     // to allow GuestViewContainer to be focusable.
99     // See http://crbug.com/231664.
100     this.element.setAttribute('tabIndex', -1);
101   }
102   this.element.addEventListener('focus', function(e) {
103     // Focus the BrowserPlugin when the GuestViewContainer takes focus.
104     privates(this).browserPluginElement.focus();
105   }.bind(this));
106   this.element.addEventListener('blur', function(e) {
107     // Blur the BrowserPlugin when the GuestViewContainer loses focus.
108     privates(this).browserPluginElement.blur();
109   }.bind(this));
112 GuestViewContainer.prototype.attachWindow = function() {
113   if (!this.internalInstanceId) {
114     return true;
115   }
117   this.guest.attach(this.internalInstanceId,
118                     this.viewInstanceId,
119                     this.buildParams());
120   return true;
123 GuestViewContainer.prototype.handleBrowserPluginAttributeMutation =
124     function(name, oldValue, newValue) {
125   if (name == 'internalinstanceid' && !oldValue && !!newValue) {
126     privates(this).browserPluginElement.removeAttribute('internalinstanceid');
127     this.internalInstanceId = parseInt(newValue);
129     // Track when the element resizes using the element resize callback.
130     GuestViewInternalNatives.RegisterElementResizeCallback(
131         this.internalInstanceId, this.onElementResize.bind(this));
133     if (!this.guest.getId()) {
134       return;
135     }
136     this.guest.attach(this.internalInstanceId,
137                       this.viewInstanceId,
138                       this.buildParams());
139   }
142 GuestViewContainer.prototype.onElementResize = function(oldWidth, oldHeight,
143                                                         newWidth, newHeight) {
144   // Dispatch the 'resize' event.
145   var resizeEvent = new Event('resize', { bubbles: true });
146   resizeEvent.oldWidth = oldWidth;
147   resizeEvent.oldHeight = oldHeight;
148   resizeEvent.newWidth = newWidth;
149   resizeEvent.newHeight = newHeight;
150   this.dispatchEvent(resizeEvent);
152   if (!this.guest.getId())
153     return;
154   this.guest.setSize({normal: {width: newWidth, height: newHeight}});
157 GuestViewContainer.prototype.buildParams = function() {
158   var params = this.buildContainerParams();
159   params['instanceId'] = this.viewInstanceId;
160   // When the GuestViewContainer is not participating in layout (display:none)
161   // then getBoundingClientRect() would report a width and height of 0.
162   // However, in the case where the GuestViewContainer has a fixed size we can
163   // use that value to initially size the guest so as to avoid a relayout of the
164   // on display:block.
165   var css = window.getComputedStyle(this.element, null);
166   var elementRect = this.element.getBoundingClientRect();
167   params['elementWidth'] = parseInt(elementRect.width) ||
168       parseInt(css.getPropertyValue('width'));
169   params['elementHeight'] = parseInt(elementRect.height) ||
170       parseInt(css.getPropertyValue('height'));
171   return params;
174 GuestViewContainer.prototype.dispatchEvent = function(event) {
175   return this.element.dispatchEvent(event);
178 // Implemented by the specific view type, if needed.
179 GuestViewContainer.prototype.buildContainerParams = function() { return {}; };
180 GuestViewContainer.prototype.onElementAttached = function() {};
181 GuestViewContainer.prototype.onElementDetached = function() {};
182 GuestViewContainer.prototype.setupAttributes = function() {};
184 // Registers the browser plugin <object> custom element. |viewType| is the
185 // name of the specific guestview container (e.g. 'webview').
186 function registerBrowserPluginElement(viewType) {
187   var proto = Object.create(HTMLObjectElement.prototype);
189   proto.createdCallback = function() {
190     this.setAttribute('type', 'application/browser-plugin');
191     this.setAttribute('id', 'browser-plugin-' + IdGenerator.GetNextId());
192     this.style.width = '100%';
193     this.style.height = '100%';
194   };
196   proto.attachedCallback = function() {
197     // Load the plugin immediately.
198     var unused = this.nonExistentAttribute;
199   };
201   proto.attributeChangedCallback = function(name, oldValue, newValue) {
202     var internal = privates(this).internal;
203     if (!internal) {
204       return;
205     }
206     internal.handleBrowserPluginAttributeMutation(name, oldValue, newValue);
207   };
209   GuestViewContainer[viewType + 'BrowserPlugin'] =
210       DocumentNatives.RegisterElement(viewType + 'browserplugin',
211                                       {extends: 'object', prototype: proto});
213   delete proto.createdCallback;
214   delete proto.attachedCallback;
215   delete proto.detachedCallback;
216   delete proto.attributeChangedCallback;
219 // Registers the guestview container as a custom element.
220 // |guestViewContainerType| is the type of guestview container
221 // (e.g.WebViewImpl).
222 function registerGuestViewElement(guestViewContainerType) {
223   var proto = Object.create(HTMLElement.prototype);
225   proto.createdCallback = function() {
226     new guestViewContainerType(this);
227   };
229   proto.attachedCallback = function() {
230     var internal = privates(this).internal;
231     if (!internal) {
232       return;
233     }
234     internal.elementAttached = true;
235     internal.onElementAttached();
236   };
238   proto.attributeChangedCallback = function(name, oldValue, newValue) {
239     var internal = privates(this).internal;
240     if (!internal || !internal.attributes[name]) {
241       return;
242     }
244     // Let the changed attribute handle its own mutation.
245     internal.attributes[name].maybeHandleMutation(oldValue, newValue);
246   };
248   proto.detachedCallback = function() {
249     var internal = privates(this).internal;
250     if (!internal) {
251       return;
252     }
253     internal.elementAttached = false;
254     internal.internalInstanceId = 0;
255     internal.guest.destroy();
256     internal.onElementDetached();
257   };
259   // Let the specific view type add extra functionality to its custom element
260   // through |proto|.
261   if (guestViewContainerType.setupElement) {
262     guestViewContainerType.setupElement(proto);
263   }
265   window[guestViewContainerType.VIEW_TYPE] =
266       DocumentNatives.RegisterElement(
267           guestViewContainerType.VIEW_TYPE.toLowerCase(),
268           {prototype: proto});
270   // Delete the callbacks so developers cannot call them and produce unexpected
271   // behavior.
272   delete proto.createdCallback;
273   delete proto.attachedCallback;
274   delete proto.detachedCallback;
275   delete proto.attributeChangedCallback;
278 // Exports.
279 exports.GuestViewContainer = GuestViewContainer;