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);