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                     win.history.back.call(win);
105             }
106         }catch(err){
107             cc.log(err);
108         }
109     },
110 
111     /**
112      * go forward
113      */
114     goForward: function(){
115         try{
116             if(ccui.WebView._polyfill.closeHistory)
117                 return cc.log("The current browser does not support the GoForward");
118             var iframe = this._renderCmd._iframe;
119             if(iframe){
120                 var win = iframe.contentWindow;
121                 if(win && win.location)
122                     win.history.forward.call(win);
123             }
124         }catch(err){
125             cc.log(err);
126         }
127     },
128 
129     /**
130      * In the webview execution within a period of js string
131      * @param {String} str
132      */
133     evaluateJS: function(str){
134         var iframe = this._renderCmd._iframe;
135         if(iframe){
136             var win = iframe.contentWindow;
137             try{
138                 win.eval(str);
139                 this._dispatchEvent(ccui.WebView.EventType.JS_EVALUATED);
140             }catch(err){
141                 console.error(err);
142             }
143         }
144     },
145 
146     /**
147      * Limited scale
148      */
149     setScalesPageToFit: function(){
150         cc.log("Web does not support zoom");
151     },
152 
153     /**
154      * The binding event
155      * @param {ccui.WebView.EventType} event
156      * @param {Function} callback
157      */
158     setEventListener: function(event, callback){
159         this._EventList[event] = callback;
160     },
161 
162     /**
163      * Delete events
164      * @param {ccui.WebView.EventType} event
165      */
166     removeEventListener: function(event){
167         this._EventList[event] = null;
168     },
169 
170     _dispatchEvent: function(event) {
171         var callback = this._EventList[event];
172         if (callback)
173             callback.call(this, this, this._renderCmd._iframe.src);
174     },
175 
176     _createRenderCmd: function(){
177         return new ccui.WebView.RenderCmd(this);
178     },
179 
180     /**
181      * Set the contentSize
182      * @param {Number} w
183      * @param {Number} h
184      */
185     setContentSize: function(w, h){
186         ccui.Widget.prototype.setContentSize.call(this, w, h);
187         if(h === undefined){
188             h = w.height;
189             w = w.width;
190         }
191         this._renderCmd.changeSize(w, h);
192     },
193 
194     /**
195      * remove node
196      */
197     cleanup: function(){
198         this._renderCmd.removeDom();
199         this.stopAllActions();
200         this.unscheduleAllCallbacks();
201     }
202 });
203 
204 /**
205  * The WebView support list of events
206  * @type {{LOADING: string, LOADED: string, ERROR: string}}
207  */
208 ccui.WebView.EventType = {
209     LOADING: "loading",
210     LOADED: "load",
211     ERROR: "error",
212     JS_EVALUATED: "js"
213 };
214 
215 (function(){
216 
217     var polyfill = ccui.WebView._polyfill = {
218         devicePixelRatio: false,
219         enableDiv: false
220     };
221 
222     if(cc.sys.os === cc.sys.OS_IOS)
223         polyfill.enableDiv = true;
224 
225     if(cc.sys.isMobile){
226         if(cc.sys.browserType === cc.sys.BROWSER_TYPE_FIREFOX){
227             polyfill.enableBG = true;
228         }
229     }else{
230         if(cc.sys.browserType === cc.sys.BROWSER_TYPE_IE){
231             polyfill.closeHistory = true;
232         }
233     }
234 
235 
236 })();
237 
238 (function(polyfill){
239 
240     ccui.WebView.RenderCmd = function(node){
241         cc.Node.CanvasRenderCmd.call(this, node);
242 
243         this._div = null;
244         this._iframe = null;
245 
246         if(polyfill.enableDiv){
247             this._div = document.createElement("div");
248             this._div.style["-webkit-overflow"] = "auto";
249             this._div.style["-webkit-overflow-scrolling"] = "touch";
250             this._iframe = document.createElement("iframe");
251             this._div.appendChild(this._iframe);
252         }else{
253             this._div = this._iframe = document.createElement("iframe");
254         }
255 
256         if(polyfill.enableBG)
257             this._div.style["background"] = "#FFF";
258 
259         this._iframe.addEventListener("load", function(){
260             node._dispatchEvent(ccui.WebView.EventType.LOADED);
261         });
262         this._iframe.addEventListener("error", function(){
263             node._dispatchEvent(ccui.WebView.EventType.ERROR);
264         });
265         this._div.style["background"] = "#FFF";
266         this._div.style.height = "200px";
267         this._div.style.width = "300px";
268         this._div.style.overflow = "scroll";
269         this._listener = null;
270         this.initStyle();
271     };
272 
273     var proto = ccui.WebView.RenderCmd.prototype = Object.create(cc.Node.CanvasRenderCmd.prototype);
274     proto.constructor = ccui.WebView.RenderCmd;
275 
276     proto.updateStatus = function(){
277         polyfill.devicePixelRatio = cc.view.isRetinaEnabled();
278         var flags = cc.Node._dirtyFlags, locFlag = this._dirtyFlag;
279         if(locFlag & flags.transformDirty){
280             //update the transform
281             this.transform(this.getParentRenderCmd(), true);
282             this.updateMatrix(this._worldTransform, cc.view._scaleX, cc.view._scaleY);
283             this._dirtyFlag = this._dirtyFlag & cc.Node._dirtyFlags.transformDirty ^ this._dirtyFlag;
284         }
285     };
286 
287     proto.visit = function(){
288         var self = this,
289             container = cc.container,
290             eventManager = cc.eventManager;
291         if(this._node._visible){
292             container.appendChild(this._div);
293             if(this._listener === null)
294                 this._listener = eventManager.addCustomListener(cc.game.EVENT_RESIZE, function () {
295                     self.resize();
296                 });
297         }else{
298             var hasChild = false;
299             if('contains' in container) {
300                 hasChild = container.contains(this._div);
301             }else {
302                 hasChild = container.compareDocumentPosition(this._div) % 16;
303             }
304             if(hasChild)
305                 container.removeChild(this._div);
306             var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners();
307             eventManager._removeListenerInVector(list, this._listener);
308             this._listener = null;
309         }
310         this.updateStatus();
311         this.resize(cc.view);
312     };
313 
314     proto.resize = function(view){
315         view = view || cc.view;
316         var node = this._node,
317             eventManager = cc.eventManager;
318         if(node._parent && node._visible)
319             this.updateMatrix(this._worldTransform, view._scaleX, view._scaleY);
320         else{
321             var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners();
322             eventManager._removeListenerInVector(list, this._listener);
323             this._listener = null;
324         }
325     };
326 
327     proto.updateMatrix = function(t, scaleX, scaleY){
328         var node = this._node;
329         if(polyfill.devicePixelRatio && scaleX !== 1 && scaleX !== 1){
330             var dpr = window.devicePixelRatio;
331             scaleX = scaleX / dpr;
332             scaleY = scaleY / dpr;
333         }
334         if(this._loaded === false) return;
335         var cw = node._contentSize.width,
336             ch = node._contentSize.height;
337         var a = t.a * scaleX,
338             b = t.b,
339             c = t.c,
340             d = t.d * scaleY,
341             tx = t.tx*scaleX - cw/2 + cw*node._scaleX/2*scaleX,
342             ty = t.ty*scaleY - ch/2 + ch*node._scaleY/2*scaleY;
343         var matrix = "matrix(" + a + "," + b + "," + c + "," + d + "," + tx + "," + -ty + ")";
344         this._div.style["transform"] = matrix;
345         this._div.style["-webkit-transform"] = matrix;
346     };
347 
348     proto.initStyle = function(){
349         if(!this._div)  return;
350         var div = this._div;
351         div.style.position = "absolute";
352         div.style.bottom = "0px";
353         div.style.left = "0px";
354     };
355 
356     proto.updateURL = function(url){
357         var iframe = this._iframe;
358         iframe.src = url;
359         var self = this;
360         var cb = function(){
361             self._loaded = true;
362             iframe.removeEventListener("load", cb);
363         };
364         iframe.addEventListener("load", cb);
365     };
366 
367     proto.changeSize = function(w, h){
368         var div = this._div;
369         if(div){
370             div.style["width"] = w+"px";
371             div.style["height"] = h+"px";
372         }
373     };
374 
375     proto.removeDom = function(){
376         var div = this._div;
377         if(div){
378             var hasChild = false;
379             if('contains' in cc.container) {
380                 hasChild = cc.container.contains(div);
381             }else {
382                 hasChild = cc.container.compareDocumentPosition(div) % 16;
383             }
384             if(hasChild)
385                 cc.container.removeChild(div);
386         }
387     };
388 
389 })(ccui.WebView._polyfill);