| OLD | NEW |
| (Empty) |
| 1 // Copyright 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 eventBindings = require('event_bindings'); | |
| 14 var process = requireNative('process'); | |
| 15 var addTagWatcher = require('tagWatcher').addTagWatcher; | |
| 16 | |
| 17 /** | |
| 18 * List of attribute names to "blindly" sync between <adview> tag and internal | |
| 19 * browser plugin. | |
| 20 */ | |
| 21 var AD_VIEW_ATTRIBUTES = [ | |
| 22 'name', | |
| 23 ]; | |
| 24 | |
| 25 /** | |
| 26 * List of custom attributes (and their behavior). | |
| 27 * | |
| 28 * name: attribute name. | |
| 29 * onMutation(adview, mutation): callback invoked when attribute is mutated. | |
| 30 * isProperty: True if the attribute should be exposed as a property. | |
| 31 */ | |
| 32 var AD_VIEW_CUSTOM_ATTRIBUTES = [ | |
| 33 { | |
| 34 name: 'ad-network', | |
| 35 onMutation: function(adview, mutation) { | |
| 36 adview.handleAdNetworkMutation(mutation); | |
| 37 }, | |
| 38 isProperty: function() { | |
| 39 return true; | |
| 40 } | |
| 41 } | |
| 42 ]; | |
| 43 | |
| 44 /** | |
| 45 * List of api methods. These are forwarded to the browser plugin. | |
| 46 */ | |
| 47 var AD_VIEW_API_METHODS = [ | |
| 48 // Empty for now. | |
| 49 ]; | |
| 50 | |
| 51 var createEvent = function(name) { | |
| 52 var eventOpts = {supportsListeners: true, supportsFilters: true}; | |
| 53 return new eventBindings.Event(name, undefined, eventOpts); | |
| 54 }; | |
| 55 | |
| 56 var AdviewLoadAbortEvent = createEvent('adview.onLoadAbort'); | |
| 57 var AdviewLoadCommitEvent = createEvent('adview.onLoadCommit'); | |
| 58 | |
| 59 var AD_VIEW_EXT_EVENTS = { | |
| 60 'loadabort': { | |
| 61 evt: AdviewLoadAbortEvent, | |
| 62 fields: ['url', 'isTopLevel', 'reason'] | |
| 63 }, | |
| 64 'loadcommit': { | |
| 65 evt: AdviewLoadCommitEvent, | |
| 66 fields: ['url', 'isTopLevel'] | |
| 67 } | |
| 68 }; | |
| 69 | |
| 70 /** | |
| 71 * List of supported ad-networks. | |
| 72 * | |
| 73 * name: identifier of the ad-network, corresponding to a valid value | |
| 74 * of the "ad-network" attribute of an <adview> element. | |
| 75 * url: url to navigate to when initially displaying the <adview>. | |
| 76 * origin: origin of urls the <adview> is allowed navigate to. | |
| 77 */ | |
| 78 var AD_VIEW_AD_NETWORKS_WHITELIST = [ | |
| 79 { | |
| 80 name: 'admob', | |
| 81 url: 'https://admob-sdk.doubleclick.net/chromeapps', | |
| 82 origin: 'https://double.net' | |
| 83 }, | |
| 84 ]; | |
| 85 | |
| 86 /** | |
| 87 * Return the whitelisted ad-network entry named |name|. | |
| 88 */ | |
| 89 function getAdNetworkInfo(name) { | |
| 90 var result = null; | |
| 91 $Array.forEach(AD_VIEW_AD_NETWORKS_WHITELIST, function(item) { | |
| 92 if (item.name === name) | |
| 93 result = item; | |
| 94 }); | |
| 95 return result; | |
| 96 } | |
| 97 | |
| 98 /** | |
| 99 * @constructor | |
| 100 */ | |
| 101 function AdView(adviewNode) { | |
| 102 this.adviewNode_ = adviewNode; | |
| 103 this.browserPluginNode_ = this.createBrowserPluginNode_(); | |
| 104 var shadowRoot = this.adviewNode_.createShadowRoot(); | |
| 105 shadowRoot.appendChild(this.browserPluginNode_); | |
| 106 | |
| 107 this.setupCustomAttributes_(); | |
| 108 this.setupAdviewNodeObservers_(); | |
| 109 this.setupAdviewNodeMethods_(); | |
| 110 this.setupAdviewNodeProperties_(); | |
| 111 this.setupAdviewNodeEvents_(); | |
| 112 this.setupBrowserPluginNodeObservers_(); | |
| 113 } | |
| 114 | |
| 115 /** | |
| 116 * @private | |
| 117 */ | |
| 118 AdView.prototype.createBrowserPluginNode_ = function() { | |
| 119 var browserPluginNode = document.createElement('object'); | |
| 120 browserPluginNode.type = 'application/browser-plugin'; | |
| 121 // The <object> node fills in the <adview> container. | |
| 122 browserPluginNode.style.width = '100%'; | |
| 123 browserPluginNode.style.height = '100%'; | |
| 124 $Array.forEach(AD_VIEW_ATTRIBUTES, function(attributeName) { | |
| 125 // Only copy attributes that have been assigned values, rather than copying | |
| 126 // a series of undefined attributes to BrowserPlugin. | |
| 127 if (this.adviewNode_.hasAttribute(attributeName)) { | |
| 128 browserPluginNode.setAttribute( | |
| 129 attributeName, this.adviewNode_.getAttribute(attributeName)); | |
| 130 } | |
| 131 }, this); | |
| 132 | |
| 133 return browserPluginNode; | |
| 134 } | |
| 135 | |
| 136 /** | |
| 137 * @private | |
| 138 */ | |
| 139 AdView.prototype.setupCustomAttributes_ = function() { | |
| 140 $Array.forEach(AD_VIEW_CUSTOM_ATTRIBUTES, function(attributeInfo) { | |
| 141 if (attributeInfo.onMutation) { | |
| 142 attributeInfo.onMutation(this); | |
| 143 } | |
| 144 }, this); | |
| 145 } | |
| 146 | |
| 147 /** | |
| 148 * @private | |
| 149 */ | |
| 150 AdView.prototype.setupAdviewNodeMethods_ = function() { | |
| 151 // this.browserPluginNode_[apiMethod] are not necessarily defined immediately | |
| 152 // after the shadow object is appended to the shadow root. | |
| 153 var self = this; | |
| 154 $Array.forEach(AD_VIEW_API_METHODS, function(apiMethod) { | |
| 155 self.adviewNode_[apiMethod] = function(var_args) { | |
| 156 return $Function.apply(self.browserPluginNode_[apiMethod], | |
| 157 self.browserPluginNode_, arguments); | |
| 158 }; | |
| 159 }, this); | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * @private | |
| 164 */ | |
| 165 AdView.prototype.setupAdviewNodeObservers_ = function() { | |
| 166 // Map attribute modifications on the <adview> tag to property changes in | |
| 167 // the underlying <object> node. | |
| 168 var handleMutation = $Function.bind(function(mutation) { | |
| 169 this.handleAdviewAttributeMutation_(mutation); | |
| 170 }, this); | |
| 171 var observer = new MutationObserver(function(mutations) { | |
| 172 $Array.forEach(mutations, handleMutation); | |
| 173 }); | |
| 174 observer.observe( | |
| 175 this.adviewNode_, | |
| 176 {attributes: true, attributeFilter: AD_VIEW_ATTRIBUTES}); | |
| 177 | |
| 178 this.setupAdviewNodeCustomObservers_(); | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * @private | |
| 183 */ | |
| 184 AdView.prototype.setupAdviewNodeCustomObservers_ = function() { | |
| 185 var handleMutation = $Function.bind(function(mutation) { | |
| 186 this.handleAdviewCustomAttributeMutation_(mutation); | |
| 187 }, this); | |
| 188 var observer = new MutationObserver(function(mutations) { | |
| 189 $Array.forEach(mutations, handleMutation); | |
| 190 }); | |
| 191 var customAttributeNames = | |
| 192 AD_VIEW_CUSTOM_ATTRIBUTES.map(function(item) { return item.name; }); | |
| 193 observer.observe( | |
| 194 this.adviewNode_, | |
| 195 {attributes: true, attributeFilter: customAttributeNames}); | |
| 196 } | |
| 197 | |
| 198 /** | |
| 199 * @private | |
| 200 */ | |
| 201 AdView.prototype.setupBrowserPluginNodeObservers_ = function() { | |
| 202 var handleMutation = $Function.bind(function(mutation) { | |
| 203 this.handleBrowserPluginAttributeMutation_(mutation); | |
| 204 }, this); | |
| 205 var objectObserver = new MutationObserver(function(mutations) { | |
| 206 $Array.forEach(mutations, handleMutation); | |
| 207 }); | |
| 208 objectObserver.observe( | |
| 209 this.browserPluginNode_, | |
| 210 {attributes: true, attributeFilter: AD_VIEW_ATTRIBUTES}); | |
| 211 } | |
| 212 | |
| 213 /** | |
| 214 * @private | |
| 215 */ | |
| 216 AdView.prototype.setupAdviewNodeProperties_ = function() { | |
| 217 var browserPluginNode = this.browserPluginNode_; | |
| 218 // Expose getters and setters for the attributes. | |
| 219 $Array.forEach(AD_VIEW_ATTRIBUTES, function(attributeName) { | |
| 220 Object.defineProperty(this.adviewNode_, attributeName, { | |
| 221 get: function() { | |
| 222 return browserPluginNode[attributeName]; | |
| 223 }, | |
| 224 set: function(value) { | |
| 225 browserPluginNode[attributeName] = value; | |
| 226 }, | |
| 227 enumerable: true | |
| 228 }); | |
| 229 }, this); | |
| 230 | |
| 231 // Expose getters and setters for the custom attributes. | |
| 232 var adviewNode = this.adviewNode_; | |
| 233 $Array.forEach(AD_VIEW_CUSTOM_ATTRIBUTES, function(attributeInfo) { | |
| 234 if (attributeInfo.isProperty()) { | |
| 235 var attributeName = attributeInfo.name; | |
| 236 Object.defineProperty(this.adviewNode_, attributeName, { | |
| 237 get: function() { | |
| 238 return adviewNode.getAttribute(attributeName); | |
| 239 }, | |
| 240 set: function(value) { | |
| 241 adviewNode.setAttribute(attributeName, value); | |
| 242 }, | |
| 243 enumerable: true | |
| 244 }); | |
| 245 } | |
| 246 }, this); | |
| 247 | |
| 248 this.setupAdviewContentWindowProperty_(); | |
| 249 } | |
| 250 | |
| 251 /** | |
| 252 * @private | |
| 253 */ | |
| 254 AdView.prototype.setupAdviewContentWindowProperty_ = function() { | |
| 255 var browserPluginNode = this.browserPluginNode_; | |
| 256 // We cannot use {writable: true} property descriptor because we want dynamic | |
| 257 // getter value. | |
| 258 Object.defineProperty(this.adviewNode_, 'contentWindow', { | |
| 259 get: function() { | |
| 260 // TODO(fsamuel): This is a workaround to enable | |
| 261 // contentWindow.postMessage until http://crbug.com/152006 is fixed. | |
| 262 if (browserPluginNode.contentWindow) | |
| 263 return browserPluginNode.contentWindow.self; | |
| 264 console.error('contentWindow is not available at this time. ' + | |
| 265 'It will become available when the page has finished loading.'); | |
| 266 }, | |
| 267 // No setter. | |
| 268 enumerable: true | |
| 269 }); | |
| 270 } | |
| 271 | |
| 272 /** | |
| 273 * @private | |
| 274 */ | |
| 275 AdView.prototype.handleAdviewAttributeMutation_ = function(mutation) { | |
| 276 // This observer monitors mutations to attributes of the <adview> and | |
| 277 // updates the BrowserPlugin properties accordingly. In turn, updating | |
| 278 // a BrowserPlugin property will update the corresponding BrowserPlugin | |
| 279 // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more | |
| 280 // details. | |
| 281 this.browserPluginNode_[mutation.attributeName] = | |
| 282 this.adviewNode_.getAttribute(mutation.attributeName); | |
| 283 }; | |
| 284 | |
| 285 /** | |
| 286 * @private | |
| 287 */ | |
| 288 AdView.prototype.handleAdviewCustomAttributeMutation_ = function(mutation) { | |
| 289 $Array.forEach(AD_VIEW_CUSTOM_ATTRIBUTES, function(item) { | |
| 290 if (mutation.attributeName.toUpperCase() == item.name.toUpperCase()) { | |
| 291 if (item.onMutation) { | |
| 292 $Function.bind(item.onMutation, item)(this, mutation); | |
| 293 } | |
| 294 } | |
| 295 }, this); | |
| 296 }; | |
| 297 | |
| 298 /** | |
| 299 * @private | |
| 300 */ | |
| 301 AdView.prototype.handleBrowserPluginAttributeMutation_ = function(mutation) { | |
| 302 // This observer monitors mutations to attributes of the BrowserPlugin and | |
| 303 // updates the <adview> attributes accordingly. | |
| 304 if (!this.browserPluginNode_.hasAttribute(mutation.attributeName)) { | |
| 305 // If an attribute is removed from the BrowserPlugin, then remove it | |
| 306 // from the <adview> as well. | |
| 307 this.adviewNode_.removeAttribute(mutation.attributeName); | |
| 308 } else { | |
| 309 // Update the <adview> attribute to match the BrowserPlugin attribute. | |
| 310 // Note: Calling setAttribute on <adview> will trigger its mutation | |
| 311 // observer which will then propagate that attribute to BrowserPlugin. In | |
| 312 // cases where we permit assigning a BrowserPlugin attribute the same value | |
| 313 // again (such as navigation when crashed), this could end up in an infinite | |
| 314 // loop. Thus, we avoid this loop by only updating the <adview> attribute | |
| 315 // if the BrowserPlugin attributes differs from it. | |
| 316 var oldValue = this.adviewNode_.getAttribute(mutation.attributeName); | |
| 317 var newValue = this.browserPluginNode_.getAttribute(mutation.attributeName); | |
| 318 if (newValue != oldValue) { | |
| 319 this.adviewNode_.setAttribute(mutation.attributeName, newValue); | |
| 320 } | |
| 321 } | |
| 322 }; | |
| 323 | |
| 324 /** | |
| 325 * @public | |
| 326 */ | |
| 327 AdView.prototype.handleAdNetworkMutation = function(mutation) { | |
| 328 if (this.adviewNode_.hasAttribute('ad-network')) { | |
| 329 var value = this.adviewNode_.getAttribute('ad-network'); | |
| 330 var item = getAdNetworkInfo(value); | |
| 331 if (!item) { | |
| 332 // Ignore the new attribute value and set it to empty string. | |
| 333 // Avoid infinite loop by checking for empty string as new value. | |
| 334 if (value != '') { | |
| 335 console.error('The ad-network "' + value + '" is not recognized.'); | |
| 336 this.adviewNode_.setAttribute('ad-network', ''); | |
| 337 } | |
| 338 } | |
| 339 } | |
| 340 } | |
| 341 | |
| 342 /** | |
| 343 * @private | |
| 344 */ | |
| 345 AdView.prototype.setupAdviewNodeEvents_ = function() { | |
| 346 var self = this; | |
| 347 var onInstanceIdAllocated = function(e) { | |
| 348 var detail = e.detail ? JSON.parse(e.detail) : {}; | |
| 349 self.instanceId_ = detail.windowId; | |
| 350 var params = { | |
| 351 'api': 'adview' | |
| 352 }; | |
| 353 self.browserPluginNode_['-internal-attach'](params); | |
| 354 | |
| 355 for (var eventName in AD_VIEW_EXT_EVENTS) { | |
| 356 self.setupExtEvent_(eventName, AD_VIEW_EXT_EVENTS[eventName]); | |
| 357 } | |
| 358 }; | |
| 359 this.browserPluginNode_.addEventListener('-internal-instanceid-allocated', | |
| 360 onInstanceIdAllocated); | |
| 361 } | |
| 362 | |
| 363 /** | |
| 364 * @private | |
| 365 */ | |
| 366 AdView.prototype.setupExtEvent_ = function(eventName, eventInfo) { | |
| 367 var self = this; | |
| 368 var adviewNode = this.adviewNode_; | |
| 369 eventInfo.evt.addListener(function(event) { | |
| 370 var adviewEvent = new Event(eventName, {bubbles: true}); | |
| 371 $Array.forEach(eventInfo.fields, function(field) { | |
| 372 adviewEvent[field] = event[field]; | |
| 373 }); | |
| 374 if (eventInfo.customHandler) { | |
| 375 eventInfo.customHandler(self, event); | |
| 376 } | |
| 377 adviewNode.dispatchEvent(adviewEvent); | |
| 378 }, {instanceId: self.instanceId_}); | |
| 379 }; | |
| 380 | |
| 381 /** | |
| 382 * @public | |
| 383 */ | |
| 384 AdView.prototype.dispatchEvent = function(eventname, detail) { | |
| 385 // Create event object. | |
| 386 var evt = new Event(eventname, { bubbles: true }); | |
| 387 for(var item in detail) { | |
| 388 evt[item] = detail[item]; | |
| 389 } | |
| 390 | |
| 391 // Dispatch event. | |
| 392 this.adviewNode_.dispatchEvent(evt); | |
| 393 } | |
| 394 | |
| 395 addTagWatcher('ADVIEW', function(addedNode) { new AdView(addedNode); }); | |
| OLD | NEW |