OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 goog.provide('image.collections.extension.DomController'); | 5 goog.provide('image.collections.extension.domextractor.DomController'); |
6 | 6 |
7 goog.require('goog.Timer'); | 7 goog.require('image.collections.extension.domextractor.DocumentImage'); |
8 goog.require('goog.array'); | 8 goog.require('image.collections.extension.domextractor.DocumentVideo'); |
9 goog.require('goog.dom'); | 9 goog.require('image.collections.extension.domextractor.DomUtils'); |
10 goog.require('goog.events.EventType'); | |
11 goog.require('goog.log'); | |
12 goog.require('gws.collections.common.Constants'); | |
13 goog.require('image.collections.extension.Controller'); | |
14 goog.require('image.collections.extension.DocumentImage'); | |
15 goog.require('image.collections.extension.DocumentVideo'); | |
16 goog.require('image.collections.extension.DomEvent'); | |
17 | 10 |
18 goog.scope(function() { | 11 goog.scope(function() { |
19 var Constants = gws.collections.common.Constants; | 12 var DocumentImage = image.collections.extension.domextractor.DocumentImage; |
20 var DocumentImage = image.collections.extension.DocumentImage; | 13 var DocumentVideo = image.collections.extension.domextractor.DocumentVideo; |
21 var DocumentVideo = image.collections.extension.DocumentVideo; | 14 var DomUtils = image.collections.extension.domextractor.DomUtils; |
22 var DomEvent = image.collections.extension.DomEvent; | 15 |
23 | 16 |
24 | 17 |
25 /** | 18 /** |
26 * This class handles page DOM events and implements DOM manipulation. | 19 * This class handles page DOM events and implements DOM manipulation. |
27 * It should be instantiated by a content script. | 20 * It should be instantiated by a content script. |
28 * TODO(busaryev): preloading may not be the best choice for mobile clients. | 21 * TODO(busaryev): preloading may not be the best choice for mobile clients. |
29 * @extends {image.collections.extension.Controller} | |
30 * @constructor | 22 * @constructor |
31 */ | 23 */ |
32 image.collections.extension.DomController = function() { | 24 image.collections.extension.domextractor.DomController = function() { |
33 DomController.base(this, 'constructor'); | |
34 | |
35 /** @private {number} Number of DOM elements left. */ | 25 /** @private {number} Number of DOM elements left. */ |
36 this.numElementsToProcess_ = 0; | 26 this.numElementsToProcess_ = 0; |
37 | 27 |
38 /** @private {number} The timeout id for goog.Timer.callOnce. */ | 28 /** |
39 this.timeoutId_ = -1; | 29 * Promise returned by initialize() call. Resolves when all elements have been |
| 30 * processed, or alternatively when a timeout has been reached. |
| 31 * @private {!Promise} |
| 32 */ |
| 33 this.initializedPromise_ = Promise.resolve(); |
| 34 |
| 35 /** @private {?function()} Resolve function for the initialized promise. */ |
| 36 this.initializedPromiseResolve_ = null; |
40 }; | 37 }; |
41 goog.inherits(image.collections.extension.DomController, | 38 var DomController = image.collections.extension.domextractor.DomController; |
42 image.collections.extension.Controller); | |
43 var DomController = image.collections.extension.DomController; | |
44 | |
45 | |
46 /** @private {goog.log.Logger} */ | |
47 DomController.logger_ = goog.log.getLogger( | |
48 'image.collections.extension.DomController'); | |
49 | 39 |
50 | 40 |
51 /** | 41 /** |
52 * @private {number} The number of milliseconds to wait for the load | 42 * @private {number} The number of milliseconds to wait for the load |
53 * event to occur before giving up. This ensures we are not wasting too much | 43 * event to occur before giving up. This ensures we are not wasting too much |
54 * time trying to get the clip. | 44 * time trying to get the clip. |
55 */ | 45 */ |
56 DomController.LOAD_TIMEOUT_MS_ = 5000; | 46 DomController.LOAD_TIMEOUT_MS_ = 5000; |
57 | 47 |
58 | 48 |
59 /** @override */ | |
60 DomController.prototype.initialize = function(parentEventTarget) { | |
61 DomController.base(this, 'initialize', parentEventTarget); | |
62 | |
63 this.eventHandler. | |
64 listen(parentEventTarget, DomEvent.Type.INITIALIZE_DOM, | |
65 this.handleInitializeDom_); | |
66 }; | |
67 | |
68 | |
69 /** | 49 /** |
70 * @param {DomEvent} e | 50 * Initializes the DomController. |
71 * @private | 51 * @return {!Promise} A promise that resolves when all elements have been |
| 52 * processed, or alternatively after a timeout has expired. |
72 */ | 53 */ |
73 DomController.prototype.handleInitializeDom_ = function(e) { | 54 DomController.prototype.initialize = function() { |
74 if (this.numElementsToProcess_ == 0) { | 55 if (this.numElementsToProcess_ == 0) { |
75 // Find <meta> and <link> tags that specify canonical page images, compute | 56 // Find <meta> and <link> tags that specify canonical page images, compute |
76 // image sizes with preloading and store them in element attributes. | 57 // image sizes with preloading and store them in element attributes. |
77 var doc = goog.dom.getDocument(); | 58 var metaElements = document.getElementsByTagName('meta'); |
78 var metaElements = doc.getElementsByTagName('meta'); | 59 var linkElements = document.getElementsByTagName('link'); |
79 var linkElements = doc.getElementsByTagName('link'); | |
80 this.numElementsToProcess_ = metaElements.length + linkElements.length; | 60 this.numElementsToProcess_ = metaElements.length + linkElements.length; |
81 if (this.numElementsToProcess_ > 0) { | 61 if (this.numElementsToProcess_ > 0) { |
82 goog.array.forEach(metaElements, this.processMetaElement_, this); | 62 this.initializedPromise_ = |
83 goog.array.forEach(linkElements, this.processLinkElement_, this); | 63 new Promise(function(resolve, reject) { |
84 this.timeoutId_ = goog.Timer.callOnce( | 64 this.initializedPromiseResolve_ = resolve; |
85 goog.bind(this.dispatchEvent, this, DomEvent.Type.DOM_INITIALIZED), | 65 }.bind(this)); |
| 66 for (var i = 0; i < metaElements.length; i++) { |
| 67 this.processMetaElement_(metaElements[i]); |
| 68 } |
| 69 for (var i = 0; i < linkElements.length; i++) { |
| 70 this.processLinkElement_(linkElements[i]); |
| 71 } |
| 72 setTimeout(this.initializedPromiseResolve_, |
86 DomController.LOAD_TIMEOUT_MS_); | 73 DomController.LOAD_TIMEOUT_MS_); |
| 74 return this.initializedPromise_; |
87 } else { | 75 } else { |
88 this.dispatchEvent(DomEvent.Type.DOM_INITIALIZED); | 76 this.initializedPromise_ = Promise.resolve(); |
| 77 return this.initializedPromise_; |
89 } | 78 } |
| 79 } else { |
| 80 return this.initializedPromise_; |
90 } | 81 } |
91 }; | 82 }; |
92 | 83 |
93 | 84 |
94 /** | 85 /** |
95 * Tries to compute the size of the image specified in a <meta> element. | 86 * Tries to compute the size of the image specified in a <meta> element. |
96 * @param {Element} element The element to process. | 87 * @param {Element} element The element to process. |
97 * @param {number} index Index of the element in the array. | |
98 * @param {goog.array.ArrayLike} array The array. | |
99 * @private | 88 * @private |
100 */ | 89 */ |
101 DomController.prototype.processMetaElement_ = function(element, index, array) { | 90 DomController.prototype.processMetaElement_ = function(element) { |
102 var url = ''; | 91 var url = ''; |
103 if (element.hasAttribute('property')) { | 92 if (element.hasAttribute('property')) { |
104 switch (element.getAttribute('property').toLowerCase()) { | 93 switch (element.getAttribute('property').toLowerCase()) { |
105 case 'og:image': | 94 case 'og:image': |
106 url = element.getAttribute('content'); | 95 url = element.getAttribute('content'); |
107 var siblings = goog.dom.getChildren(goog.dom.getParentElement(element)); | 96 var siblings = DomUtils.getParentElement(element).children; |
108 var width = this.getPropertyContent_(siblings, 'og:image:width'); | 97 var width = this.getPropertyContent_(siblings, 'og:image:width'); |
109 var height = this.getPropertyContent_(siblings, 'og:image:height'); | 98 var height = this.getPropertyContent_(siblings, 'og:image:height'); |
110 if (width > 0 && height > 0) { | 99 if (width > 0 && height > 0) { |
111 element.setAttribute(DocumentImage.CustomAttribute.WIDTH, width); | 100 element.setAttribute(DocumentImage.CustomAttribute.WIDTH, width); |
112 element.setAttribute(DocumentImage.CustomAttribute.HEIGHT, height); | 101 element.setAttribute(DocumentImage.CustomAttribute.HEIGHT, height); |
113 } | 102 } |
114 break; | 103 break; |
115 case 'og:video': | 104 case 'og:video': |
116 var children = goog.dom.getChildren(goog.dom.getParentElement(element)); | 105 var children = DomUtils.getParentElement(element).children; |
117 var width = this.getPropertyContent_(children, 'og:video:width'); | 106 var width = this.getPropertyContent_(children, 'og:video:width'); |
118 var height = this.getPropertyContent_(children, 'og:video:height'); | 107 var height = this.getPropertyContent_(children, 'og:video:height'); |
119 if (width > 0 && height > 0) { | 108 if (width > 0 && height > 0) { |
120 element.setAttribute(DocumentVideo.CustomAttribute.WIDTH, width); | 109 element.setAttribute(DocumentVideo.CustomAttribute.WIDTH, width); |
121 element.setAttribute(DocumentVideo.CustomAttribute.HEIGHT, height); | 110 element.setAttribute(DocumentVideo.CustomAttribute.HEIGHT, height); |
122 } | 111 } |
123 } | 112 } |
124 } else if (element.hasAttribute('name')) { | 113 } else if (element.hasAttribute('name')) { |
125 switch (element.getAttribute('name').toLowerCase()) { | 114 switch (element.getAttribute('name').toLowerCase()) { |
126 case 'msapplication-tileimage': | 115 case 'msapplication-tileimage': |
127 case 'twitter:image': | 116 case 'twitter:image': |
128 url = element.getAttribute('content'); | 117 url = element.getAttribute('content'); |
129 } | 118 } |
130 } else if (element.hasAttribute('itemprop')) { | 119 } else if (element.hasAttribute('itemprop')) { |
131 switch (element.getAttribute('itemprop').toLowerCase()) { | 120 switch (element.getAttribute('itemprop').toLowerCase()) { |
132 case 'thumbnailurl': | 121 case 'thumbnailurl': |
133 url = element.getAttribute('href') || | 122 url = element.getAttribute('href') || |
134 element.getAttribute('content'); | 123 element.getAttribute('content'); |
135 } | 124 } |
136 } | 125 } |
137 this.maybeComputeAndStoreImageSize_(url, element); | 126 this.maybeComputeAndStoreImageSize_(url, element); |
138 }; | 127 }; |
139 | 128 |
140 | 129 |
141 /** | 130 /** |
142 * Tries to compute the size of the image specified in a <link> element. | 131 * Tries to compute the size of the image specified in a <link> element. |
143 * @param {Element} element The element to process. | 132 * @param {Element} element The element to process. |
144 * @param {number} index Index of the element in the array. | |
145 * @param {goog.array.ArrayLike} array The array. | |
146 * @private | 133 * @private |
147 */ | 134 */ |
148 DomController.prototype.processLinkElement_ = function(element, index, array) { | 135 DomController.prototype.processLinkElement_ = function(element) { |
149 var url = ''; | 136 var url = ''; |
150 if (element.hasAttribute('rel')) { | 137 if (element.hasAttribute('rel')) { |
151 switch (element.getAttribute('rel').toLowerCase()) { | 138 switch (element.getAttribute('rel').toLowerCase()) { |
152 case 'apple-touch-icon-precomposed': | 139 case 'apple-touch-icon-precomposed': |
153 case 'apple-touch-icon': | 140 case 'apple-touch-icon': |
154 case 'image_src': | 141 case 'image_src': |
155 url = element.getAttribute('href'); | 142 url = element.getAttribute('href'); |
156 } | 143 } |
157 } else if (element.hasAttribute('itemprop')) { | 144 } else if (element.hasAttribute('itemprop')) { |
158 switch (element.getAttribute('itemprop').toLowerCase()) { | 145 switch (element.getAttribute('itemprop').toLowerCase()) { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
190 * Tries to store the size of the element image in custom element tags. | 177 * Tries to store the size of the element image in custom element tags. |
191 * @param {string} url Image url (empty if the element defines no image). | 178 * @param {string} url Image url (empty if the element defines no image). |
192 * @param {!Element} element Element. | 179 * @param {!Element} element Element. |
193 * @private | 180 * @private |
194 */ | 181 */ |
195 DomController.prototype.maybeComputeAndStoreImageSize_ = function( | 182 DomController.prototype.maybeComputeAndStoreImageSize_ = function( |
196 url, element) { | 183 url, element) { |
197 var CustomAttribute = DocumentImage.CustomAttribute; | 184 var CustomAttribute = DocumentImage.CustomAttribute; |
198 if (url && (!element.hasAttribute(CustomAttribute.WIDTH) || | 185 if (url && (!element.hasAttribute(CustomAttribute.WIDTH) || |
199 !element.hasAttribute(CustomAttribute.HEIGHT))) { | 186 !element.hasAttribute(CustomAttribute.HEIGHT))) { |
200 this.computeImageSize_(url, goog.bind(this.storeImageSize_, this, element)); | 187 this.computeImageSize_(url, this.storeImageSize_.bind(this, element)); |
201 } else { | 188 } else { |
202 this.maybeDispatchDomInitialized_(); | 189 this.maybeResolveInitializedPromise_(); |
203 } | 190 } |
204 }; | 191 }; |
205 | 192 |
206 | 193 |
207 /** | 194 /** |
208 * Computes the image size with preloading and returns it via a callback. | 195 * Computes the image size with preloading and returns it via a callback. |
209 * @param {string} url Image url. | 196 * @param {string} url Image url. |
210 * @param {!function(number, number)} callback A callback. | 197 * @param {!function(number, number)} callback A callback. |
211 * @private | 198 * @private |
212 */ | 199 */ |
213 DomController.prototype.computeImageSize_ = function(url, callback) { | 200 DomController.prototype.computeImageSize_ = function(url, callback) { |
214 var image = new Image(); | 201 var image = new Image(); |
215 this.eventHandler.listenOnce(image, | 202 |
216 [goog.events.EventType.LOAD, goog.events.EventType.ERROR], | 203 var that = this; |
217 goog.bind(this.handleImageLoadOrError_, this, callback)); | 204 var handleImageLoadOrError = function(e) { |
| 205 if (e.type == 'load') { |
| 206 callback(image.naturalWidth, image.naturalHeight); |
| 207 } |
| 208 that.maybeResolveInitializedPromise_(); |
| 209 image.removeEventListener('load', handleImageLoadOrError); |
| 210 image.removeEventListener('error', handleImageLoadOrError); |
| 211 }; |
| 212 |
| 213 image.addEventListener('load', handleImageLoadOrError); |
| 214 image.addEventListener('error', handleImageLoadOrError); |
218 image.src = url; | 215 image.src = url; |
219 }; | 216 }; |
220 | 217 |
221 | 218 |
222 /** | 219 /** |
223 * Handles image LOAD and ERROR events. | 220 * Resolves the initialized promise if all elements have been processed. |
224 * @param {!function(number, number)} callback A callback. | |
225 * @param {goog.events.Event} e Image event. | |
226 * @private | 221 * @private |
227 */ | 222 */ |
228 DomController.prototype.handleImageLoadOrError_ = function(callback, e) { | 223 DomController.prototype.maybeResolveInitializedPromise_ = function() { |
229 var image = /** @type {!Image} */ (e.target); | |
230 if (e.type == goog.events.EventType.LOAD) { | |
231 callback(image.naturalWidth, image.naturalHeight); | |
232 } else { | |
233 goog.log.warning(DomController.logger_, | |
234 'Failed to load image ' + image.src); | |
235 } | |
236 this.maybeDispatchDomInitialized_(); | |
237 }; | |
238 | |
239 | |
240 /** | |
241 * Dispatches the DOM_INITIALIZED event if all elements have been processed. | |
242 * @private | |
243 */ | |
244 DomController.prototype.maybeDispatchDomInitialized_ = function() { | |
245 if (--this.numElementsToProcess_ == 0) { | 224 if (--this.numElementsToProcess_ == 0) { |
246 if (this.timeoutId_ != -1) { | 225 this.initializedPromiseResolve_(); |
247 goog.Timer.clear(this.timeoutId_); | |
248 this.timeoutId_ = -1; | |
249 } | |
250 this.dispatchEvent(DomEvent.Type.DOM_INITIALIZED); | |
251 } | 226 } |
252 }; | 227 }; |
253 | 228 |
254 | 229 |
255 /** | 230 /** |
256 * Stores the image size in custom element attributes. | 231 * Stores the image size in custom element attributes. |
257 * @param {!Element} element An element defining the image url. | 232 * @param {!Element} element An element defining the image url. |
258 * @param {number} width Image width. | 233 * @param {number} width Image width. |
259 * @param {number} height Image height. | 234 * @param {number} height Image height. |
260 * @private | 235 * @private |
261 */ | 236 */ |
262 DomController.prototype.storeImageSize_ = function(element, width, height) { | 237 DomController.prototype.storeImageSize_ = function(element, width, height) { |
263 var CustomAttribute = DocumentImage.CustomAttribute; | 238 var CustomAttribute = DocumentImage.CustomAttribute; |
264 element.setAttribute(CustomAttribute.WIDTH, width); | 239 element.setAttribute(CustomAttribute.WIDTH, width); |
265 element.setAttribute(CustomAttribute.HEIGHT, height); | 240 element.setAttribute(CustomAttribute.HEIGHT, height); |
266 }; | 241 }; |
267 | 242 |
268 }); // goog.scope | 243 }); // goog.scope |
OLD | NEW |