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         if (locFlag & flags.orderDirty) {
287             this._dirtyFlag = this._dirtyFlag & flags.orderDirty ^ this._dirtyFlag;
288         }
289     };
290 
291     proto.visit = function(){
292         var self = this,
293             container = cc.container,
294             eventManager = cc.eventManager;
295         if(this._node._visible){
296             container.appendChild(this._div);
297             if(this._listener === null)
298                 this._listener = eventManager.addCustomListener(cc.game.EVENT_RESIZE, function () {
299                     self.resize();
300                 });
301         }else{
302             var hasChild = false;
303             if('contains' in container) {
304                 hasChild = container.contains(this._div);
305             }else {
306                 hasChild = container.compareDocumentPosition(this._div) % 16;
307             }
308             if(hasChild)
309                 container.removeChild(this._div);
310             var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners();
311             eventManager._removeListenerInVector(list, this._listener);
312             this._listener = null;
313         }
314         this.updateStatus();
315         this.resize(cc.view);
316     };
317 
318     proto.resize = function(view){
319         view = view || cc.view;
320         var node = this._node,
321             eventManager = cc.eventManager;
322         if(node._parent && node._visible)
323             this.updateMatrix(this._worldTransform, view._scaleX, view._scaleY);
324         else{
325             var list = eventManager._listenersMap[cc.game.EVENT_RESIZE].getFixedPriorityListeners();
326             eventManager._removeListenerInVector(list, this._listener);
327             this._listener = null;
328         }
329     };
330 
331     proto.updateMatrix = function(t, scaleX, scaleY){
332         var node = this._node;
333         if(polyfill.devicePixelRatio && scaleX !== 1 && scaleX !== 1){
334             var dpr = window.devicePixelRatio;
335             scaleX = scaleX / dpr;
336             scaleY = scaleY / dpr;
337         }
338         if(this._loaded === false) return;
339         var cw = node._contentSize.width,
340             ch = node._contentSize.height;
341         var a = t.a * scaleX,
342             b = t.b,
343             c = t.c,
344             d = t.d * scaleY,
345             tx = t.tx*scaleX - cw/2 + cw*node._scaleX/2*scaleX,
346             ty = t.ty*scaleY - ch/2 + ch*node._scaleY/2*scaleY;
347         var matrix = "matrix(" + a + "," + b + "," + c + "," + d + "," + tx + "," + -ty + ")";
348         this._div.style["transform"] = matrix;
349         this._div.style["-webkit-transform"] = matrix;
350     };
351 
352     proto.initStyle = function(){
353         if(!this._div)  return;
354         var div = this._div;
355         div.style.position = "absolute";
356         div.style.bottom = "0px";
357         div.style.left = "0px";
358     };
359 
360     proto.updateURL = function(url){
361         var iframe = this._iframe;
362         iframe.src = url;
363         var self = this;
364         var cb = function(){
365             self._loaded = true;
366             iframe.removeEventListener("load", cb);
367         };
368         iframe.addEventListener("load", cb);
369     };
370 
371     proto.changeSize = function(w, h){
372         var div = this._div;
373         if(div){
374             div.style["width"] = w+"px";
375             div.style["height"] = h+"px";
376         }
377     };
378 
379     proto.removeDom = function(){
380         var div = this._div;
381         if(div){
382             var hasChild = false;
383             if('contains' in cc.container) {
384                 hasChild = cc.container.contains(div);
385             }else {
386                 hasChild = cc.container.compareDocumentPosition(div) % 16;
387             }
388             if(hasChild)
389                 cc.container.removeChild(div);
390         }
391     };
392 
393 })(ccui.WebView._polyfill);