Chromium Code Reviews| Index: chrome/renderer/resources/extensions/ad_view.js |
| diff --git a/chrome/renderer/resources/extensions/ad_view.js b/chrome/renderer/resources/extensions/ad_view.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fe15e49882b707f8e2ada41f2832793cdeb7e2de |
| --- /dev/null |
| +++ b/chrome/renderer/resources/extensions/ad_view.js |
| @@ -0,0 +1,297 @@ |
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +// Shim that simulates a <adview> tag via Mutation Observers. |
| +// |
| +// The actual tag is implemented via the browser plugin. The internals of this |
| +// are hidden via Shadow DOM. |
| + |
| +// TODO(rpaquay): This file is currently very similar to "web_view.js". Do we |
| +// want to refactor to extract common pieces? |
| + |
| +var adViewCustom = require('adViewCustom'); |
| + |
| +var allowCustomAdNetworks = (function(allow){ |
| + return function() { return Boolean(allow); } |
| +})(adViewCustom ? adViewCustom.enabled : false); |
| + |
| + |
| +// List of attribute names to "blindly" sync between <adview> tag and internal |
| +// browser plugin. |
| +var AD_VIEW_ATTRIBUTES = [ |
| + 'name', |
| +]; |
| + |
| +// List of custom attributes (and their behavior) |
| +// |
| +// name: attribute name. |
| +// onInit(adview): callback invoked when the <adview> element is created. |
| +var AD_VIEW_CUSTOM_ATTRIBUTES = [ |
| + { |
| + 'name': "ad-network", |
| + 'onInit': function(adview) { |
| + if (adview.node_.hasAttribute(this.name)) { |
| + var value = adview.node_.getAttribute(this.name); |
| + var item = getAdNetworkInfo(value); |
| + if (item) { |
| + adview.objectNode_.setAttribute("src", item.url); |
| + } |
| + else if (allowCustomAdNetworks()) { |
| + console.log('The ad-network \"' + value + '\" is not recognized, ' + |
| + 'but custom ad-networks are enabled.'); |
| + } |
| + else { |
| + console.error('The ad-network \"' + value + '\" is not recognized.'); |
| + } |
| + } |
| + } |
| + }, |
| + { |
| + 'name': "src", |
| + 'onInit': function(adview) { |
| + if (allowCustomAdNetworks()) { |
| + if (adview.node_.hasAttribute(this.name)) { |
| + var newValue = adview.node_.getAttribute(this.name); |
| + adview.objectNode_.setAttribute("src", newValue); |
| + } |
| + } |
| + }, |
| + 'onMutation': function(adview, mutation) { |
| + if (allowCustomAdNetworks()) { |
| + if (adview.node_.hasAttribute(this.name)) { |
| + var newValue = adview.node_.getAttribute(this.name); |
| + //console.log('Setting <adview> "src": ' + newValue); |
|
asargent_no_longer_on_chrome
2013/03/12 20:54:27
nit: remove this debugging line
rpaquay
2013/03/12 23:05:36
Done.
|
| + // Note: setAttribute does not work as intended here. |
| + //adview.objectNode_.setAttribute(this.name, newValue); |
| + adview.objectNode_[this.name] = newValue; |
| + } |
| + else { |
| + // If an attribute is removed from the BrowserPlugin, then remove it |
| + // from the <adview> as well. |
| + this.objectNode_.removeAttribute(this.name); |
| + } |
| + } |
| + } |
| + } |
| +]; |
| + |
| +// List of api methods. These are forwarded to the browser plugin. |
| +var AD_VIEW_API_METHODS = [ |
| + // Empty for now. |
| +]; |
| + |
| +// List of events to blindly forward from the browser plugin to the <adview>. |
| +var AD_VIEW_EVENTS = { |
| + 'loadcommit' : [], |
| + 'sizechanged': ['oldHeight', 'oldWidth', 'newHeight', 'newWidth'], |
| +}; |
| + |
| +// List of supported ad-networks. |
| +// |
| +// name: identifier of the ad-network, corresponding to a valid value |
| +// of the "ad-network" attribute of an <adview> element. |
| +// url: url to navigate to when initially displaying the <adview>. |
| +// origin: origin of urls the <adview> is allowed navigate to. |
| +var AD_VIEW_AD_NETWORKS_WHITELIST = [ |
| + { |
| + 'name': 'admob', |
| + 'url': 'https://admob-sdk.doubleclick.net/chromeapps', |
| + 'origin': 'https://double.net' |
| + }, |
| +]; |
| + |
| +// |
| +// Return the whitelisted ad-network entry named |name|. |
| +// |
| +function getAdNetworkInfo(name) { |
| + var result = null; |
| + AD_VIEW_AD_NETWORKS_WHITELIST.forEach(function(item) { |
| + if (item.name === name) |
| + result = item; |
| + }); |
| + return result; |
| +} |
| + |
| +/** |
| + * @constructor |
| + */ |
| +function AdView(node) { |
| + this.node_ = node; |
| + var shadowRoot = node.webkitCreateShadowRoot(); |
| + |
| + this.objectNode_ = document.createElement('object'); |
| + this.objectNode_.type = 'application/browser-plugin'; |
| + // The <object> node fills in the <adview> container. |
| + this.objectNode_.style.width = '100%'; |
| + this.objectNode_.style.height = '100%'; |
| + AD_VIEW_ATTRIBUTES.forEach(function(attributeName) { |
| + // Only copy attributes that have been assigned values, rather than copying |
| + // a series of undefined attributes to BrowserPlugin. |
| + if (this.node_.hasAttribute(attributeName)) { |
| + this.objectNode_.setAttribute( |
| + attributeName, this.node_.getAttribute(attributeName)); |
| + } |
| + }, this); |
| + |
| + AD_VIEW_CUSTOM_ATTRIBUTES.forEach(function(attributeInfo) { |
| + if (attributeInfo.onInit) { |
| + attributeInfo.onInit(this); |
| + } |
| + }, this); |
| + |
| + shadowRoot.appendChild(this.objectNode_); |
| + |
| + // this.objectNode_[apiMethod] are not necessarily defined immediately after |
| + // the shadow object is appended to the shadow root. |
| + var self = this; |
| + AD_VIEW_API_METHODS.forEach(function(apiMethod) { |
|
asargent_no_longer_on_chrome
2013/03/12 20:54:27
nit: see kalman's recent mail about Array.forEach
rpaquay
2013/03/12 23:05:36
Done.
|
| + node[apiMethod] = function(var_args) { |
| + return self.objectNode_[apiMethod].apply(self.objectNode_, arguments); |
| + }; |
| + }, this); |
| + |
| + // Map attribute modifications on the <adview> tag to property changes in |
| + // the underlying <object> node. |
| + var handleMutation = this.handleMutation_.bind(this); |
| + var observer = new WebKitMutationObserver(function(mutations) { |
| + mutations.forEach(handleMutation); |
| + }); |
| + observer.observe( |
| + this.node_, |
| + {attributes: true, attributeFilter: AD_VIEW_ATTRIBUTES}); |
| + |
| + var handleObjectMutation = this.handleObjectMutation_.bind(this); |
| + var objectObserver = new WebKitMutationObserver(function(mutations) { |
| + mutations.forEach(handleObjectMutation); |
| + }); |
| + objectObserver.observe( |
| + this.objectNode_, |
| + {attributes: true, attributeFilter: AD_VIEW_ATTRIBUTES}); |
| + |
| + // Map custom attribute modifications on the <adview> tag to property changes |
| + // in the underlying <object> node. |
| + var handleCustomMutation = this.handleCustomMutation_.bind(this); |
| + var observer = new WebKitMutationObserver(function(mutations) { |
| + mutations.forEach(handleCustomMutation); |
| + }); |
| + var customAttributeNames = |
| + AD_VIEW_CUSTOM_ATTRIBUTES.map(function(item) { return item.name; }); |
| + observer.observe( |
| + this.node_, |
| + {attributes: true, attributeFilter: customAttributeNames}); |
| + |
| + var objectNode = this.objectNode_; |
| + // Expose getters and setters for the attributes. |
| + AD_VIEW_ATTRIBUTES.forEach(function(attributeName) { |
| + Object.defineProperty(this.node_, attributeName, { |
| + get: function() { |
| + return objectNode[attributeName]; |
| + }, |
| + set: function(value) { |
| + objectNode[attributeName] = value; |
| + }, |
| + enumerable: true |
| + }); |
| + }, this); |
| + |
| + // We cannot use {writable: true} property descriptor because we want dynamic |
| + // getter value. |
| + Object.defineProperty(this.node_, 'contentWindow', { |
| + get: function() { |
| + // TODO(fsamuel): This is a workaround to enable |
| + // contentWindow.postMessage until http://crbug.com/152006 is fixed. |
| + if (objectNode.contentWindow) |
| + return objectNode.contentWindow.self; |
| + console.error('contentWindow is not available at this time. ' + |
| + 'It will become available when the page has finished loading.'); |
| + }, |
| + // No setter. |
| + enumerable: true |
| + }); |
| + |
| + for (var eventName in AD_VIEW_EVENTS) { |
| + this.setupEvent_(eventName, AD_VIEW_EVENTS[eventName]); |
| + } |
| +} |
| + |
| +/** |
| + * @private |
| + */ |
| +AdView.prototype.handleMutation_ = function(mutation) { |
| + // This observer monitors mutations to attributes of the <adview> and |
| + // updates the BrowserPlugin properties accordingly. In turn, updating |
| + // a BrowserPlugin property will update the corresponding BrowserPlugin |
| + // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more |
| + // details. |
| + this.objectNode_[mutation.attributeName] = |
| + this.node_.getAttribute(mutation.attributeName); |
| +}; |
| + |
| +/** |
| + * @private |
| + */ |
| +AdView.prototype.handleCustomMutation_ = function(mutation) { |
| + // This observer monitors mutations to attributes of the <adview> and |
| + // updates the BrowserPlugin properties accordingly. In turn, updating |
| + // a BrowserPlugin property will update the corresponding BrowserPlugin |
| + // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more |
| + // details. |
| + AD_VIEW_CUSTOM_ATTRIBUTES.forEach(function(item) { |
| + if (mutation.attributeName.toUpperCase() == item.name.toUpperCase()) { |
| + if (item.onMutation) { |
| + item.onMutation.bind(item)(this, mutation); |
| + } |
| + } |
| + }, this); |
| +}; |
| + |
| +/** |
| + * @private |
| + */ |
| +AdView.prototype.handleObjectMutation_ = function(mutation) { |
| + // This observer monitors mutations to attributes of the BrowserPlugin and |
| + // updates the <adview> attributes accordingly. |
| + if (!this.objectNode_.hasAttribute(mutation.attributeName)) { |
| + // If an attribute is removed from the BrowserPlugin, then remove it |
| + // from the <adview> as well. |
| + this.node_.removeAttribute(mutation.attributeName); |
| + } else { |
| + // Update the <adview> attribute to match the BrowserPlugin attribute. |
| + // Note: Calling setAttribute on <adview> will trigger its mutation |
| + // observer which will then propagate that attribute to BrowserPlugin. In |
| + // cases where we permit assigning a BrowserPlugin attribute the same value |
| + // again (such as navigation when crashed), this could end up in an infinite |
| + // loop. Thus, we avoid this loop by only updating the <adview> attribute |
| + // if the BrowserPlugin attributes differs from it. |
| + var oldValue = this.node_.getAttribute(mutation.attributeName); |
| + var newValue = this.objectNode_.getAttribute(mutation.attributeName); |
| + if (newValue != oldValue) { |
| + this.node_.setAttribute(mutation.attributeName, newValue); |
| + } |
| + } |
| +}; |
| + |
| +/** |
| + * @private |
| + */ |
| +AdView.prototype.setupEvent_ = function(eventname, attribs) { |
| + var node = this.node_; |
| + this.objectNode_.addEventListener('-internal-' + eventname, function(e) { |
| + var evt = new Event(eventname, { bubbles: true }); |
| + var detail = e.detail ? JSON.parse(e.detail) : {}; |
| + attribs.forEach(function(attribName) { |
| + evt[attribName] = detail[attribName]; |
| + }); |
| + node.dispatchEvent(evt); |
| + }); |
| +} |
| + |
| +// |
| +// Hook up <adview> tag creation in DOM. |
| +// |
| +var watchForTag = require("tagWatcher").watchForTag; |
| + |
| +window.addEventListener('DOMContentLoaded', function() { |
| + watchForTag('ADVIEW', function(addedNode) { new AdView(addedNode); }); |
| +}); |