chromium/extensions/renderer/resources/guest_view/web_view/web_view_attributes.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 attributes of the <webview> tag.

var $Element = require('safeMethods').SafeMethods.$Element;
var $MutationObserver = require('safeMethods').SafeMethods.$MutationObserver;
var GuestViewAttributes = require('guestViewAttributes').GuestViewAttributes;
var WebViewConstants = require('webViewConstants').WebViewConstants;
var WebViewInternal = getInternalApi('webViewInternal');

// -----------------------------------------------------------------------------
// AllowScalingAttribute object.

// Attribute that specifies whether scaling is allowed in the webview.
function AllowScalingAttribute(view) {
  $Function.call(
      GuestViewAttributes.BooleanAttribute, this,
      WebViewConstants.ATTRIBUTE_ALLOWSCALING, view);
}

AllowScalingAttribute.prototype.__proto__ =
    GuestViewAttributes.BooleanAttribute.prototype;

AllowScalingAttribute.prototype.handleMutation = function(oldValue, newValue) {
  if (!this.view.guest.getId())
  return;

  WebViewInternal.setAllowScaling(this.view.guest.getId(), this.getValue());
};

// -----------------------------------------------------------------------------
// AllowTransparencyAttribute object.

// Attribute that specifies whether transparency is allowed in the webview.
function AllowTransparencyAttribute(view) {
  $Function.call(
      GuestViewAttributes.BooleanAttribute, this,
      WebViewConstants.ATTRIBUTE_ALLOWTRANSPARENCY, view);
}

AllowTransparencyAttribute.prototype.__proto__ =
    GuestViewAttributes.BooleanAttribute.prototype;

AllowTransparencyAttribute.prototype.handleMutation = function(oldValue,
                                                               newValue) {
  if (!this.view.guest.getId())
    return;

  WebViewInternal.setAllowTransparency(this.view.guest.getId(),
                                       this.getValue());
};

// -----------------------------------------------------------------------------
// AutosizeDimensionAttribute object.

// Attribute used to define the demension limits of autosizing.
function AutosizeDimensionAttribute(name, view) {
  $Function.call(GuestViewAttributes.IntegerAttribute, this, name, view);
}

AutosizeDimensionAttribute.prototype.__proto__ =
    GuestViewAttributes.IntegerAttribute.prototype;

AutosizeDimensionAttribute.prototype.handleMutation = function(
    oldValue, newValue) {
  if (!this.view.guest.getId())
    return;

  this.view.guest.setSize({
    'enableAutoSize': this.view.attributes[
      WebViewConstants.ATTRIBUTE_AUTOSIZE].getValue(),
    'min': {
      'width': this.view.attributes[
          WebViewConstants.ATTRIBUTE_MINWIDTH].getValue(),
      'height': this.view.attributes[
          WebViewConstants.ATTRIBUTE_MINHEIGHT].getValue()
    },
    'max': {
      'width': this.view.attributes[
          WebViewConstants.ATTRIBUTE_MAXWIDTH].getValue(),
      'height': this.view.attributes[
          WebViewConstants.ATTRIBUTE_MAXHEIGHT].getValue()
    }
  });
  return;
};

// -----------------------------------------------------------------------------
// AutosizeAttribute object.

// Attribute that specifies whether the webview should be autosized.
function AutosizeAttribute(view) {
  $Function.call(
      GuestViewAttributes.BooleanAttribute, this,
      WebViewConstants.ATTRIBUTE_AUTOSIZE, view);
}

AutosizeAttribute.prototype.__proto__ =
    GuestViewAttributes.BooleanAttribute.prototype;

AutosizeAttribute.prototype.handleMutation =
    AutosizeDimensionAttribute.prototype.handleMutation;

// -----------------------------------------------------------------------------
// NameAttribute object.

// Attribute that sets the guest content's window.name object.
function NameAttribute(view) {
  $Function.call(
      GuestViewAttributes.Attribute, this, WebViewConstants.ATTRIBUTE_NAME,
      view);
}

NameAttribute.prototype.__proto__ = GuestViewAttributes.Attribute.prototype

NameAttribute.prototype.handleMutation = function(oldValue, newValue) {
  oldValue = oldValue || '';
  newValue = newValue || '';
  if (oldValue === newValue || !this.view.guest.getId())
    return;

  WebViewInternal.setName(this.view.guest.getId(), newValue);
};

NameAttribute.prototype.setValue = function(value) {
  value = value || '';
  if (value === '')
    $Element.removeAttribute(this.view.element, this.name);
  else
    $Element.setAttribute(this.view.element, this.name, value);
};

// -----------------------------------------------------------------------------
// PartitionAttribute object.

// Attribute representing the state of the storage partition.
function PartitionAttribute(view) {
  $Function.call(
      GuestViewAttributes.Attribute, this, WebViewConstants.ATTRIBUTE_PARTITION,
      view);
  this.validPartitionId = true;
}

PartitionAttribute.prototype.__proto__ =
    GuestViewAttributes.Attribute.prototype;

PartitionAttribute.prototype.handleMutation = function(oldValue, newValue) {
  newValue = newValue || '';

  // The partition cannot change if the webview has already navigated.
  if (!this.view.attributes[
          WebViewConstants.ATTRIBUTE_SRC].beforeFirstNavigation) {
    window.console.error(WebViewConstants.ERROR_MSG_ALREADY_NAVIGATED);
    this.setValueIgnoreMutation(oldValue);
    return;
  }
  if (newValue == 'persist:') {
    this.validPartitionId = false;
    window.console.error(
        WebViewConstants.ERROR_MSG_INVALID_PARTITION_ATTRIBUTE);
  }
};

PartitionAttribute.prototype.detach = function() {
  this.validPartitionId = true;
};

// -----------------------------------------------------------------------------
// SrcAttribute object.

// Attribute that handles the location and navigation of the webview.
function SrcAttribute(view) {
  $Function.call(
      GuestViewAttributes.Attribute, this, WebViewConstants.ATTRIBUTE_SRC,
      view);
  this.setupMutationObserver();
  this.beforeFirstNavigation = true;
}

SrcAttribute.prototype.__proto__ = GuestViewAttributes.Attribute.prototype;

SrcAttribute.prototype.setValueIgnoreMutation = function(value) {
  $Function.call(
      GuestViewAttributes.Attribute.prototype.setValueIgnoreMutation, this,
      value);
  // takeRecords() is needed to clear queued up src mutations. Without it, it is
  // possible for this change to get picked up asyncronously by src's mutation
  // observer |observer|, and then get handled even though we do not want to
  // handle this mutation.
  $MutationObserver.takeRecords(this.observer);
};

SrcAttribute.prototype.handleMutation = function(oldValue, newValue) {
  // Once we have navigated, we don't allow clearing the src attribute.
  // Once <webview> enters a navigated state, it cannot return to a
  // placeholder state.
  if (!newValue && oldValue) {
    // src attribute changes normally initiate a navigation. We suppress
    // the next src attribute handler call to avoid reloading the page
    // on every guest-initiated navigation.
    this.setValueIgnoreMutation(oldValue);
    return;
  }
  this.parse();
};

SrcAttribute.prototype.attach = function() {
  this.parse();
};

SrcAttribute.prototype.detach = function() {
  this.beforeFirstNavigation = true;
};

// The purpose of this mutation observer is to catch assignment to the src
// attribute without any changes to its value. This is useful in the case
// where the webview guest has crashed and navigating to the same address
// spawns off a new process.
SrcAttribute.prototype.setupMutationObserver = function() {
  this.observer = new $MutationObserver($Function.bind(function(mutations) {
    $Array.forEach(mutations, $Function.bind(function(mutation) {
      var oldValue = mutation.oldValue;
      var newValue = this.getValue();
      if (oldValue != newValue) {
        return;
      }
      this.handleMutation(oldValue, newValue);
    }, this));
  }, this));
  var params = {
    attributes: true,
    attributeOldValue: true,
    attributeFilter: [this.name]
  };
  $MutationObserver.observe(this.observer, this.view.element, params);
};

SrcAttribute.prototype.parse = function() {
  if (!this.view.elementAttached ||
      !this.view.attributes[
          WebViewConstants.ATTRIBUTE_PARTITION].validPartitionId ||
      !this.getValue()) {
    return;
  }

  if (!this.view.guest.getId()) {
    if (this.beforeFirstNavigation) {
      this.beforeFirstNavigation = false;
      this.view.createGuest();
    }
    return;
  }

  WebViewInternal.navigate(this.view.guest.getId(), this.getValue());
};

var WebViewAttributes = {
  AllowScalingAttribute: AllowScalingAttribute,
  AllowTransparencyAttribute: AllowTransparencyAttribute,
  AutosizeDimensionAttribute: AutosizeDimensionAttribute,
  AutosizeAttribute: AutosizeAttribute,
  NameAttribute: NameAttribute,
  PartitionAttribute: PartitionAttribute,
  SrcAttribute: SrcAttribute
};

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