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 var RenderCmd = null; 249 if (cc._renderType === cc.game.RENDER_TYPE_WEBGL) { 250 RenderCmd = cc.Node.WebGLRenderCmd; 251 } else { 252 RenderCmd = cc.Node.CanvasRenderCmd; 253 } 254 255 ccui.WebView.RenderCmd = function(node){ 256 RenderCmd.call(this, node); 257 258 this._div = null; 259 this._iframe = null; 260 261 if(polyfill.enableDiv){ 262 this._div = document.createElement("div"); 263 this._div.style["-webkit-overflow"] = "auto"; 264 this._div.style["-webkit-overflow-scrolling"] = "touch"; 265 this._iframe = document.createElement("iframe"); 266 this._iframe.style["width"] = "100%"; 267 this._iframe.style["height"] = "100%"; 268 this._div.appendChild(this._iframe); 269 }else{ 270 this._div = this._iframe = document.createElement("iframe"); 271 } 272 273 if(polyfill.enableBG) 274 this._div.style["background"] = "#FFF"; 275 276 this._iframe.addEventListener("load", function(){ 277 node._dispatchEvent(ccui.WebView.EventType.LOADED); 278 }); 279 this._iframe.addEventListener("error", function(){ 280 node._dispatchEvent(ccui.WebView.EventType.ERROR); 281 }); 282 this._div.style["background"] = "#FFF"; 283 this._div.style.height = "200px"; 284 this._div.style.width = "300px"; 285 this._div.style.overflow = "scroll"; 286 this._listener = null; 287 this.initStyle(); 288 }; 289 290 var proto = ccui.WebView.RenderCmd.prototype = Object.create(RenderCmd.prototype); 291 proto.constructor = ccui.WebView.RenderCmd; 292 293 proto.transform = function (parentCmd, recursive) { 294 this.originTransform(parentCmd, recursive); 295 this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY); 296 }; 297 298 proto.updateStatus = function(){ 299 polyfill.devicePixelRatio = cc.view.isRetinaEnabled(); 300 var flags = cc.Node._dirtyFlags, locFlag = this._dirtyFlag; 301 if(locFlag & flags.transformDirty){ 302 //update the transform 303 this.transform(this.getParentRenderCmd(), true); 304 this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY); 305 this._dirtyFlag = this._dirtyFlag & cc.Node._dirtyFlags.transformDirty ^ this._dirtyFlag; 306 } 307 308 if (locFlag & flags.orderDirty) { 309 this._dirtyFlag = this._dirtyFlag & flags.orderDirty ^ this._dirtyFlag; 310 } 311 }; 312 313 proto.visit = function(){ 314 var self = this, 315 container = cc.container, 316 eventManager = cc.eventManager; 317 if(this._node._visible){ 318 container.appendChild(this._div); 319 if(this._listener === null) 320 this._listener = eventManager.addCustomListener(cc.game.EVENT_RESIZE, function () { 321 self.resize(); 322 }); 323 }else{ 324 var hasChild = false; 325 if('contains' in container) { 326 hasChild = container.contains(this._div); 327 }else { 328 hasChild = container.compareDocumentPosition(this._div) % 16; 329 } 330 if(hasChild) 331 container.removeChild(this._div); 332 var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners(); 333 eventManager._removeListenerInVector(list, this._listener); 334 this._listener = null; 335 } 336 this.updateStatus(); 337 this.resize(cc.view); 338 }; 339 340 proto.resize = function(view){ 341 view = view || cc.view; 342 var node = this._node, 343 eventManager = cc.eventManager; 344 if(node._parent && node._visible) 345 this.updateMatrix(this._worldTransform, view._scaleX, view._scaleY); 346 else{ 347 var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners(); 348 eventManager._removeListenerInVector(list, this._listener); 349 this._listener = null; 350 } 351 }; 352 353 proto.updateMatrix = function(t, scaleX, scaleY){ 354 var node = this._node; 355 if (polyfill.devicePixelRatio && scaleX !== 1 && scaleX !== 1) { 356 var dpr = cc.view.getDevicePixelRatio(); 357 scaleX = scaleX / dpr; 358 scaleY = scaleY / dpr; 359 } 360 if(this._loaded === false) return; 361 var cw = node._contentSize.width, 362 ch = node._contentSize.height; 363 var a = t.a * scaleX, 364 b = t.b, 365 c = t.c, 366 d = t.d * scaleY, 367 tx = t.tx*scaleX - cw/2 + cw*node._scaleX/2*scaleX, 368 ty = t.ty*scaleY - ch/2 + ch*node._scaleY/2*scaleY; 369 var matrix = "matrix(" + a + "," + b + "," + c + "," + d + "," + tx + "," + -ty + ")"; 370 this._div.style["transform"] = matrix; 371 this._div.style["-webkit-transform"] = matrix; 372 }; 373 374 proto.initStyle = function(){ 375 if(!this._div) return; 376 var div = this._div; 377 div.style.position = "absolute"; 378 div.style.bottom = "0px"; 379 div.style.left = "0px"; 380 }; 381 382 proto.updateURL = function(url){ 383 var iframe = this._iframe; 384 iframe.src = url; 385 var self = this; 386 var cb = function(){ 387 self._loaded = true; 388 iframe.removeEventListener("load", cb); 389 }; 390 iframe.addEventListener("load", cb); 391 }; 392 393 proto.changeSize = function(w, h){ 394 var div = this._div; 395 if(div){ 396 div.style["width"] = w+"px"; 397 div.style["height"] = h+"px"; 398 } 399 }; 400 401 proto.removeDom = function(){ 402 var div = this._div; 403 if(div){ 404 var hasChild = false; 405 if('contains' in cc.container) { 406 hasChild = cc.container.contains(div); 407 }else { 408 hasChild = cc.container.compareDocumentPosition(div) % 16; 409 } 410 if(hasChild) 411 cc.container.removeChild(div); 412 } 413 }; 414 415 })(ccui.WebView._polyfill);