1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * This file contains the object manager.
 27  * 
 28  */
 29 
 30 /**
 31  * Creates an object manager.
 32  * @class
 33  * This class is used to high-light objects within a given view.
 34  * 
 35  * @author Kevin Henrikson
 36  *
 37  * @param {DwtComposite}	view			the view this manager is going to high-light for
 38  * @param {AjxCallback}	selectCallback  the callback triggered when user clicks on high-light object (provide if you want to do something before the clicked on object opens its corresponding view)
 39  * @param {Boolean}	skipHandlers 	<code>true</code> to avoid adding the standard handlers
 40  */
 41 ZmObjectManager = function(view, selectCallback, skipHandlers) {
 42 
 43 	this._selectCallback = selectCallback;
 44 	this._uuid = Dwt.getNextId();
 45 	this._objectIdPrefix = "OBJ_PREFIX_";
 46 	this._objectHandlers = {};
 47 
 48 	// don't include when looking for objects. only used to provide tool tips for images
 49 	if (appCtxt.get(ZmSetting.MAIL_ENABLED) && window["ZmImageAttachmentObjectHandler"]) {
 50 		this._imageAttachmentHandler = new ZmImageAttachmentObjectHandler();
 51 	}
 52 
 53 	// create handlers (see registerHandler below)
 54 	if (!skipHandlers) {
 55         this.initialized = false;
 56         this._addAutoHandlers();
 57 	} else {
 58         this.initialized = true;
 59     }
 60 
 61     this.sortHandlers();
 62 	this.reset();
 63 	this.setView(view);
 64 };
 65 
 66 ZmObjectManager._TOOLTIP_DELAY = 275;
 67 
 68 // Define common types for quicker object matching.
 69 ZmObjectManager.EMAIL = "email";
 70 ZmObjectManager.URL = "url";
 71 ZmObjectManager.PHONE = "phone";
 72 ZmObjectManager.DATE = "date";
 73 ZmObjectManager.ADDRESS = "address";
 74 
 75 // Allows callers to pass in a current date
 76 ZmObjectManager.ATTR_CURRENT_DATE = "currentDate";
 77 
 78 ZmObjectManager._autohandlers = [];
 79 
 80 /**
 81  * Registers the handler.
 82  * 
 83  * @param	{Object}	obj		the object
 84  * @param	{constant}	type	the type
 85  * @param	{constant}	priority	the priority
 86  */
 87 ZmObjectManager.registerHandler =
 88 function(obj, type, priority) {
 89 	if (typeof obj == "string") {
 90 		obj = eval(obj);
 91 	}
 92 	var c = ZmObjectManager._autohandlers;
 93 	if (!obj.__registered) {
 94 		var id = c.push(obj);
 95 		var i = id - 1;
 96 		if(type) {
 97 			c[i].useType = type;
 98 		}
 99 		if(priority) {
100 			c[i].usePrio = priority;
101 		}
102 		obj.__registered = true;
103 	}
104 };
105 
106 /**
107  * @private
108  */
109 ZmObjectManager.unregisterHandler =
110 function(obj) {
111 	if (typeof obj == "string") {
112 		obj = eval(obj);
113 	}
114  	var c = ZmObjectManager._autohandlers, i;
115 	for (i = c.length; --i >= 0;) {
116 		if (c[i] === obj) {
117 			c.splice(i, 1);
118 			break;
119 		}
120 	}
121 };
122 
123 /**
124  * Returns a string representation of the object.
125  * 
126  * @return		{String}		a string representation of the object
127  */
128 ZmObjectManager.prototype.toString =
129 function() {
130 	return "ZmObjectManager";
131 };
132 
133 /**
134  * Gets the handlers.
135  * 
136  * @return	{Array}	an array of {@link ZmObjectHandler} objects
137  */
138 ZmObjectManager.prototype.getHandlers =
139 function() {
140 	if (!this.initialized) {
141 		var zimletMgr = appCtxt.getZimletMgr();
142 		if (zimletMgr.isLoaded()) {
143 			this.initialized = true;
144 			var zimlets = zimletMgr.getContentZimlets();
145 			for (var i = 0; i < zimlets.length; i++) {
146 				this.addHandler(zimlets[i], zimlets[i].type, zimlets[i].prio);
147 			}
148 		}
149 	}
150 	return this._objectHandlers;
151 };
152 
153 /**
154  * Adds the handler.
155  * 
156  * @param	{ZmObjectHandler}	h		the handler
157  * @param	{constant}			type	the type
158  * @param	{constant}		priority	the priority
159  */
160 ZmObjectManager.prototype.addHandler =
161 function(h, type, priority) {
162 	type = type || (h.getTypeName() ? h.getTypeName() : "none");
163 	priority = priority ? priority : -1;
164 	h._prio = priority;
165 	//DBG.println(AjxDebug.DBG3, "addHandler " + h + " type: " + type + " prio: " + priority);
166 	var oh = this.getHandlers();
167 	if (!oh[type]) {oh[type] = [];}
168 	oh[type].push(h);
169 };
170 
171 /**
172  * Removes the handler.
173  * 
174  * @param	{ZmObjectHandler}	h		the handler
175  * @param	{constant}			type	the type
176  */
177 ZmObjectManager.prototype.removeHandler =
178 function(h, type) {
179 	type = type || (h.getTypeName() ? h.getTypeName() : "none");
180 	var oh = this.getHandlers();
181 	if (oh[type]) {
182 		for (var i = 0, count = oh[type].length; i < count; i++) {
183 			if (oh[type][i] == h) {
184 				oh[type].splice(i, 1);
185 				break;
186 			}
187 		}
188 	}
189 };
190 
191 /**
192  * Sorts the handlers.
193  * 
194  */
195 ZmObjectManager.prototype.sortHandlers =
196 function() {
197 	this._allObjectHandlers = [];
198     var objectHandlers = this.getHandlers();
199     for (var type in objectHandlers) {
200 		// Object handlers grouped by Type
201 		objectHandlers[type].sort(ZmObjectManager.__byPriority);
202 
203 		// Copy each array to a single array of all Object Handlers
204 		for (var k = 0; k < objectHandlers[type].length; k++) {
205 			this._allObjectHandlers.push(objectHandlers[type][k]);
206 		}
207 	}
208 	this._allObjectHandlers.sort(ZmObjectManager.__byPriority);
209 };
210 
211 /**
212  * @private
213  */
214 ZmObjectManager.prototype._addAutoHandlers =
215 function() {
216 	var c = ZmObjectManager._autohandlers, i, obj, prio;
217 	for (i = 0; i < c.length; ++i) {
218 		obj = c[i];
219 		var	handler = obj;
220 		var type = obj.TYPE;
221 		if (!(obj.isZmObjectHandler)) {
222 			handler = new obj();
223 		}
224 		if (obj.useType) {
225 			type = obj.useType;
226 		}
227 		if (obj.usePrio) {
228 			prio = obj.usePrio;
229 		}
230 		this.addHandler(handler, type, prio);
231 	}
232 };
233 
234 /**
235  * Resets the objects.
236  * 
237  */
238 ZmObjectManager.prototype.reset =
239 function() {
240 	this._objects = {};
241 };
242 
243 /**
244  * Sets the view.
245  * 
246  * @param	{DwtComposite}		view		the view
247  */
248 ZmObjectManager.prototype.setView =
249 function(view) {
250 	if (view != null && appCtxt.getZimletMgr().isLoaded()) {
251 	    view.addListener(DwtEvent.ONMOUSEOVER, new AjxListener(this, this._mouseOverListener));
252 	    view.addListener(DwtEvent.ONMOUSEOUT, new AjxListener(this, this._mouseOutListener));
253 	    view.addListener(DwtEvent.ONMOUSEDOWN, new AjxListener(this, this._mouseDownListener));
254 	    view.addListener(DwtEvent.ONMOUSEUP, new AjxListener(this, this._mouseUpListener));
255 	    view.addListener(DwtEvent.ONMOUSEMOVE, new AjxListener(this, this._mouseMoveListener));
256 		view.addListener(DwtEvent.ONCONTEXTMENU, new AjxListener(this, this._rightClickListener));
257 		this._hoverOverListener = new AjxListener(this, this._handleHoverOver);
258 	    this._hoverOutListener = new AjxListener(this, this._handleHoverOut);
259 	}
260 	this._view = view;
261 };
262 
263 ZmObjectManager.prototype.getView = function() {
264 	return this._view;
265 };
266 
267 /**
268  * Gets the count of objects.
269  * 
270  * @return	{int}	the count
271  */
272 ZmObjectManager.prototype.objectsCount =
273 function() {
274 	return (appCtxt.zimletsPresent()) ? appCtxt.getZimletMgr().getContentZimlets().length : 0;
275 };
276 
277 /**
278  * Gets the image attachment handler.
279  * 
280  * @return	{ZmImageAttachmentObjectHandler}	the handler
281  */
282 ZmObjectManager.prototype.getImageAttachmentHandler =
283 function() {
284 	return this._imageAttachmentHandler;
285 };
286 
287 /**
288  * @private
289  */
290 ZmObjectManager.prototype._getAjxEmailAddress =
291 function(obj){
292     if(appCtxt.isChildWindow && obj.isAjxEmailAddress){ //Making sure child window knows its type AjxEmailAddress
293         obj = AjxEmailAddress.copy(obj);
294     }
295     return obj;
296 };
297 
298 /**
299  * Finds objects.
300  * 
301  * @param	{String}	content		the content
302  * @param	{Boolean}	htmlEncode	<code>true</code> to HTML encode the content
303  * @param	{constant}	type		the type
304  * @param	{Boolean}	isTextMsg	<code>true</code> if is text msg
305  * @param	{hash}		options		arbitrary options to pass to handler
306  *
307  * @return	{String}	the object
308  */
309 ZmObjectManager.prototype.findObjects =
310 function(content, htmlEncode, type, isTextMsg, options) {
311 	if  (!content) {return "";}
312 	var html = [];
313 	var idx = 0;
314 
315 	var maxIndex = content.length;
316 	var lastIndex = 0;
317 
318     var objectHandlers = this.getHandlers();
319     while (true) {
320 		var lowestResult = null;
321 		var lowestIndex = maxIndex;
322 		var lowestHandler = null;
323 
324 		// if given a type, just go thru the handler defined for that type.
325 		// otherwise, go thru every handler we have. Regardless, ask each handler
326 		// to find us a match >= to lastIndex. Handlers that didn't match last
327 		// time will simply return, handlers that matched last time that we didn't
328 		// use (because we found a closer match) will simply return that match again.
329 		//
330 		// when we are done, we take the handler with the lowest index.
331 		var i;
332 		var handlers;
333 		var chunk;
334 		var result = null;
335 		if (type) {
336 			//DBG.println(AjxDebug.DBG3, "findObjects type [" + type + "]");
337 			handlers = objectHandlers[type];
338 			if (handlers) {
339 				for (i = 0; i < handlers.length; i++) {
340 					//DBG.println(AjxDebug.DBG3, "findObjects by TYPE (" + handlers[i] + ")");
341 					result = handlers[i].findObject(content, lastIndex, this);
342 					// No match keep trying.
343 					if(!result) {continue;}
344 					// Got a match let's handle it.
345 					if (result.index >= lowestIndex) {break;}
346 					lowestResult = result;
347 					lowestIndex = result.index;
348 					lowestHandler = handlers[i];
349 				}
350 			}
351 			// If it's an email address just handle it and return the result.
352 			if (type == "email" || content instanceof AjxEmailAddress) {
353 				if (lowestHandler) {
354                     content = this._getAjxEmailAddress(content);
355 					this.generateSpan(lowestHandler, html, idx, content, lowestResult, options);
356 				} else {
357 					html[idx++] = AjxStringUtil.htmlEncode(content.toString());
358 				}
359 				return html.join("");
360 			}
361 		} else {
362 			for (var j = 0; j < this._allObjectHandlers.length; j++) {
363 				var handler = this._allObjectHandlers[j];
364 				//DBG.println(AjxDebug.DBG3, "findObjects trying (" + handler + ")");
365 				result = handler.findObject(content, lastIndex, this);
366 				if (result && result.index < lowestIndex) {
367 					lowestResult = result;
368 					lowestIndex = result.index;
369 					lowestHandler = handler;
370 				}
371 			}
372 		}
373 
374 		if (!lowestResult) {
375 			// all done
376 			// do last chunk
377 			chunk = content.substring(lastIndex, maxIndex);
378 			if (htmlEncode) {
379 				html[idx++] = AjxStringUtil.htmlEncode(chunk, !!isTextMsg);
380 			} else {
381 				html[idx++] = chunk;
382 			}
383 			break;
384 		}
385 
386 		//  add anything before the match
387 		if (lowestIndex > lastIndex) {
388 			chunk = content.substring(lastIndex, lowestIndex);
389 			if (htmlEncode) {
390 				html[idx++] = AjxStringUtil.htmlEncode(chunk, !!isTextMsg);
391 			} else {
392 				html[idx++] = chunk;
393 			}
394 		}
395 
396 		// add the match
397 		if(lowestHandler) {
398 			idx = this.generateSpan(lowestHandler, html, idx, lowestResult[0], lowestResult.context);
399 		} else {
400 			html[idx++] = lowestResult[0];
401 		}
402 
403 		// update the index
404 		lastIndex = lowestResult.index + (lowestResult.matchLength || lowestResult[0].length);
405 	}
406 
407 	return html.join("");
408 };
409 
410 
411 /**
412  * Added this customized method for the sake of ZmMailMsgView performance.
413  * 
414  * TODO: Integrate this method to findObjectsInNode()
415  * 
416  * @private
417  */
418 ZmObjectManager.prototype.processObjectsInNode = function(doc, node){
419 
420     var objectManager = this;
421 	doc = doc || node.ownerDocument;
422     var tmpdiv = doc.createElement("div");
423 
424     var recurse = function(node, handlers) {
425 		var tmp, i, val, next;
426 		switch (node.nodeType) {
427 		    case 1:	// ELEMENT_NODE
428 			node.normalize();
429 			tmp = node.tagName.toLowerCase();
430 
431 			if (next == null) {
432 				if (/^(img|a)$/.test(tmp)) {
433                     var href;
434                     try {
435                         // IE can throw an "Invalid Argument" error depending on value of href
436                         // e.g: http://0:0:0:0:0:0:0:1%0:7070/service/soap/ContactActionRequest:1331608015326:9c4f5868c5b0b4f2
437                         href = node.href;
438                     }
439                     catch(e) {
440                         //do nothing
441                     }
442 
443                     var isMailToLink = tmp === "a" && ZmMailMsgView._MAILTO_RE.test(href),
444                         isUrlLink = tmp === "a" && ZmMailMsgView._URL_RE.test(href);
445 
446                     if ((isMailToLink || isUrlLink) && node.target){
447 						// tricky.
448 						var txt = isMailToLink ? href :RegExp.$1 ;
449 						tmp = doc.createElement("div");
450 						tmp.innerHTML = objectManager.findObjects(AjxStringUtil.trim(txt));
451 						tmp = tmp.firstChild;
452 						if (tmp.nodeType == 3 /* Node.TEXT_NODE */) {
453 							// probably no objects were found.  A warning would be OK here
454 							// since the regexps guarantee that objects _should_ be found.
455 							return tmp.nextSibling;
456 						}
457 						// here, tmp is an object span, but it
458 						// contains the URL (href) instead of
459 						// the original link text.
460 						node.parentNode.insertBefore(tmp, node); // add it to DOM
461 						tmp.innerHTML = "";
462 						tmp.appendChild(node); // we have the original link now
463 						return tmp.nextSibling;	// move on
464 					}
465 					handlers = false;
466 				}
467 			} else {
468 				// consider processed
469 				node = next;
470 			}
471 
472 			// bug 28264: the only workaround possible seems to be
473 			// to remove textIndent styles that have a negative value:
474 			if (parseFloat(node.style.textIndent) < 0) {
475 				node.style.textIndent = "";
476 			}
477             for (i = node.firstChild; i; i = recurse(i, handlers)) {}
478 			return node.nextSibling;
479 
480 		    case 3:	// TEXT_NODE
481 		    case 4:	// CDATA_SECTION_NODE (just in case)
482 			// generate ObjectHandler-s
483 			if (handlers && /[^\s\xA0]/.test(node.data)) try {
484  				var a = null, b = null;
485 
486 				if (!AjxEnv.isIE) {
487 					// this block of code is supposed to free the object handlers from
488 					// dealing with whitespace.  However, IE sometimes crashes here, for
489 					// reasons that weren't possible to determine--hence we avoid this
490 					// step for IE.  (bug #5345)
491 					var results = /^[\s\xA0]+/.exec(node.data);
492 					if (results) {
493 						a = node;
494 						node = node.splitText(results[0].length);
495 					}
496 					results = /[\s\xA0]+$/.exec(node.data);
497 					if (results)
498 						b = node.splitText(node.data.length - results[0].length);
499 				}
500 
501 				tmp = tmpdiv;
502 				var code = objectManager.findObjects(node.data, true, null, false);
503 				var disembowel = false;
504 				if (AjxEnv.isIE) {
505 					// Bug #6481, #4498: innerHTML in IE massacrates whitespace
506 					//            unless it sees a <pre> in the code.
507 					tmp.innerHTML = [ "<pre>", code, "</pre>" ].join("");
508 					disembowel = true;
509 				} else {
510 					tmp.innerHTML = code;
511 				}
512 
513 				if (a)
514 					tmp.insertBefore(a, tmp.firstChild);
515 				if (b)
516 					tmp.appendChild(b);
517 
518 				a = node.parentNode;
519 				if (disembowel)
520 					tmp = tmp.firstChild;
521 				while (tmp.firstChild)
522 					a.insertBefore(tmp.firstChild, node);
523 				tmp = node.nextSibling;
524 				a.removeChild(node);
525 				return tmp;
526 			} catch(ex) {};
527 		}
528 		return node.nextSibling;
529 	};
530 
531     // Parse through the DOM directly and find objects.
532 	if (node && node.childNodes && node.childNodes.length) {
533 		for (var i = 0; i < node.childNodes.length; i++){
534 			recurse(node.childNodes[i], true);
535 		}
536 	}
537 };
538 
539 /**
540  * @private
541  */
542 ZmObjectManager.prototype.findObjectsInNode =
543 function(node, re_discard, re_allow, callbacks) {
544 	var objectManager = this, doc = node.ownerDocument, tmpdiv = doc.createElement("div");
545 
546 	if (!re_discard)
547 		re_discard = /^(script|link|object|iframe|applet)$/i;
548 
549 	// This inner function does the actual work.  BEWARE that it return-s
550 	// in various places, not only at the end.
551 	var recurse = function(node, handlers) {
552 		var tmp, i, val, next;
553 		switch (node.nodeType) {
554 		    case 1:	// ELEMENT_NODE
555 			node.normalize();
556 			tmp = node.tagName.toLowerCase();
557 			if (callbacks && callbacks.foreachElement) {
558 				next = callbacks.foreachElement(node, tmp, re_discard, re_allow);
559 			}
560 			if (next == null) {
561 				if (/^(img|a)$/.test(tmp)) {
562 					if (tmp == "a" && node.target
563 					    && (ZmMailMsgView._URL_RE.test(node.href)
564 						|| ZmMailMsgView._MAILTO_RE.test(node.href)))
565 					{
566 						// tricky.
567 						var txt = RegExp.$1;
568 						tmp = doc.createElement("div");
569 						tmp.innerHTML = objectManager.findObjects(AjxStringUtil.trim(RegExp.$1));
570 						tmp = tmp.firstChild;
571 						if (tmp.nodeType == 3 /* Node.TEXT_NODE */) {
572 							// probably no objects were found.  A warning would be OK here
573 							// since the regexps guarantee that objects _should_ be found.
574 							return tmp.nextSibling;
575 						}
576 						// here, tmp is an object span, but it
577 						// contains the URL (href) instead of
578 						// the original link text.
579 						node.parentNode.insertBefore(tmp, node); // add it to DOM
580 						tmp.innerHTML = "";
581 						tmp.appendChild(node); // we have the original link now
582 						return tmp.nextSibling;	// move on
583 					}
584 					handlers = false;
585 				} else if (re_discard.test(tmp) || (re_allow && !re_allow.test(tmp))) {
586 					tmp = node.nextSibling;
587 					node.parentNode.removeChild(node);
588 					return tmp;
589 				}
590 			} else {
591 				// consider processed
592 				node = next;
593 			}
594 
595 			if (AjxEnv.isIE) {
596 				// strips expression()-s, bwuahahaha!
597 				// granted, they get lost on the server-side anyway, but assuming some get through...
598 				// the line below exterminates them.
599 				node.style.cssText = node.style.cssText;
600 			}
601 
602 			for (i = node.firstChild; i; i = recurse(i, handlers)) {}
603 			return node.nextSibling;
604 
605 		    case 3:	// TEXT_NODE
606 		    case 4:	// CDATA_SECTION_NODE (just in case)
607 			// generate ObjectHandler-s
608 			if (handlers && /[^\s\xA0]/.test(node.data)) try {
609  				var a = null, b = null;
610 
611 				if (!AjxEnv.isIE) {
612 					// this block of code is supposed to free the object handlers from
613 					// dealing with whitespace.  However, IE sometimes crashes here, for
614 					// reasons that weren't possible to determine--hence we avoid this
615 					// step for IE.  (bug #5345)
616 					var results = /^[\s\xA0]+/.exec(node.data);
617 					if (results) {
618 						a = node;
619 						node = node.splitText(results[0].length);
620 					}
621 					results = /[\s\xA0]+$/.exec(node.data);
622 					if (results)
623 						b = node.splitText(node.data.length - results[0].length);
624 				}
625 
626 				tmp = tmpdiv;
627 				var code = objectManager.findObjects(node.data, true, null, false);
628 				var disembowel = false;
629 				if (AjxEnv.isIE) {
630 					// Bug #6481, #4498: innerHTML in IE massacrates whitespace
631 					//            unless it sees a <pre> in the code.
632 					tmp.innerHTML = [ "<pre>", code, "</pre>" ].join("");
633 					disembowel = true;
634 				} else {
635 					tmp.innerHTML = code;
636 				}
637 
638 				if (a)
639 					tmp.insertBefore(a, tmp.firstChild);
640 				if (b)
641 					tmp.appendChild(b);
642 
643 				a = node.parentNode;
644 				if (disembowel)
645 					tmp = tmp.firstChild;
646 				while (tmp.firstChild)
647 					a.insertBefore(tmp.firstChild, node);
648 				tmp = node.nextSibling;
649 				a.removeChild(node);
650 				return tmp;
651 			} catch(ex) {};
652 		}
653 		return node.nextSibling;
654 	};
655 	var df = doc.createDocumentFragment();
656 	while (node.firstChild) {
657 		df.appendChild(node.firstChild); // NODE now out of the displayable DOM
658 		recurse(df.lastChild, true, this);	 // parse tree and findObjects()
659 	}
660 	node.appendChild(df);	// put nodes back in the document
661 };
662 
663 /**
664  * Sets handler attribute.
665  * 
666  * @param	{String}	type		the type
667  * @param	{String}	name		the attribute name
668  * @param	{Object}	value		the value
669  */
670 ZmObjectManager.prototype.setHandlerAttr =
671 function(type, name, value) {
672     var handlers = this.getHandlers()[type];
673 	if (handlers) {
674 		for (var i = 0; i < handlers.length; i++) {
675 			handlers[i][name] = value;
676 		}
677 	}
678 };
679 
680 /**
681  * Generates the span.
682  * 
683  * @private
684  */
685 ZmObjectManager.prototype.generateSpan =
686 function(handler, html, idx, obj, context, options) {
687 	var id = this._objectIdPrefix + Dwt.getNextId();
688     if (handler && handler.name) {
689         id = id + "_" + handler.name;
690     }
691 	this._objects[id] = {object: obj, handler: handler, id: id, context: context };
692 	return handler.generateSpan(html, idx, obj, id, context, options);
693 };
694 
695 /**
696  * @private
697  */
698 ZmObjectManager.prototype._findObjectSpan =
699 function(e) {
700 	while (e && (!e.id || e.id.indexOf(this._objectIdPrefix) !== 0)) {
701 		e = e.parentNode;
702 	}
703 	return e;
704 };
705 
706 /**
707  * @private
708  */
709 ZmObjectManager.prototype._mouseOverListener =
710 function(ev) {
711 	var span = this._findObjectSpan(ev.target);
712 	if (!span) {return false;}
713 	var object = this._objects[span.id];
714 	if (!object) {return false;}
715 
716 	span.className = object.handler.getHoveredClassName(object.object, object.context, span.id);
717 	if (object.handler.hasToolTipText()) {
718 		var shell = DwtShell.getShell(window);
719 		var manager = shell.getHoverMgr();
720 		if ((!manager.isHovering() || manager.getHoverObject() != object) && !DwtMenu.menuShowing()) {
721 			manager.reset();
722 			manager.setHoverOverDelay(ZmObjectManager._TOOLTIP_DELAY);
723 			manager.setHoverObject(object);
724 			manager.setHoverOverData(object);
725 			manager.setHoverOverListener(this._hoverOverListener);
726 			manager.hoverOver(ev.docX, ev.docY);
727 			ev.hoverStarted = true;
728 		}
729 	}
730 
731 	ev._returnValue = true;
732 	ev._dontCallPreventDefault = true;
733 	return false;
734 };
735 
736 /**
737  * @private
738  */
739 ZmObjectManager.prototype._mouseOutListener =
740 function(ev) {
741 	var span = this._findObjectSpan(ev.target);
742 	var object = span ? this._objects[span.id] : null;
743 
744 	if (object) {
745 		span.className = object.handler.getClassName(object.object, object.context, span.id);
746 		var shell = DwtShell.getShell(window);
747 		var manager = shell.getHoverMgr();
748 		manager.setHoverOutDelay(150);
749 		manager.setHoverOutData(object);
750 		manager.setHoverOutListener(this._hoverOutListener);
751 		manager.hoverOut();
752 	}
753 
754 	return false;
755 };
756 
757 /**
758  * @private
759  */
760 ZmObjectManager.prototype._mouseMoveListener =
761 function(ev) {
762 	ev._returnValue = true;
763 	ev._dontCallPreventDefault = true;
764 	ev._stopPropagation = true;
765 	var span = this._findObjectSpan(ev.target);
766 	var object = span ? this._objects[span.id] : null;
767 
768 	if (object) {
769 		var shell = DwtShell.getShell(window);
770 		var manager = shell.getHoverMgr();
771 		if (!manager.isHovering()) {
772 			// NOTE: mouseOver already init'd hover settings
773 			manager.hoverOver(ev.docX, ev.docY);
774 		}
775 	}
776 
777 	return false;
778 };
779 
780 /**
781  * @private
782  */
783 ZmObjectManager.prototype._rightClickListener =
784 function(ev) {
785 	ev.button = DwtMouseEvent.RIGHT;
786 	return this._mouseDownListener(ev);
787 };
788 
789 /**
790  * @private
791  */
792 ZmObjectManager.prototype._mouseDownListener =
793 function(ev) {
794 
795 	// "authoritative" means a previous listener doesn't want propagation to get reset
796 	if (!ev._authoritative) {
797 		ev._dontCallPreventDefault = true;
798 		ev._returnValue = true;
799 		ev._stopPropagation = false;
800 	}
801 
802 	var span = this._findObjectSpan(ev.target);
803 	if (!span) {
804 		return true;
805 	}
806 	var object = this._objects[span.id];
807 	if (!object) {
808 		return true;
809 	}
810 
811 	ev._stopPropagation = true;
812 
813 	var shell = DwtShell.getShell(window);
814 	var manager = shell.getHoverMgr();
815 	manager.setHoverOutDelay(0);
816 	manager.setHoverOutData(object);
817 	manager.setHoverOutListener(this._hoverOutListener);
818 	manager.hoverOut();
819 
820 	span.className = object.handler.getActiveClassName(object.object, object.context, span.id);
821 	if (ev.button == DwtMouseEvent.RIGHT) {
822 		var menu = object.handler.getActionMenu(object.object, span, object.context, ev);
823 		if (menu) {
824 			menu.popup(0, ev.docX, ev.docY);
825 			// if we have an action menu, don't let the browser show its context menu too
826 			ev._dontCallPreventDefault = false;
827 			ev._returnValue = false;
828 			ev._stopPropagation = true;
829 			return true;
830 		}
831 	} else if (ev.button == DwtMouseEvent.LEFT) {
832 		if (this._selectCallback) {
833 			this._selectCallback.run();
834 		}
835 		object.handler.selected(object.object, span, ev, object.context);
836 		return true;
837 	}
838 	return false;
839 };
840 
841 /**
842  * @private
843  */
844 ZmObjectManager.prototype._mouseUpListener =
845 function(ev) {
846 	ev._returnValue = true;
847 	ev._dontCallPreventDefault = true;
848 	ev._stopPropagation = true;
849 	var span = this._findObjectSpan(ev.target);
850 	if (!span) {return false;}
851 	var object = this._objects[span.id];
852 	if (!object) {return false;}
853 
854 	span.className = object.handler.getHoveredClassName(object.object, object.context, span.id);
855 	return false;
856 };
857 
858 /**
859  * @private
860  */
861 ZmObjectManager.prototype._handleHoverOver =
862 function(event) {
863 	if (!(event && event.object)) { return; }
864 
865 	var span = this._findObjectSpan(event.target);
866 	var handler = event.object.handler;
867 	var object = event.object.object;
868 	var context = event.object.context;
869 	var id = event.object.id;
870 	var x = event.x;
871 	var y = event.y;
872 
873 	handler.hoverOver(object, context, x, y, span, id);
874 };
875 
876 /**
877  * @private
878  */
879 ZmObjectManager.prototype._handleHoverOut =
880 function(event) {
881 	if (!(event && event.object)) { return; }
882 
883 	var span = this._findObjectSpan(event.target);
884 	var handler = event.object.handler;
885 	var object = event.object.object;
886 	var context = event.object.context;
887 	var id = event.object.id;
888 
889 	handler.hoverOut(object, context, span, id);
890 };
891 
892 // Private static functions
893 
894 /**
895  * @private
896  */
897 ZmObjectManager.__byPriority =
898 function(a, b) {
899 	return (b._prio < a._prio) - (a._prio < b._prio);
900 };
901