1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 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  * Creates an iframe.
 26  * @class
 27  * This class represents a simple event proxy. Creates an IFRAME, inserts the given html into it and
 28  * forwards any events to the parent widget, translating mouse coordinates in
 29  * between.
 30  *
 31  * @param {hash}	params		a hash of parameters:
 32  *
 33  * @param   {DwtComposite}		parent	                the parent
 34  * @param   {string}	        html 	                the HTML code to be inserted in the IFRAME.  There will be
 35  *   slight modifications to it (i.e. the margins and paddings of the HTML
 36  *   element will be set to 0, also any margins for BODY).
 37  * @param   {boolean}		    noscroll 	            if <code>true</code>, do not show the scroll bars
 38  * @param   {string}	        posStyle	            the position style (see {@link DwtControl})
 39  * @param   {AjxCallback}	    processHtmlCallback		the callback that will be called
 40  *   immediately after the HTML code was inserted. A reference to the document object will be passed
 41  * @param   {boolean}	        useKbMgmt	            if <code>true</code>, participate in keyboard management
 42  * @param   {string}            title                   title for the IFRAME
 43  * 
 44  * @author Mihai Bazon
 45  * 
 46  * @extends		DwtControl
 47  */
 48 DwtIframe = function(params) {
 49 	params.posStyle = params.posStyle || DwtControl.STATIC_STYLE;
 50 	params.className = params.className || "DwtIframe";
 51 	DwtControl.call(this, params);
 52 	this._styles = params.styles;
 53 	this._noscroll = params.noscroll;
 54 	this._iframeID = params.id ? DwtId.getIframeId(params.id) : Dwt.getNextId();
 55 	this._onLoadHandler = params.onload;	
 56 	this._processHtmlCallback = params.processHtmlCallback;
 57 	this._hidden = params.hidden;
 58 	this._title = params.title;
 59 
 60 	if (!this._createFrame(params.html)) {
 61 		this.initFailed = true;
 62 		return;	// this object is still returned
 63 	}
 64 	
 65 	if (params.useKbMgmt) {
 66 		var iframe = this.getIframe();
 67 		var idoc = Dwt.getIframeDoc(iframe);
 68 		var doc = AjxEnv.isIE ? idoc : iframe.contentWindow;
 69 		Dwt.setHandler(doc, DwtEvent.ONKEYDOWN, DwtKeyboardMgr.__keyDownHdlr);
 70 		Dwt.setHandler(doc, DwtEvent.ONKEYUP, DwtKeyboardMgr.__keyUpHdlr);
 71 		Dwt.setHandler(doc, DwtEvent.ONKEYPRESS, DwtKeyboardMgr.__keyPressHdlr);
 72 	}
 73 };
 74 
 75 DwtIframe.prototype = new DwtControl;
 76 DwtIframe.prototype.constructor = DwtIframe;
 77 
 78 DwtIframe.prototype.isDwtIframe = true;
 79 DwtIframe.prototype.toString = function() { return "DwtIframe"; };
 80 
 81 /**
 82  * Gets the iframe.
 83  * 
 84  * @return	{Element}	the iframe
 85  */
 86 DwtIframe.prototype.getIframe = function() {
 87 	return document.getElementById(this._iframeID);
 88 };
 89 
 90 DwtIframe.prototype.getInputElement = function() {
 91 	return this.getIframe();
 92 };
 93 
 94 /**
 95  * Gets the iframe window document.
 96  * 
 97  * @return	{Document}		the document
 98  */
 99 DwtIframe.prototype.getDocument = function() {
100 	return this.getIframe().contentWindow.document;
101 };
102 
103 /**
104  * Notifies the parent widget's listeners. The event is not directly propagated to the parent's raw event handler.
105  *
106  * @private
107  */
108 DwtIframe.prototype._rawEventHandler = function(ev) {
109 
110 	var iframe = this.getIframe();
111 	var win = iframe.contentWindow;
112 	if (AjxEnv.isIE) {
113 		ev = win.event;
114 	}
115 
116 	var dw;
117 	// This probably sucks.
118 	if (/mouse|context|click|select/i.test(ev.type)) {
119 		dw = new DwtMouseEvent(true);
120 	}
121 	else {
122 		dw = new DwtUiEvent(true);
123 	}
124 	dw.setFromDhtmlEvent(ev);
125 
126 	// Notify since the manager doesn't know about events in the iframe's document
127 	if (ev.type == "mousedown" || ev.type == "mousewheel") {
128 		DwtOutsideMouseEventMgr.forwardEvent(ev);
129 	}
130 
131 	// HACK! who would have know.. :-(
132 	// perhaps we need a proper mapping
133 	var type = dw.type.toLowerCase();
134 	if (!/^on/.test(type)) {
135 		type = "on" + type;
136 	}
137 	// translate event coordinates
138 	var pos = this.getLocation();
139 
140 	// What I can tell for sure is that we don't want the code below for IE
141 	// and we want it for Gecko, but I can't be sure of other browsers..
142 	// Let's assume they follow Gecko.  Seems mostly a trial and error
143 	// process :(
144 	if (!AjxEnv.isIE) {
145 		var doc = win.document;
146 		var sl = doc.documentElement.scrollLeft || ( doc.body ? doc.body.scrollLeft : 0); 
147 		var st = doc.documentElement.scrollTop || ( doc.body ? doc.body.scrollTop : 0 );
148 		pos.x -= sl;
149 		pos.y -= st;
150 	}
151 
152 	dw.docX += pos.x;
153 	dw.docY += pos.y;
154 	dw.elementX += pos.x;
155 	dw.elementY += pos.y;
156 
157 //   	window.status = dw.type + " doc(" + dw.docX + ", " + dw.docY + ") " +
158 //   		" element(" + dw.elementX + ", " + dw.elementY + ") " +
159 //  		" stopPropagation: " + dw._stopPropagation + ", " +
160 //  		" returnValue: " + dw._returnValue;
161 
162 	var capture = DwtMouseEventCapture.getCaptureObj();
163 	capture = capture && dw.button != DwtMouseEvent.RIGHT; // ignore capture if it's right-click
164 	if (AjxEnv.isIE || !capture) {
165         // TODO: handle all events generically by calling handlers instead of listeners
166         if (type === DwtEvent.ONFOCUS || type === DwtEvent.ONBLUR) {
167             DwtControl.__HANDLER[type].call(null, dw, type, this.parent);
168         }
169         else {
170             // notify listeners
171             DwtEventManager.notifyListeners(type, dw);
172             this.parent.notifyListeners(type, dw);
173         }
174 	} else {
175 		// Satisfy object that holds the mouse capture.
176 
177 		// the following is DOM2, not supported by IE
178 		var fake = document.createEvent("MouseEvents");
179 		fake.initMouseEvent(ev.type,
180 				    true, // can bubble
181 				    true, // cancellable
182 				    document.defaultView, // the view
183 				    0, // event detail ("click count")
184 				    ev.screenX, // screen X
185 				    ev.screenY, // screen Y
186 				    dw.docX, // clientX, but translated to page
187 				    dw.docY, // clientY, translated
188 				    ev.ctrlKey, // key status...
189 				    ev.altKey,
190 				    ev.shiftKey,
191 				    ev.metaKey,
192 				    ev.button,
193 				    ev.relatedTarget);
194 		document.body.dispatchEvent(fake);
195 		// capture[DwtIframe._captureEvents[dw.type]](fake);
196 	}
197 
198 	dw.setToDhtmlEvent(ev);
199 	return dw._returnValue;
200 };
201 
202 // map event names to the handler name in a DwtMouseEventCapture object
203 // DwtIframe._captureEvents = { mousedown : "_mouseDownHdlr",
204 // 			     mousemove : "_mouseMoveHdlr",
205 // 			     mouseout  : "_mouseOutHdlr",
206 // 			     mouseover : "_mouseOverHdlr",
207 // 			     mouseup   : "_mouseUpHdlr" };
208 
209 DwtIframe._forwardEvents = [ DwtEvent.ONCHANGE,
210 			     DwtEvent.ONCLICK,
211 			     DwtEvent.ONCONTEXTMENU,
212 			     DwtEvent.ONDBLCLICK,
213 			     DwtEvent.ONFOCUS,
214 			     DwtEvent.ONKEYDOWN,
215 			     DwtEvent.ONKEYPRESS,
216 			     DwtEvent.ONKEYUP,
217 			     DwtEvent.ONMOUSEDOWN,
218 			     DwtEvent.ONMOUSEENTER,
219 			     DwtEvent.ONMOUSELEAVE,
220 			     DwtEvent.ONMOUSEMOVE,
221 			     DwtEvent.ONMOUSEOUT,
222 			     DwtEvent.ONMOUSEOVER,
223 			     DwtEvent.ONMOUSEUP,
224 			     DwtEvent.ONSELECTSTART ];
225 
226 DwtIframe.prototype._createFrame = function(html) {
227 	var myId = this.getHTMLElId();
228 
229 	// this is an inner function so that we can access the object (self).
230 	// it shouldn't create a memory leak since it doesn't directly "see"
231 	// the iframe variable (it's protected below)
232 	function rawHandlerProxy(ev) {
233 		var myElement = document.getElementById(myId);
234 		var self = DwtControl.findControl(myElement);		
235 		return self._rawEventHandler(ev);
236 	};
237 
238 	// closure: protect the reference to the iframe node here.
239 	return (function() {
240 		var iframe, tmp = [], i = 0, idoc;
241 		var myElement = document.getElementById(myId);
242 		var self = DwtControl.findControl(myElement);
243 
244 		if (!self) {
245 			return false;
246 		}
247 		
248 		tmp[i++] = "<iframe";
249 		if (self._noscroll) {
250 			tmp[i++] = " scrolling='no'";
251 		}
252 		if (self._hidden) {
253 			tmp[i++] = " style='visibility:hidden'";
254 		}
255 		if (self._title) {
256 			tmp[i++] = " title='" + AjxStringUtil.encodeQuotes(AjxStringUtil.htmlEncode(self._title)) + "'";
257 		}
258 		tmp[i++] = " frameborder='0' width='100%' id='";
259 		tmp[i++] = self._iframeID;
260 		tmp[i++] = "' name='"+ self._iframeID + "'";
261 		if(self._onLoadHandler){
262 			tmp[i++] = " onload='" + self._onLoadHandler + "'";
263 		}
264 		tmp[i++] = " src='javascript:\"\";' ></iframe>";
265 		self.setContent(tmp.join(''));
266 
267 		// Bug 7523: @import url() lines will make Gecko report
268 		// document.body is undefined until "onload" (unacceptable) so
269 		// we drop these lines now.
270 		html = html.replace(/(<style[^>]*>)[\s\t\u00A0]*((.|\n)*?)[\s\t\u00A0]*<\x2fstyle>/mgi,
271 				    function(s, p1, p2) {
272 					    return p1 + p2.replace(/@import.*?(;|[\s\t\u00A0]*$)/gi, "") + "</style>";
273 				    });
274         html = AjxStringUtil.checkForCleanHtml(html, ZmMailMsgView.TRUSTED_TAGS, ZmMailMsgView.UNTRUSTED_ATTRS).html;
275 		iframe = self.getIframe();
276 		idoc = Dwt.getIframeDoc(iframe);
277 		idoc.open();
278 		if (self._styles) {
279 			idoc.write([ "<style type='text/css'>", self._styles, "</style>" ].join(""));
280 		}
281 		idoc.write(html);
282 		idoc.close();
283 		// if we're not giving a break, we can safely do any postprocessing
284 		// here.  I.e. if we want to drop backgroundImage-s, it's safe to do it
285 		// here because the browser won't have a chance to load them.
286 		if (self._processHtmlCallback) {
287 			self._processHtmlCallback.run(idoc);
288 		}
289 
290 		// if we have margins, the translated coordinates won't be OK.
291 		// it's best to remove them.  THE way to have some spacing is
292 		// to set padding on the body element.
293 		tmp = idoc.documentElement.style;
294 		tmp.margin = tmp.padding = "0";
295 		if (idoc.body) {
296 			idoc.body.style.margin = "0";
297 		}
298 
299 		// set language in IFRAME <html> element
300 		var htmlEl = idoc.firstChild && idoc.firstChild.tagName && idoc.firstChild.tagName.toLowerCase() === 'html' ? idoc.firstChild : null,
301 			langAttr = htmlEl && htmlEl.getAttribute('lang');
302 		if (htmlEl && !langAttr) {
303 			htmlEl.setAttribute('lang', window.appLang);
304 		}
305 
306 		// assign event handlers
307 		tmp = DwtIframe._forwardEvents;
308 		if (!AjxEnv.isIE) {
309 			idoc = iframe.contentWindow;
310 		}
311 		for (i = tmp.length; --i >= 0;) {
312 			idoc[tmp[i]] = rawHandlerProxy;
313 		}
314 
315 		// catch browser context menus
316 		// idoc[DwtEvent.ONCONTEXTMENU] = DwtShell._preventDefaultPrt;
317 		
318 		return true;
319 	})();
320 };
321 
322 DwtIframe.prototype._resetEventHandlers = function() {
323 	var self = this;
324 
325 	// this is an inner function so that we can access the object (self).
326 	// it shouldn't create a memory leak since it doesn't directly "see"
327 	// the iframe variable (it's protected below)
328 	function rawHandlerProxy(ev) { return self._rawEventHandler(ev); };
329 
330 	// closure: protect the reference to the iframe node here.
331 	(function() {
332 		var iframe, tmp = [], i = 0, idoc;
333 		iframe = self.getIframe();
334 		idoc = Dwt.getIframeDoc(iframe);
335 
336 		// assign event handlers
337 		tmp = DwtIframe._forwardEvents;
338 		if (!AjxEnv.isIE)
339 			idoc = iframe.contentWindow;
340 			
341 			
342 		for (i = tmp.length; --i >= 0;){
343 			idoc[tmp[i]] = rawHandlerProxy;
344 		}
345 
346 		// catch browser context menus
347 		// idoc[DwtEvent.ONCONTEXTMENU] = DwtShell._preventDefaultPrt;
348 	})();
349 };
350 
351 DwtIframe.prototype.setSrc =
352 function(src){
353 
354     src = src || 'javascript:\"\";'
355     var iframe = this.getIframe();
356     iframe.src = src;
357 };
358 
359 DwtIframe.prototype.setIframeContent =
360 function(html){    
361     var iDoc = this.getDocument();
362 	if (iDoc.body) {
363         iDoc.body.innerHTML = html;
364 	}
365 };
366