1 /**************************************************************************** 2 Copyright (c) 2013-2014 Chukong Technologies Inc. 3 4 http://www.cocos2d-x.org 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in 14 all copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 THE SOFTWARE. 23 ****************************************************************************/ 24 25 /** 26 * @class 27 * @extends ccui.Widget 28 * @brief A View that displays web pages. 29 * 30 * @note WebView displays web pages based on DOM element 31 * WebView will be rendered above all other graphical elements. 32 * 33 * @property {String} path - The url to be shown in the web view 34 */ 35 ccui.WebView = ccui.Widget.extend(/** @lends ccui.WebView# */{ 36 37 ctor: function(path){ 38 ccui.Widget.prototype.ctor.call(this); 39 this._EventList = {}; 40 if(path) 41 this.loadURL(path); 42 }, 43 44 setJavascriptInterfaceScheme: function(scheme){}, 45 loadData: function(data, MIMEType, encoding, baseURL){}, 46 loadHTMLString: function(string, baseURL){}, 47 48 49 /** 50 * Load an URL 51 * @param {String} url 52 */ 53 loadURL: function(url){ 54 this._renderCmd.updateURL(url); 55 this._dispatchEvent(ccui.WebView.EventType.LOADING); 56 }, 57 58 /** 59 * Stop loading 60 */ 61 stopLoading: function(){ 62 cc.log("Web does not support loading"); 63 }, 64 65 /** 66 * Reload the WebView 67 */ 68 reload: function(){ 69 var iframe = this._renderCmd._iframe; 70 if(iframe){ 71 var win = iframe.contentWindow; 72 if(win && win.location) 73 win.location.reload(); 74 } 75 }, 76 77 /** 78 * Determine whether to go back 79 */ 80 canGoBack: function(){ 81 cc.log("Web does not support query history"); 82 return true; 83 }, 84 85 /** 86 * Determine whether to go forward 87 */ 88 canGoForward: function(){ 89 cc.log("Web does not support query history"); 90 return true; 91 }, 92 93 /** 94 * go back 95 */ 96 goBack: function(){ 97 try{ 98 if(ccui.WebView._polyfill.closeHistory) 99 return cc.log("The current browser does not support the GoBack"); 100 var iframe = this._renderCmd._iframe; 101 if(iframe){ 102 var win = iframe.contentWindow; 103 if(win && win.location) 104 try { 105 win.history.back.call(win); 106 } catch (error) { 107 win.history.back(); 108 } 109 } 110 }catch(err){ 111 cc.log(err); 112 } 113 }, 114 115 /** 116 * go forward 117 */ 118 goForward: function(){ 119 try{ 120 if(ccui.WebView._polyfill.closeHistory) 121 return cc.log("The current browser does not support the GoForward"); 122 var iframe = this._renderCmd._iframe; 123 if(iframe){ 124 var win = iframe.contentWindow; 125 if(win && win.location) 126 try { 127 win.history.forward.call(win); 128 } catch (error) { 129 win.history.forward(); 130 } 131 } 132 }catch(err){ 133 cc.log(err); 134 } 135 }, 136 137 /** 138 * In the webview execution within a period of js string 139 * @param {String} str 140 */ 141 evaluateJS: function(str){ 142 var iframe = this._renderCmd._iframe; 143 if(iframe){ 144 var win = iframe.contentWindow; 145 try{ 146 win.eval(str); 147 this._dispatchEvent(ccui.WebView.EventType.JS_EVALUATED); 148 }catch(err){ 149 console.error(err); 150 } 151 } 152 }, 153 154 /** 155 * Limited scale 156 */ 157 setScalesPageToFit: function(){ 158 cc.log("Web does not support zoom"); 159 }, 160 161 /** 162 * The binding event 163 * @param {ccui.WebView.EventType} event 164 * @param {Function} callback 165 */ 166 setEventListener: function(event, callback){ 167 this._EventList[event] = callback; 168 }, 169 170 /** 171 * Delete events 172 * @param {ccui.WebView.EventType} event 173 */ 174 removeEventListener: function(event){ 175 this._EventList[event] = null; 176 }, 177 178 _dispatchEvent: function(event) { 179 var callback = this._EventList[event]; 180 if (callback) 181 callback.call(this, this, this._renderCmd._iframe.src); 182 }, 183 184 _createRenderCmd: function(){ 185 return new ccui.WebView.RenderCmd(this); 186 }, 187 188 /** 189 * Set the contentSize 190 * @param {Number} w 191 * @param {Number} h 192 */ 193 setContentSize: function(w, h){ 194 ccui.Widget.prototype.setContentSize.call(this, w, h); 195 if(h === undefined){ 196 h = w.height; 197 w = w.width; 198 } 199 this._renderCmd.changeSize(w, h); 200 }, 201 202 /** 203 * remove node 204 */ 205 cleanup: function(){ 206 this._renderCmd.removeDom(); 207 this.stopAllActions(); 208 this.unscheduleAllCallbacks(); 209 } 210 }); 211 212 /** 213 * The WebView support list of events 214 * @type {{LOADING: string, LOADED: string, ERROR: string}} 215 */ 216 ccui.WebView.EventType = { 217 LOADING: "loading", 218 LOADED: "load", 219 ERROR: "error", 220 JS_EVALUATED: "js" 221 }; 222 223 (function(){ 224 225 var polyfill = ccui.WebView._polyfill = { 226 devicePixelRatio: false, 227 enableDiv: false 228 }; 229 230 if(cc.sys.os === cc.sys.OS_IOS) 231 polyfill.enableDiv = true; 232 233 if(cc.sys.isMobile){ 234 if(cc.sys.browserType === cc.sys.BROWSER_TYPE_FIREFOX){ 235 polyfill.enableBG = true; 236 } 237 }else{ 238 if(cc.sys.browserType === cc.sys.BROWSER_TYPE_IE){ 239 polyfill.closeHistory = true; 240 } 241 } 242 243 244 })(); 245 246 (function(polyfill){ 247 248 ccui.WebView.RenderCmd = function(node){ 249 cc.Node.CanvasRenderCmd.call(this, node); 250 251 this._div = null; 252 this._iframe = null; 253 254 if(polyfill.enableDiv){ 255 this._div = document.createElement("div"); 256 this._div.style["-webkit-overflow"] = "auto"; 257 this._div.style["-webkit-overflow-scrolling"] = "touch"; 258 this._iframe = document.createElement("iframe"); 259 this._iframe.style["width"] = "100%"; 260 this._iframe.style["height"] = "100%"; 261 this._div.appendChild(this._iframe); 262 }else{ 263 this._div = this._iframe = document.createElement("iframe"); 264 } 265 266 if(polyfill.enableBG) 267 this._div.style["background"] = "#FFF"; 268 269 this._iframe.addEventListener("load", function(){ 270 node._dispatchEvent(ccui.WebView.EventType.LOADED); 271 }); 272 this._iframe.addEventListener("error", function(){ 273 node._dispatchEvent(ccui.WebView.EventType.ERROR); 274 }); 275 this._div.style["background"] = "#FFF"; 276 this._div.style.height = "200px"; 277 this._div.style.width = "300px"; 278 this._div.style.overflow = "scroll"; 279 this._listener = null; 280 this.initStyle(); 281 }; 282 283 var proto = ccui.WebView.RenderCmd.prototype = Object.create(cc.Node.CanvasRenderCmd.prototype); 284 proto.constructor = ccui.WebView.RenderCmd; 285 286 proto.updateStatus = function(){ 287 polyfill.devicePixelRatio = cc.view.isRetinaEnabled(); 288 var flags = cc.Node._dirtyFlags, locFlag = this._dirtyFlag; 289 if(locFlag & flags.transformDirty){ 290 //update the transform 291 this.transform(this.getParentRenderCmd(), true); 292 this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY); 293 this._dirtyFlag = this._dirtyFlag & cc.Node._dirtyFlags.transformDirty ^ this._dirtyFlag; 294 } 295 296 if (locFlag & flags.orderDirty) { 297 this._dirtyFlag = this._dirtyFlag & flags.orderDirty ^ this._dirtyFlag; 298 } 299 }; 300 301 proto.visit = function(){ 302 var self = this, 303 container = cc.container, 304 eventManager = cc.eventManager; 305 if(this._node._visible){ 306 container.appendChild(this._div); 307 if(this._listener === null) 308 this._listener = eventManager.addCustomListener(cc.game.EVENT_RESIZE, function () { 309 self.resize(); 310 }); 311 }else{ 312 var hasChild = false; 313 if('contains' in container) { 314 hasChild = container.contains(this._div); 315 }else { 316 hasChild = container.compareDocumentPosition(this._div) % 16; 317 } 318 if(hasChild) 319 container.removeChild(this._div); 320 var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners(); 321 eventManager._removeListenerInVector(list, this._listener); 322 this._listener = null; 323 } 324 this.updateStatus(); 325 this.resize(cc.view); 326 }; 327 328 proto.resize = function(view){ 329 view = view || cc.view; 330 var node = this._node, 331 eventManager = cc.eventManager; 332 if(node._parent && node._visible) 333 this.updateMatrix(this._worldTransform, view._scaleX, view._scaleY); 334 else{ 335 var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners(); 336 eventManager._removeListenerInVector(list, this._listener); 337 this._listener = null; 338 } 339 }; 340 341 proto.updateMatrix = function(t, scaleX, scaleY){ 342 var node = this._node; 343 if(polyfill.devicePixelRatio && scaleX !== 1 && scaleX !== 1){ 344 var dpr = window.devicePixelRatio; 345 scaleX = scaleX / dpr; 346 scaleY = scaleY / dpr; 347 } 348 if(this._loaded === false) return; 349 var cw = node._contentSize.width, 350 ch = node._contentSize.height; 351 var a = t.a * scaleX, 352 b = t.b, 353 c = t.c, 354 d = t.d * scaleY, 355 tx = t.tx*scaleX - cw/2 + cw*node._scaleX/2*scaleX, 356 ty = t.ty*scaleY - ch/2 + ch*node._scaleY/2*scaleY; 357 var matrix = "matrix(" + a + "," + b + "," + c + "," + d + "," + tx + "," + -ty + ")"; 358 this._div.style["transform"] = matrix; 359 this._div.style["-webkit-transform"] = matrix; 360 }; 361 362 proto.initStyle = function(){ 363 if(!this._div) return; 364 var div = this._div; 365 div.style.position = "absolute"; 366 div.style.bottom = "0px"; 367 div.style.left = "0px"; 368 }; 369 370 proto.updateURL = function(url){ 371 var iframe = this._iframe; 372 iframe.src = url; 373 var self = this; 374 var cb = function(){ 375 self._loaded = true; 376 iframe.removeEventListener("load", cb); 377 }; 378 iframe.addEventListener("load", cb); 379 }; 380 381 proto.changeSize = function(w, h){ 382 var div = this._div; 383 if(div){ 384 div.style["width"] = w+"px"; 385 div.style["height"] = h+"px"; 386 } 387 }; 388 389 proto.removeDom = function(){ 390 var div = this._div; 391 if(div){ 392 var hasChild = false; 393 if('contains' in cc.container) { 394 hasChild = cc.container.contains(div); 395 }else { 396 hasChild = cc.container.compareDocumentPosition(div) % 16; 397 } 398 if(hasChild) 399 cc.container.removeChild(div); 400 } 401 }; 402 403 })(ccui.WebView._polyfill);