package org.geogebra.web.html5; import org.geogebra.common.kernel.Kernel; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.Window.Location; public class Browser { private static boolean webWorkerSupported = false; public static native boolean isFirefox() /*-{ // copying checking code from the checkWorkerSupport method // however, this is not necessarily the best method to decide if (navigator.userAgent.toLowerCase().indexOf("firefox") != -1) { return true; } return false; }-*/; public static native boolean isIE() /*-{ // copying checking code from isFirefox() and checked from web // however, this is not necessarily the best method to decide if (navigator.userAgent.toLowerCase().indexOf("msie") > -1) { return true; } return false; }-*/; public static native boolean isIE9() /*-{ // copying checking code from isFirefox() and checked from web // however, this is not necessarily the best method to decide if (navigator.userAgent.toLowerCase().indexOf("msie") > -1 && navigator.userAgent.toLowerCase().indexOf("9.0") > -1) { return true; } return false; }-*/; /** * Better solution, copied from CopyPasteCutW originally TODO: add * toLowerCase() call to it! * * @return */ public static native boolean isInternetExplorer() /*-{ // check if app is running in IE5 or greater // clipboardData object is available from IE5 and onwards var userAgent = $wnd.navigator.userAgent; if ((userAgent.indexOf('MSIE ') > -1) || (userAgent.indexOf('Trident/') > -1)) { return true; } return false; }-*/; public native static boolean externalCAS() /*-{ return typeof $wnd.evalGeoGebraCASExternal == 'function'; }-*/; public static boolean checkWorkerSupport(String workerpath) { if ("tablet".equals(GWT.getModuleName()) || "phone".equals(GWT.getModuleName())) { return false; } return nativeCheckWorkerSupport(workerpath); } public static native boolean nativeCheckWorkerSupport(String workerpath) /*-{ // Worker support in Firefox is incompatible at the moment for zip.js, // see http://gildas-lormeau.github.com/zip.js/ for details: if (navigator.userAgent.toLowerCase().indexOf("firefox") != -1) { @org.geogebra.common.util.debug.Log::debug(Ljava/lang/String;)("INIT: worker not supported in Firefox, fallback for simple js"); return false; } if (navigator.userAgent.toLowerCase().indexOf("safari") != -1 && navigator.userAgent.toLowerCase().indexOf("chrome") == -1) { @org.geogebra.common.util.debug.Log::debug(Ljava/lang/String;)("INIT: worker not supported in Safari, fallback for simple js"); return false; } try { var worker = new $wnd.Worker(workerpath+"js/workercheck.js"); } catch (e) { @org.geogebra.common.util.debug.Log::debug(Ljava/lang/String;)("INIT: worker not supported (no worker at " + workerpath + "), fallback for simple js"); return false; } @org.geogebra.common.util.debug.Log::debug(Ljava/lang/String;)("INIT: workers are supported"); worker.terminate(); return true; }-*/; public static native boolean checkIfFallbackSetExplicitlyInArrayBufferJs() /*-{ if ($wnd.zip.useWebWorkers === false) { //we set this explicitly in arraybuffer.js @org.geogebra.common.util.debug.Log::debug(Ljava/lang/String;)("INIT: workers maybe supported, but fallback set explicitly in arraybuffer.js"); return true; ; } return false; }-*/; /** * @return whether we are running under iOS */ public static native String getMobileOperatingSystem()/*-{ var userAgent = navigator.userAgent || navigator.vendor || window.opera; //iOS detection from: http://sackoverflow.com/a/9039885/177710 if (/Mac|iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { return "iOS"; } return "unknown"; }-*/; private static boolean float64supported = true; /** * Checks whether browser supports float64. Must be called before a polyfill * kicks in. */ public static void checkFloat64() { float64supported = doCheckFloat64(); } public static boolean isFloat64supported() { return float64supported; } private static native boolean doCheckFloat64()/*-{ var floatSupport = 'undefined' !== typeof Float64Array; return 'undefined' !== typeof Float64Array; }-*/; public static native boolean supportsPointerEvents(boolean usePen)/*-{ //$wnd.console.log("PEN SUPPORT" + usePen + "," + (!!$wnd.PointerEvent)); if (usePen && $wnd.PointerEvent) { return true; } return $wnd.navigator.msPointerEnabled ? true : false; }-*/; private static native boolean isHTTP() /*-{ return $wnd.location.protocol != 'file:'; }-*/; public static boolean supportsSessionStorage() { return !Browser.isIE() || Browser.isHTTP(); } public static String normalizeURL(String thumb) { if (thumb.startsWith("data:")) { return thumb; } String url; if (thumb.startsWith("http://") || thumb.startsWith("file://")) { url = thumb.substring("http://".length()); } else if (thumb.startsWith("https://")) { url = thumb.substring("https://".length()); } else if (thumb.startsWith("//")) { url = thumb.substring("//".length()); } else { url = thumb; } return "https://" + url; } private static Boolean webglSupported = null; public static boolean supportsWebGL() { if (webglSupported == null) { webglSupported = supportsWebGLNative(); } return webglSupported.booleanValue(); } /* * http://stackoverflow.com/questions/11871077/proper-way-to-detect-webgl- * support */ public static native boolean supportsWebGLNative()/*-{ try { var canvas = $wnd.document.createElement('canvas'); var ret = !!$wnd.WebGLRenderingContext && (canvas.getContext('webgl') || canvas .getContext('experimental-webgl')); return !!ret; } catch (e) { return false; } }-*/; public static native boolean supportsWebGLTriangleFan()/*-{ return $wnd.WebGLRenderingContext && (!!$wnd.WebGLRenderingContext.TRIANGLE_FAN); }-*/; /** * @return whether we are running this from another website (local install * of app bundle) */ public static boolean runningLocal() { return Location.getProtocol().startsWith("http") && Location.getHost() != null && !Location.getHost().contains("geogebra.org"); } public native static String navigatorLanguage() /*-{ return $wnd.navigator.language || "en"; }-*/; public static native boolean isAndroidVersionLessThan(double d) /*-{ var navString = navigator.userAgent.toLowerCase(); if (navString.indexOf("android") < 0) { return false; } if (parseFloat(navString.substring(navString.indexOf("android") + 8)) < d) { return true; } return false; }-*/; public static void scale(Element parent, double externalScale, int x, int y) { if (externalScale < 0) { return; } String transform = "scale(" + externalScale + "," + externalScale + ")"; if (Kernel.isEqual(externalScale, 1)) { transform = "none"; } String pos = x + "% " + y + "%"; parent.getStyle().setProperty("webkitTransform", transform); parent.getStyle().setProperty("mozTransform", transform); parent.getStyle().setProperty("msTransform", transform); parent.getStyle().setProperty("transform", transform); parent.getStyle().setProperty("msTransformOrigin", pos); parent.getStyle().setProperty("mozTransformOrigin", pos); parent.getStyle().setProperty("webkitTransformOrigin", pos); parent.getStyle().setProperty("transformOrigin", pos); } public static native boolean supportsWebcam() /*-{ if ($wnd.navigator.getUserMedia || $wnd.navigator.webkitGetUserMedia || $wnd.navigator.mozGetUserMedia || $wnd.navigator.msGetUserMedia) { return true; } return false; }-*/; /** * @return true if Javascript CAS is supported. */ public static boolean supportsJsCas() { return Browser.isFloat64supported() && !Browser.isAndroidVersionLessThan(4.0); } public static native boolean isMobile()/*-{ return !!(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i .test($wnd.navigator.userAgent)); }-*/; public native static boolean isIE10plus() /*-{ return !!$wnd.MSBlobBuilder; }-*/; public static native double getPixelRatio() /*-{ var testCanvas = document.createElement("canvas"), testCtx = testCanvas .getContext("2d"); devicePixelRatio = $wnd.devicePixelRatio || 1; backingStorePixelRatio = testCtx.webkitBackingStorePixelRatio || testCtx.mozBackingStorePixelRatio || testCtx.msBackingStorePixelRatio || testCtx.oBackingStorePixelRatio || testCtx.backingStorePixelRatio || 1; return devicePixelRatio / backingStorePixelRatio; }-*/; public static native void exportImage(String url, String title) /*-{ //idea from http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript/16245768#16245768 if ($wnd.navigator.msSaveBlob) { var sliceSize = 512; var byteCharacters = atob(url .substring("data:image/png;base64,".length)); var byteArrays = []; for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { var slice = byteCharacters.slice(offset, offset + sliceSize); var byteNumbers = new Array(slice.length); for (var i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } var byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } var blob = new Blob(byteArrays, { type : "image/png" }); //works for internet explorer $wnd.navigator.msSaveBlob(blob, title); } else { //works for firefox var a = $doc.createElement("a"); $doc.body.appendChild(a); a.style = "display: none"; a.href = url; a.download = title; $wnd.setTimeout(function() { a.click() }, 1000); } }-*/; public static void changeUrl(String string) { if (Location.getHost() != null && Location.getHost().contains("geogebra.org")) { nativeChangeUrl(string); } } private static native void nativeChangeUrl(String name) /*-{ if (name && $wnd.history && $wnd.history.pushState) { try { $wnd.parent.history.pushState({}, "GeoGebra", "/" + name); } catch (e) { $wnd.history.pushState({}, "GeoGebra", "/" + name); } } }-*/; /** * Opens GeoGebraTube material in a new window * * @param url * GeoGebraTube url */ public native static void openTubeWindow(String url)/*-{ $wnd.open(url); }-*/; /** * Returns a string based on base 64 encoded value * * @param base64 * a base64 encoded string * * @return decoded string */ public static native String decodeBase64(String base64)/*-{ return atob(base64); }-*/; /** * * Returns a base64 encoding of the specified (binary) string * * @param text * A binary string (obtained for instance by the FileReader API) * @return a base64 encoded string. */ public static native String encodeBase64(String text)/*-{ return btoa(text); }-*/; public static native void removeDefaultContextMenu(Element element) /*-{ function eventOnElement(e) { x1 = @org.geogebra.web.html5.main.AppW::getAbsoluteLeft(Lcom/google/gwt/dom/client/Element;)(element); x2 = @org.geogebra.web.html5.main.AppW::getAbsoluteRight(Lcom/google/gwt/dom/client/Element;)(element); y1 = @org.geogebra.web.html5.main.AppW::getAbsoluteTop(Lcom/google/gwt/dom/client/Element;)(element); y2 = @org.geogebra.web.html5.main.AppW::getAbsoluteBottom(Lcom/google/gwt/dom/client/Element;)(element); if ((e.pageX < x1) || (e.pageX > x2) || (e.pageY < y1) || (e.pageY > y2)) { return false; } return true; } if ($doc.addEventListener) { element.addEventListener("MSHoldVisual", function(e) { e.preventDefault(); }, false); $doc.addEventListener('contextmenu', function(e) { if (eventOnElement(e)) e.preventDefault(); }, false); } else { $doc.attachEvent('oncontextmenu', function() { if (eventOnElement(e)) $wnd.event.returnValue = false; }); } }-*/; public static native void removeDefaultContextMenu() /*-{ if ($doc.addEventListener) { $doc.addEventListener('contextmenu', function(e) { e.preventDefault(); }, false); $doc.addEventListener("MSHoldVisual", function(e) { e.preventDefault(); }, false); } else { $doc.attachEvent('oncontextmenu', function() { $wnd.event.returnValue = false; }); } }-*/; public static native boolean isChromeWebApp() /*-{ if ($doc.isChromeWebapp()) { return true; } return false; }-*/; public static native boolean isXWALK() /*-{ return !!$wnd.ggbExamXWalkExtension; }-*/; public native static boolean isAndroid()/*-{ var userAgent = navigator.userAgent; if (userAgent) { return navigator.userAgent.indexOf("Android") != -1; } return false; }-*/; public native static boolean isIPad()/*-{ var userAgent = navigator.userAgent; if (userAgent) { return navigator.userAgent.indexOf("iPad") != -1; } return false; }-*/; public static boolean isTabletBrowser() { return (isAndroid() || isIPad()); } public static void setWebWorkerSupported(boolean b) { webWorkerSupported = b; } public static boolean webWorkerSupported() { return webWorkerSupported; } public static native int getScreenWidth() /*-{ return $wnd.screen.width; }-*/; public static native int getScreenHeight() /*-{ return $wnd.screen.height; }-*/; public static native boolean isEdge() /*-{ return $wnd.navigator.userAgent.indexOf("Edge") > -1; }-*/; }