chromium/extensions/renderer/resources/guest_view/guest_view_container.js

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This module implements the shared functionality for different guestview
// containers, such as web_view, app_view, etc.

var $parseInt = require('safeMethods').SafeMethods.$parseInt;
var $getComputedStyle = require('safeMethods').SafeMethods.$getComputedStyle;
var $Document = require('safeMethods').SafeMethods.$Document;
var $Element = require('safeMethods').SafeMethods.$Element;
var $EventTarget = require('safeMethods').SafeMethods.$EventTarget;
var $HTMLElement = require('safeMethods').SafeMethods.$HTMLElement;
var $Node = require('safeMethods').SafeMethods.$Node;
var GuestView = require('guestView').GuestView;
var GuestViewInternalNatives = requireNative('guest_view_internal');
var IdGenerator = requireNative('id_generator');
var MessagingNatives = requireNative('messaging_natives');

function GuestViewContainer(element, viewType) {
  this.attributes = $Object.create(null);
  this.element = element;
  this.elementAttached = false;
  this.viewInstanceId = IdGenerator.GetNextId();
  this.viewType = viewType;

  this.setupGuestProperty();
  this.guest = new GuestView(viewType);
  this.setupAttributes();

  this.internalElement = this.createInternalElement();
  this.shadowRoot = $Element.attachShadow(this.element, {mode: 'closed'});
  $Node.appendChild(this.shadowRoot, this.internalElement);

  GuestViewInternalNatives.RegisterView(this.viewInstanceId, this, viewType);
}

// Prevent GuestViewContainer inadvertently inheriting code from the global
// Object, allowing a pathway for executing unintended user code execution.
// TODO(wjmaclean): Track down other issues of Object inheritance.
// https://crbug.com/701034
GuestViewContainer.prototype.__proto__ = null;

// Create the 'guest' property to track new GuestViews and always listen for
// their resizes.
GuestViewContainer.prototype.setupGuestProperty = function() {
  $Object.defineProperty(this, 'guest', {
    get: $Function.bind(function() {
      return this.guest_;
    }, this),
    set: $Function.bind(function(value) {
      this.guest_ = value;
      if (!value) {
        return;
      }
      this.guest_.onresize = $Function.bind(function(e) {
        // Dispatch the 'contentresize' event.
        var contentResizeEvent = new Event('contentresize', { bubbles: true });
        contentResizeEvent.oldWidth = e.oldWidth;
        contentResizeEvent.oldHeight = e.oldHeight;
        contentResizeEvent.newWidth = e.newWidth;
        contentResizeEvent.newHeight = e.newHeight;
        this.dispatchEvent(contentResizeEvent);
      }, this);
    }, this),
    enumerable: true
  });
};

GuestViewContainer.prototype.createInternalElement = function() {
  var iframeElement = $Document.createElement(document, 'iframe');

  var style = $HTMLElement.style.get(iframeElement);
  $Object.defineProperty(style, 'width', {value: '100%'});
  $Object.defineProperty(style, 'height', {value: '100%'});
  $Object.defineProperty(style, 'border', {value: '0px'});

  return iframeElement;
};

GuestViewContainer.prototype.prepareForReattach = function() {
  // Since attachment swaps a local frame for a remote frame, we need our
  // internal iframe element to be local again before we can reattach.
  var newFrame = this.createInternalElement();
  var oldFrame = this.internalElement;
  this.internalElement = newFrame;
  var frameParent = $Node.parentNode.get(oldFrame);
  $Node.replaceChild(frameParent, newFrame, oldFrame);
};

GuestViewContainer.prototype.focus = function() {
  // Focus the internal element when focus() is called on the GuestView element.
  $HTMLElement.focus(this.internalElement);
}

GuestViewContainer.prototype.attachWindow = function() {
  var generatedId = IdGenerator.GetNextId();
  // Generate an instance id for the container.
  this.onInternalInstanceId(generatedId);
};

GuestViewContainer.prototype.makeGCOwnContainer = function(internalInstanceId) {
  MessagingNatives.BindToGC(this, function() {
    GuestViewInternalNatives.DestroyContainer(internalInstanceId);
  });
};

GuestViewContainer.prototype.onInternalInstanceId = function(
    internalInstanceId) {
  this.internalInstanceId = internalInstanceId;
  this.makeGCOwnContainer(this.internalInstanceId);

  if (!this.guest.getId()) {
    return;
  }
  this.guest.attach(this.internalInstanceId,
                    this.viewInstanceId,
                    this.buildParams());
};

GuestViewContainer.prototype.buildParams = function() {
  var params = this.buildContainerParams();
  params['instanceId'] = this.viewInstanceId;
  // When the GuestViewContainer is not participating in layout (display:none)
  // then getBoundingClientRect() would report a width and height of 0.
  // However, in the case where the GuestViewContainer has a fixed size we can
  // use that value to initially size the guest so as to avoid a relayout of the
  // on display:block.
  var css = $getComputedStyle(this.element, null);
  var elementRect = $Element.getBoundingClientRect(this.element);
  params['elementWidth'] =
      $parseInt(elementRect.width) || $parseInt(css.getPropertyValue('width'));
  params['elementHeight'] = $parseInt(elementRect.height) ||
      $parseInt(css.getPropertyValue('height'));
  return params;
};

GuestViewContainer.prototype.dispatchEvent = function(event) {
  return $EventTarget.dispatchEvent(this.element, event);
};

// Returns a wrapper function for |func| with a weak reference to |this|.
GuestViewContainer.prototype.weakWrapper = function(func) {
  var viewInstanceId = this.viewInstanceId;
  return function() {
    var view = GuestViewInternalNatives.GetViewFromID(viewInstanceId);
    if (view) {
      return $Function.apply(func, view, $Array.slice(arguments));
    }
  };
};

GuestViewContainer.prototype.willAttachElement = function() {
  if (this.deferredAttachCallback) {
    this.deferredAttachCallback();
    this.deferredAttachCallback = null;
  }
};

// Implemented by the specific view type, if needed.
GuestViewContainer.prototype.buildContainerParams = function() {
  return $Object.create(null);
};
GuestViewContainer.prototype.onElementAttached = function() {};
GuestViewContainer.prototype.onElementDetached = function() {};
GuestViewContainer.prototype.setupAttributes = function() {};

// Exports.
exports.$set('GuestViewContainer', GuestViewContainer);