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