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