| 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 |