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..84c7008857f99766613f4e24c4db4295e4827322 |
--- /dev/null |
+++ b/chrome/renderer/resources/extensions/ad_view.js |
@@ -0,0 +1,307 @@ |
+// 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 chrome = requireNative('chrome').GetChrome(); |
+var forEach = require('utils').forEach; |
+var watchForTag = require('tagWatcher').watchForTag; |
+ |
+ |
+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. |
+// onMutate(adview, mutation): callback invoked when attribute is mutated. |
+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); |
+ // 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; |
+ forEach(AD_VIEW_AD_NETWORKS_WHITELIST, function(i, 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%'; |
+ forEach(AD_VIEW_ATTRIBUTES, function(i, 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); |
+ |
+ forEach(AD_VIEW_CUSTOM_ATTRIBUTES, function(i, 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; |
+ forEach(AD_VIEW_API_METHODS, function(i, apiMethod) { |
+ 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 = function(i, mutation) { |
+ this.handleMutation_(mutation); |
+ }.bind(this); |
+ var observer = new WebKitMutationObserver(function(mutations) { |
+ forEach(mutations, handleMutation); |
+ }); |
+ observer.observe( |
+ this.node_, |
+ {attributes: true, attributeFilter: AD_VIEW_ATTRIBUTES}); |
+ |
+ var handleObjectMutation = function(i, mutation) { |
+ this.handleObjectMutation_(mutation); |
+ }.bind(this); |
+ var objectObserver = new WebKitMutationObserver(function(mutations) { |
+ forEach(mutations, 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 = function(i, mutation) { |
+ this.handleCustomMutation_(mutation); |
+ }.bind(this); |
+ var observer = new WebKitMutationObserver(function(mutations) { |
+ forEach(mutations, 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. |
+ forEach(AD_VIEW_ATTRIBUTES, function(i, 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. |
+ forEach(AD_VIEW_CUSTOM_ATTRIBUTES, function(i, 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) : {}; |
+ forEach(attribs, function(i, 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); }); |
+}); |