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); | |
asargent_no_longer_on_chrome
2013/03/12 20:54:27
nit: remove this debugging line
rpaquay
2013/03/12 23:05:36
Done.
| |
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) { | |
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.
| |
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 |