OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013 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. |
| 4 |
| 5 // Shim that simulates a <adview> tag via Mutation Observers. |
| 6 // |
| 7 // The actual tag is implemented via the browser plugin. The internals of this |
| 8 // are hidden via Shadow DOM. |
| 9 |
| 10 // TODO(rpaquay): This file is currently very similar to "web_view.js". Do we |
| 11 // want to refactor to extract common pieces? |
| 12 |
| 13 var adViewCustom = require('adViewCustom'); |
| 14 |
| 15 var allowCustomAdNetworks = (function(allow){ |
| 16 return function() { return Boolean(allow); } |
| 17 })(adViewCustom ? adViewCustom.enabled : false); |
| 18 |
| 19 |
| 20 // List of attribute names to "blindly" sync between <adview> tag and internal |
| 21 // browser plugin. |
| 22 var AD_VIEW_ATTRIBUTES = [ |
| 23 'name', |
| 24 ]; |
| 25 |
| 26 // List of custom attributes (and their behavior) |
| 27 // |
| 28 // name: attribute name. |
| 29 // onInit(adview): callback invoked when the <adview> element is created. |
| 30 var AD_VIEW_CUSTOM_ATTRIBUTES = [ |
| 31 { |
| 32 'name': "ad-network", |
| 33 'onInit': function(adview) { |
| 34 if (adview.node_.hasAttribute(this.name)) { |
| 35 var value = adview.node_.getAttribute(this.name); |
| 36 var item = getAdNetworkInfo(value); |
| 37 if (item) { |
| 38 adview.objectNode_.setAttribute("src", item.url); |
| 39 } |
| 40 else if (allowCustomAdNetworks()) { |
| 41 console.log('The ad-network \"' + value + '\" is not recognized, ' + |
| 42 'but custom ad-networks are enabled.'); |
| 43 } |
| 44 else { |
| 45 console.error('The ad-network \"' + value + '\" is not recognized.'); |
| 46 } |
| 47 } |
| 48 } |
| 49 }, |
| 50 { |
| 51 'name': "src", |
| 52 'onInit': function(adview) { |
| 53 if (allowCustomAdNetworks()) { |
| 54 if (adview.node_.hasAttribute(this.name)) { |
| 55 var newValue = adview.node_.getAttribute(this.name); |
| 56 adview.objectNode_.setAttribute("src", newValue); |
| 57 } |
| 58 } |
| 59 }, |
| 60 'onMutation': function(adview, mutation) { |
| 61 if (allowCustomAdNetworks()) { |
| 62 if (adview.node_.hasAttribute(this.name)) { |
| 63 var newValue = adview.node_.getAttribute(this.name); |
| 64 //console.log('Setting <adview> "src": ' + newValue); |
| 65 // Note: setAttribute does not work as intended here. |
| 66 //adview.objectNode_.setAttribute(this.name, newValue); |
| 67 adview.objectNode_[this.name] = newValue; |
| 68 } |
| 69 else { |
| 70 // If an attribute is removed from the BrowserPlugin, then remove it |
| 71 // from the <adview> as well. |
| 72 this.objectNode_.removeAttribute(this.name); |
| 73 } |
| 74 } |
| 75 } |
| 76 } |
| 77 ]; |
| 78 |
| 79 // List of api methods. These are forwarded to the browser plugin. |
| 80 var AD_VIEW_API_METHODS = [ |
| 81 // Empty for now. |
| 82 ]; |
| 83 |
| 84 // List of events to blindly forward from the browser plugin to the <adview>. |
| 85 var AD_VIEW_EVENTS = { |
| 86 'loadcommit' : [], |
| 87 'sizechanged': ['oldHeight', 'oldWidth', 'newHeight', 'newWidth'], |
| 88 }; |
| 89 |
| 90 // List of supported ad-networks. |
| 91 // |
| 92 // name: identifier of the ad-network, corresponding to a valid value |
| 93 // of the "ad-network" attribute of an <adview> element. |
| 94 // url: url to navigate to when initially displaying the <adview>. |
| 95 // origin: origin of urls the <adview> is allowed navigate to. |
| 96 var AD_VIEW_AD_NETWORKS_WHITELIST = [ |
| 97 { |
| 98 'name': 'admob', |
| 99 'url': 'https://admob-sdk.doubleclick.net/chromeapps', |
| 100 'origin': 'https://double.net' |
| 101 }, |
| 102 ]; |
| 103 |
| 104 // |
| 105 // Return the whitelisted ad-network entry named |name|. |
| 106 // |
| 107 function getAdNetworkInfo(name) { |
| 108 var result = null; |
| 109 AD_VIEW_AD_NETWORKS_WHITELIST.forEach(function(item) { |
| 110 if (item.name === name) |
| 111 result = item; |
| 112 }); |
| 113 return result; |
| 114 } |
| 115 |
| 116 /** |
| 117 * @constructor |
| 118 */ |
| 119 function AdView(node) { |
| 120 this.node_ = node; |
| 121 var shadowRoot = node.webkitCreateShadowRoot(); |
| 122 |
| 123 this.objectNode_ = document.createElement('object'); |
| 124 this.objectNode_.type = 'application/browser-plugin'; |
| 125 // The <object> node fills in the <adview> container. |
| 126 this.objectNode_.style.width = '100%'; |
| 127 this.objectNode_.style.height = '100%'; |
| 128 AD_VIEW_ATTRIBUTES.forEach(function(attributeName) { |
| 129 // Only copy attributes that have been assigned values, rather than copying |
| 130 // a series of undefined attributes to BrowserPlugin. |
| 131 if (this.node_.hasAttribute(attributeName)) { |
| 132 this.objectNode_.setAttribute( |
| 133 attributeName, this.node_.getAttribute(attributeName)); |
| 134 } |
| 135 }, this); |
| 136 |
| 137 AD_VIEW_CUSTOM_ATTRIBUTES.forEach(function(attributeInfo) { |
| 138 if (attributeInfo.onInit) { |
| 139 attributeInfo.onInit(this); |
| 140 } |
| 141 }, this); |
| 142 |
| 143 shadowRoot.appendChild(this.objectNode_); |
| 144 |
| 145 // this.objectNode_[apiMethod] are not necessarily defined immediately after |
| 146 // the shadow object is appended to the shadow root. |
| 147 var self = this; |
| 148 AD_VIEW_API_METHODS.forEach(function(apiMethod) { |
| 149 node[apiMethod] = function(var_args) { |
| 150 return self.objectNode_[apiMethod].apply(self.objectNode_, arguments); |
| 151 }; |
| 152 }, this); |
| 153 |
| 154 // Map attribute modifications on the <adview> tag to property changes in |
| 155 // the underlying <object> node. |
| 156 var handleMutation = this.handleMutation_.bind(this); |
| 157 var observer = new WebKitMutationObserver(function(mutations) { |
| 158 mutations.forEach(handleMutation); |
| 159 }); |
| 160 observer.observe( |
| 161 this.node_, |
| 162 {attributes: true, attributeFilter: AD_VIEW_ATTRIBUTES}); |
| 163 |
| 164 var handleObjectMutation = this.handleObjectMutation_.bind(this); |
| 165 var objectObserver = new WebKitMutationObserver(function(mutations) { |
| 166 mutations.forEach(handleObjectMutation); |
| 167 }); |
| 168 objectObserver.observe( |
| 169 this.objectNode_, |
| 170 {attributes: true, attributeFilter: AD_VIEW_ATTRIBUTES}); |
| 171 |
| 172 // Map custom attribute modifications on the <adview> tag to property changes |
| 173 // in the underlying <object> node. |
| 174 var handleCustomMutation = this.handleCustomMutation_.bind(this); |
| 175 var observer = new WebKitMutationObserver(function(mutations) { |
| 176 mutations.forEach(handleCustomMutation); |
| 177 }); |
| 178 var customAttributeNames = |
| 179 AD_VIEW_CUSTOM_ATTRIBUTES.map(function(item) { return item.name; }); |
| 180 observer.observe( |
| 181 this.node_, |
| 182 {attributes: true, attributeFilter: customAttributeNames}); |
| 183 |
| 184 var objectNode = this.objectNode_; |
| 185 // Expose getters and setters for the attributes. |
| 186 AD_VIEW_ATTRIBUTES.forEach(function(attributeName) { |
| 187 Object.defineProperty(this.node_, attributeName, { |
| 188 get: function() { |
| 189 return objectNode[attributeName]; |
| 190 }, |
| 191 set: function(value) { |
| 192 objectNode[attributeName] = value; |
| 193 }, |
| 194 enumerable: true |
| 195 }); |
| 196 }, this); |
| 197 |
| 198 // We cannot use {writable: true} property descriptor because we want dynamic |
| 199 // getter value. |
| 200 Object.defineProperty(this.node_, 'contentWindow', { |
| 201 get: function() { |
| 202 // TODO(fsamuel): This is a workaround to enable |
| 203 // contentWindow.postMessage until http://crbug.com/152006 is fixed. |
| 204 if (objectNode.contentWindow) |
| 205 return objectNode.contentWindow.self; |
| 206 console.error('contentWindow is not available at this time. ' + |
| 207 'It will become available when the page has finished loading.'); |
| 208 }, |
| 209 // No setter. |
| 210 enumerable: true |
| 211 }); |
| 212 |
| 213 for (var eventName in AD_VIEW_EVENTS) { |
| 214 this.setupEvent_(eventName, AD_VIEW_EVENTS[eventName]); |
| 215 } |
| 216 } |
| 217 |
| 218 /** |
| 219 * @private |
| 220 */ |
| 221 AdView.prototype.handleMutation_ = function(mutation) { |
| 222 // This observer monitors mutations to attributes of the <adview> and |
| 223 // updates the BrowserPlugin properties accordingly. In turn, updating |
| 224 // a BrowserPlugin property will update the corresponding BrowserPlugin |
| 225 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more |
| 226 // details. |
| 227 this.objectNode_[mutation.attributeName] = |
| 228 this.node_.getAttribute(mutation.attributeName); |
| 229 }; |
| 230 |
| 231 /** |
| 232 * @private |
| 233 */ |
| 234 AdView.prototype.handleCustomMutation_ = function(mutation) { |
| 235 // This observer monitors mutations to attributes of the <adview> and |
| 236 // updates the BrowserPlugin properties accordingly. In turn, updating |
| 237 // a BrowserPlugin property will update the corresponding BrowserPlugin |
| 238 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more |
| 239 // details. |
| 240 AD_VIEW_CUSTOM_ATTRIBUTES.forEach(function(item) { |
| 241 if (mutation.attributeName.toUpperCase() == item.name.toUpperCase()) { |
| 242 if (item.onMutation) { |
| 243 item.onMutation.bind(item)(this, mutation); |
| 244 } |
| 245 } |
| 246 }, this); |
| 247 }; |
| 248 |
| 249 /** |
| 250 * @private |
| 251 */ |
| 252 AdView.prototype.handleObjectMutation_ = function(mutation) { |
| 253 // This observer monitors mutations to attributes of the BrowserPlugin and |
| 254 // updates the <adview> attributes accordingly. |
| 255 if (!this.objectNode_.hasAttribute(mutation.attributeName)) { |
| 256 // If an attribute is removed from the BrowserPlugin, then remove it |
| 257 // from the <adview> as well. |
| 258 this.node_.removeAttribute(mutation.attributeName); |
| 259 } else { |
| 260 // Update the <adview> attribute to match the BrowserPlugin attribute. |
| 261 // Note: Calling setAttribute on <adview> will trigger its mutation |
| 262 // observer which will then propagate that attribute to BrowserPlugin. In |
| 263 // cases where we permit assigning a BrowserPlugin attribute the same value |
| 264 // again (such as navigation when crashed), this could end up in an infinite |
| 265 // loop. Thus, we avoid this loop by only updating the <adview> attribute |
| 266 // if the BrowserPlugin attributes differs from it. |
| 267 var oldValue = this.node_.getAttribute(mutation.attributeName); |
| 268 var newValue = this.objectNode_.getAttribute(mutation.attributeName); |
| 269 if (newValue != oldValue) { |
| 270 this.node_.setAttribute(mutation.attributeName, newValue); |
| 271 } |
| 272 } |
| 273 }; |
| 274 |
| 275 /** |
| 276 * @private |
| 277 */ |
| 278 AdView.prototype.setupEvent_ = function(eventname, attribs) { |
| 279 var node = this.node_; |
| 280 this.objectNode_.addEventListener('-internal-' + eventname, function(e) { |
| 281 var evt = new Event(eventname, { bubbles: true }); |
| 282 var detail = e.detail ? JSON.parse(e.detail) : {}; |
| 283 attribs.forEach(function(attribName) { |
| 284 evt[attribName] = detail[attribName]; |
| 285 }); |
| 286 node.dispatchEvent(evt); |
| 287 }); |
| 288 } |
| 289 |
| 290 // |
| 291 // Hook up <adview> tag creation in DOM. |
| 292 // |
| 293 var watchForTag = require("tagWatcher").watchForTag; |
| 294 |
| 295 window.addEventListener('DOMContentLoaded', function() { |
| 296 watchForTag('ADVIEW', function(addedNode) { new AdView(addedNode); }); |
| 297 }); |
OLD | NEW |