const PLATFORM_IS_MAC_OS = /mac/i.test(window.navigator.platform);
const PLATFORM_IS_WINDOWS = /win/i.test(window.navigator.platform);
const PLATFORM_IS_LINUX = /linux/i.test(window.navigator.platform);
//BROWSER BUG: used to work around Firefox bugs.
const BROWSER_ENGINE_IS_GECKO = /Gecko\/\S+/.test(window.navigator.userAgent);
/*
----------------------------
Why is Safari not supported?
----------------------------
Tested against
- Safari 18.0.1 AppleWebKit/605.1.15 on November 2, 2024.
- Safari 17.5 AppleWebKit/605.1.15 on June 11, 2024.
- Safari 17.1 AppleWebKit/605.1.15 on November 16, 2023.
(As of Safari 17, it's no longer possible to turn
NSUrlSession WebSocket OFF.)
- Safari 16.6 AppleWebKit/605.1.15 on August 17, 2023.
- Safari 16.5.1 AppleWebKit/605.1.15 on July 2, 2023.
Same issues.
----------------------------
Tested against Safari 16.4.1 AppleWebKit/605.1.15 on April 12, 2023.
Works normally^1 except that, in practice, Safari's WebSocket client
implementation is unusable.
- With Develop|Experimental Features|NSUrlSession WebSocket ON (the default):
* When opening a remote document containing images, Safari closes the
WebSocket with code 1006.
See https://bugs.webkit.org/show_bug.cgi?id=228296
* A self-signed certificate works after being sanctioned by the user.
- With Develop|Experimental Features|NSUrlSession WebSocket OFF:
* Opening a remote document containing images works.
* Quitting XMLEditor makes Safari closes the WebSocket in a unclean way
(Jetty always reports: XXEWebSocket.onError [client=null]:
java.nio.channels.ClosedChannelException)
* A self-signed certificate causes the wss:// connection to fail
even if this certificate is sanctioned by the user.
---
^1 Very minor issues:
* Resizing XMLEditor by dragging is bottom/right corner works but there is
no resize icon there.
* A multi-line outline around focused text is rendered using a solid style
and not a dotted style.
*/
function browserEngineIsSupported() {
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/
// Browser_detection_using_the_user_agent
//
// Also note that Apple forces every web browser on iOS to use Apple's
// WebKit engine.
// Remove/add trailing "000" below to allow/deny using Safari.
const appleWebKitMinVersion = 605000;
let supported = false;
if (window.navigator) {
const userAgent = window.navigator.userAgent;
if (userAgent) {
let match = /Chrome\/([0-9.]+)/.exec(userAgent);
if (match !== null) {
// Something like "87.0.4280.141" parses as 87.
let version = parseInt(match[1]);
if (!isNaN(version) && version >= 107) {
supported = true;
}
} else {
let match = /AppleWebKit\/([0-9.]+)/.exec(userAgent);
if (match !== null) {
// Something like "605.1.15" parses as 605.
let version = parseInt(match[1]);
if (!isNaN(version) && version >= appleWebKitMinVersion) {
supported = true;
}
} else if (/Gecko\/\S+/.test(userAgent)) {
let match = /rv:([0-9.]+)/.exec(userAgent);
if (match !== null) {
let version = parseInt(match[1]);
if (!isNaN(version) && version >= 102) {
supported = true;
}
}
}
}
}
if (supported && ("ontouchstart" in window)) {
// In practice, XMLEditor is unusable on a touch-capable device
// like a tablet or a smartphone.
supported = false;
}
}
return supported;
}
/**
* Unlike <code>console.assert</code>, this function throws an error when
* the assertion fails.
*/
function assertOrError(assertion, message=null) {
if (!assertion) {
if (!message) {
message = "ASSERTION FAILED!";
}
let error = new Error(message);
console.error(`assertOrError
---
${error.stack}
---`);
throw error;
}
}
/**
* Unlike <code>String.split</code> which, when the string is empty,
* returns an array containing one empty string, this function returns
* an empty array.
*
* @param {string} s - string to be first <em>trimmed</em> then split.
* May be <code>null</code>.
* @param {separ} separ - where each split should occur;
* can be a string or a regular expression
* @return {array} an array of strings, split at each point where
* the separator occurs.
*/
function splitTrimString(s, separ) {
return (s === null || (s = s.trim()).length === 0)? [] : s.split(separ);
}