JavaScript 65.5%
Python 17.8%
CSS 13%
HTML 3.7%
1/*2 * noVNC: HTML5 VNC client3 * Copyright (C) 2019 The noVNC authors4 * Licensed under MPL 2.0 (see LICENSE.txt)5 *6 * See README.md for usage and integration instructions.7 *8 * Browser feature support detection9 */1011import * as Log from './logging.js';12import Base64 from '../base64.js';1314// Touch detection15export let isTouchDevice = ('ontouchstart' in document.documentElement) ||16 // required for Chrome debugger17 (document.ontouchstart !== undefined) ||18 // required for MS Surface19 (navigator.maxTouchPoints > 0) ||20 (navigator.msMaxTouchPoints > 0);21window.addEventListener('touchstart', function onFirstTouch() {22 isTouchDevice = true;23 window.removeEventListener('touchstart', onFirstTouch, false);24}, false);252627// The goal is to find a certain physical width, the devicePixelRatio28// brings us a bit closer but is not optimal.29export let dragThreshold = 10 * (window.devicePixelRatio || 1);3031let _supportsCursorURIs = false;3233try {34 const target = document.createElement('canvas');35 target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';3637 if (target.style.cursor.indexOf("url") === 0) {38 Log.Info("Data URI scheme cursor supported");39 _supportsCursorURIs = true;40 } else {41 Log.Warn("Data URI scheme cursor not supported");42 }43} catch (exc) {44 Log.Error("Data URI scheme cursor test exception: " + exc);45}4647export const supportsCursorURIs = _supportsCursorURIs;4849let _hasScrollbarGutter = true;50try {51 // Create invisible container52 const container = document.createElement('div');53 container.style.visibility = 'hidden';54 container.style.overflow = 'scroll'; // forcing scrollbars55 document.body.appendChild(container);5657 // Create a div and place it in the container58 const child = document.createElement('div');59 container.appendChild(child);6061 // Calculate the difference between the container's full width62 // and the child's width - the difference is the scrollbars63 const scrollbarWidth = (container.offsetWidth - child.offsetWidth);6465 // Clean up66 container.parentNode.removeChild(container);6768 _hasScrollbarGutter = scrollbarWidth != 0;69} catch (exc) {70 Log.Error("Scrollbar test exception: " + exc);71}72export const hasScrollbarGutter = _hasScrollbarGutter;7374export let supportsWebCodecsH264Decode = false;7576async function _checkWebCodecsH264DecodeSupport() {77 if (!('VideoDecoder' in window)) {78 return false;79 }8081 // We'll need to make do with some placeholders here82 const config = {83 codec: 'avc1.42401f',84 codedWidth: 1920,85 codedHeight: 1080,86 optimizeForLatency: true,87 };8889 let support = await VideoDecoder.isConfigSupported(config);90 if (!support.supported) {91 return false;92 }9394 // Firefox incorrectly reports supports for H.264 under some95 // circumstances, so we need to actually test a real frame96 // https://bugzilla.mozilla.org/show_bug.cgi?id=19323929798 const data = new Uint8Array(Base64.decode(99 'AAAAAWdCwBTZnpuAgICgAAADACAAAAZB4oVNAAAAAWjJYyyAAAABBgX//4Hc' +100 'Rem95tlIt5Ys2CDZI+7veDI2NCAtIGNvcmUgMTY0IHIzMTA4IDMxZTE5Zjkg' +101 'LSBILjI2NC9NUEVHLTQgQVZDIGNvZGVjIC0gQ29weWxlZnQgMjAwMy0yMDIz' +102 'IC0gaHR0cDovL3d3dy52aWRlb2xhbi5vcmcveDI2NC5odG1sIC0gb3B0aW9u' +103 'czogY2FiYWM9MCByZWY9NSBkZWJsb2NrPTE6MDowIGFuYWx5c2U9MHgxOjB4' +104 'MTExIG1lPWhleCBzdWJtZT04IHBzeT0xIHBzeV9yZD0xLjAwOjAuMDAgbWl4' +105 'ZWRfcmVmPTEgbWVfcmFuZ2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0yIDh4' +106 'OGRjdD0wIGNxbT0wIGRlYWR6b25lPTIxLDExIGZhc3RfcHNraXA9MSBjaHJv' +107 'bWFfcXBfb2Zmc2V0PS0yIHRocmVhZHM9MSBsb29rYWhlYWRfdGhyZWFkcz0x' +108 'IHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9' +109 'MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVz' +110 'PTAgd2VpZ2h0cD0wIGtleWludD1pbmZpbml0ZSBrZXlpbnRfbWluPTI1IHNj' +111 'ZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NTAgcmM9' +112 'YWJyIG1idHJlZT0xIGJpdHJhdGU9NDAwIHJhdGV0b2w9MS4wIHFjb21wPTAu' +113 'NjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFx' +114 'PTE6MS4wMACAAAABZYiEBrxmKAAPVccAAS044AA5DRJMnkycJk4TPw=='));115116 let gotframe = false;117 let error = null;118119 let decoder = new VideoDecoder({120 output: (frame) => { gotframe = true; },121 error: (e) => { error = e; },122 });123 let chunk = new EncodedVideoChunk({124 timestamp: 0,125 type: 'key',126 data: data,127 });128129 decoder.configure(config);130 decoder.decode(chunk);131 try {132 await decoder.flush();133 } catch (e) {134 // Firefox incorrectly throws an exception here135 // https://bugzilla.mozilla.org/show_bug.cgi?id=1932566136 error = e;137 }138139 // Firefox fails to deliver the error on Windows, so we need to140 // check if we got a frame instead141 // https://bugzilla.mozilla.org/show_bug.cgi?id=1932579142 if (!gotframe) {143 return false;144 }145146 if (error !== null) {147 return false;148 }149150 return true;151}152supportsWebCodecsH264Decode = await _checkWebCodecsH264DecodeSupport();153154/*155 * The functions for detection of platforms and browsers below are exported156 * but the use of these should be minimized as much as possible.157 *158 * It's better to use feature detection than platform detection.159 */160161/* OS */162163export function isMac() {164 return !!(/mac/i).exec(navigator.platform);165}166167export function isWindows() {168 return !!(/win/i).exec(navigator.platform);169}170171export function isIOS() {172 return (!!(/ipad/i).exec(navigator.platform) ||173 !!(/iphone/i).exec(navigator.platform) ||174 !!(/ipod/i).exec(navigator.platform));175}176177export function isAndroid() {178 /* Android sets navigator.platform to Linux :/ */179 return !!navigator.userAgent.match('Android ');180}181182export function isChromeOS() {183 /* ChromeOS sets navigator.platform to Linux :/ */184 return !!navigator.userAgent.match(' CrOS ');185}186187/* Browser */188189export function isSafari() {190 return !!navigator.userAgent.match('Safari/...') &&191 !navigator.userAgent.match('Chrome/...') &&192 !navigator.userAgent.match('Chromium/...') &&193 !navigator.userAgent.match('Epiphany/...');194}195196export function isFirefox() {197 return !!navigator.userAgent.match('Firefox/...') &&198 !navigator.userAgent.match('Seamonkey/...');199}200201export function isChrome() {202 return !!navigator.userAgent.match('Chrome/...') &&203 !navigator.userAgent.match('Chromium/...') &&204 !navigator.userAgent.match('Edg/...') &&205 !navigator.userAgent.match('OPR/...');206}207208export function isChromium() {209 return !!navigator.userAgent.match('Chromium/...');210}211212export function isOpera() {213 return !!navigator.userAgent.match('OPR/...');214}215216export function isEdge() {217 return !!navigator.userAgent.match('Edg/...');218}219220/* Engine */221222export function isGecko() {223 return !!navigator.userAgent.match('Gecko/...');224}225226export function isWebKit() {227 return !!navigator.userAgent.match('AppleWebKit/...') &&228 !navigator.userAgent.match('Chrome/...');229}230231export function isBlink() {232 return !!navigator.userAgent.match('Chrome/...');233}234