@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
798 lines (753 loc) • 26.9 kB
JavaScript
/**
* @license Hyphenopoly_Loader 3.4.0 - client side hyphenation
* ©2019 Mathias Nater, Zürich (mathiasnater at gmail dot com)
* https://github.com/mnater/Hyphenopoly
*
* Released under the MIT license
* http://mnater.github.io/Hyphenopoly/LICENSE
* Modifications SAP SE or an SAP affiliate company and OpenUI5 contributors. All rights reserved.
*/
/* globals Hyphenopoly:readonly */
/**
* Wrap all code in an iife to keep a scope. Important objects are parameters
* of this iife to keep codesize low.
* @param {Object} w shorthand for window
* @param {Object} d shorthand for document
* @param {Object} H shorthand for Hyphenopoly
* @param {Object} o shorthand for object
*/
(function H9YL(w, d, H, o) {
"use strict";
var store = sessionStorage;
var wa = w.WebAssembly;
var lcFallbacks = new Map();
var lcRequire = new Map();
/**
* Create Object without standard Object-prototype
* @returns {Object} empty object
*/
function empty() {
return o.create(null);
}
/**
* Shorthand for Object.keys(obj).forEach(function () {})
* @param {Object} obj the object to iterate
* @param {function} fn the function to execute
* @returns {undefined}
*/
function eachKey(obj, fn) {
o.keys(obj).forEach(fn);
}
/**
* Set H.cf (Hyphenopoly.clientFeatures) either by reading out previously
* computed settings from sessionStorage or creating an template object.
* This is in an iife to keep complexity low.
*/
(function configFeat() {
if (H.cacheFeatureTests && store.getItem("Hyphenopoly_Loader")) {
H.cf = JSON.parse(store.getItem("Hyphenopoly_Loader"));
} else {
H.cf = {
"langs": empty(),
"polyfill": false,
"wasm": null
};
}
}());
/**
* Set H.paths defaults or overwrite with user settings.
* This is in an iife to keep complexity low.
*/
(function configPaths() {
var maindir = (d.currentScript)
? d.currentScript.src.replace(/Hyphenopoly_Loader.js/i, "")
: "../";
var patterndir = maindir + "patterns/";
if (H.paths) {
H.paths.maindir = H.paths.maindir || maindir;
H.paths.patterndir = H.paths.patterndir || patterndir;
} else {
H.paths = o.create({
"maindir": maindir,
"patterndir": patterndir
});
}
}());
/**
* Set some H.setup fields to defaults or overwrite with user settings.
* This is in an iife to keep complexity low.
*/
(function configSetup() {
if (H.setup) {
H.setup.selectors = H.setup.selectors || {".hyphenate": {}};
H.setup.timeout = H.setup.timeout || 1000;
H.setup.hide = H.setup.hide || "all";
} else {
H.setup = {
"hide": "all",
"selectors": {".hyphenate": {}},
"timeout": 1000
};
}
}());
/**
* Copy required languages to local lcRequire and
* eventually fallbacks to local lcFallbacks.
* This is in an iife to keep complexity low.
*/
(function configRequire() {
eachKey(H.require, function copyRequire(k) {
// eslint-disable-next-line security/detect-object-injection
lcRequire.set(k.toLowerCase(), H.require[k]);
});
if (H.fallbacks) {
eachKey(H.fallbacks, function copyFallbacks(k) {
lcFallbacks.set(
k.toLowerCase(),
// eslint-disable-next-line security/detect-object-injection
H.fallbacks[k].toLowerCase()
);
});
}
}());
/**
* Define function H.toggle.
* This function hides or unhides (depending of the parameter state)
* the whole document (H.setup.hide == "all") or
* each selected element (H.setup.hide == "element") or
* text of each selected element (H.setup.hide == "text")
* @param {string} state State: either on (visible) or off (hidden)
*/
H.toggle = function toggle(state) {
if (state === "on") {
var stylesNode = d.getElementById("H9Y_Styles");
if (stylesNode) {
stylesNode.parentNode.removeChild(stylesNode);
}
} else {
var vis = " {visibility: hidden !important}\n";
var sc = d.createElement("style");
var myStyle = "";
sc.id = "H9Y_Styles";
switch (H.setup.hide) {
case "all":
myStyle = "html" + vis;
break;
case "element":
eachKey(H.setup.selectors, function eachSelector(sel) {
myStyle += sel + vis;
});
break;
case "text":
eachKey(H.setup.selectors, function eachSelector(sel) {
myStyle += sel + " {color: transparent !important}\n";
});
break;
// No Default
}
sc.appendChild(d.createTextNode(myStyle));
d.head.appendChild(sc);
}
};
/**
* Setup basic event system. Some events are defined but the definition of
* what happens when they are triggered is deferred to Hyphenopoly.js
* This is in an iife to keep complexity low.
*/
(function setupEvents() {
// Events known to the system
var definedEvents = new Map();
// Default events, execution deferred to Hyphenopoly.js
var deferred = [];
/*
* Register for custom event handlers, where event is not yet defined
* these events will be correctly registered in Hyphenopoly.js
*/
var tempRegister = [];
/**
* Create Event Object
* @param {string} name The Name of the event
* @param {function} defFunc The default method of the event
* @param {boolean} cancellable Is the default cancellable
* @returns {undefined}
*/
function define(name, defFunc, cancellable) {
definedEvents.set(name, {
"cancellable": cancellable,
"default": defFunc,
"register": []
});
}
define(
"timeout",
function def(e) {
H.toggle("on");
w.console.info(
"Hyphenopolys 'FOUHC'-prevention timed out after %dms",
e.delay
);
},
false
);
define(
"error",
function def(e) {
switch (e.lvl) {
case "info":
w.console.info(e.msg);
break;
case "warn":
w.console.warn(e.msg);
break;
default:
w.console.error(e.msg);
}
},
true
);
define(
"contentLoaded",
function def(e) {
deferred.push({
"data": e,
"name": "contentLoaded"
});
},
false
);
define(
"engineLoaded",
function def(e) {
deferred.push({
"data": e,
"name": "engineLoaded"
});
},
false
);
define(
"hpbLoaded",
function def(e) {
deferred.push({
"data": e,
"name": "hpbLoaded"
});
},
false
);
define(
"loadError",
function def(e) {
deferred.push({
"data": e,
"name": "loadError"
});
},
false
);
define(
"tearDown",
null,
true
);
/**
* Dispatch event <name> with arguments <data>
* @param {string} name The name of the event
* @param {Object|undefined} data Data of the event
* @returns {undefined}
*/
function dispatch(name, data) {
data = data || empty();
var defaultPrevented = false;
definedEvents.get(name).register.forEach(
function call(currentHandler) {
data.preventDefault = function preventDefault() {
if (definedEvents.get(name).cancellable) {
defaultPrevented = true;
}
};
currentHandler(data);
}
);
if (
!defaultPrevented &&
definedEvents.get(name).default
) {
definedEvents.get(name).default(data);
}
}
/**
* Add EventListender <handler> to event <name>
* @param {string} name The name of the event
* @param {function} handler Function to register
* @param {boolean} defer If the registration is deferred
* @returns {undefined}
*/
function addListener(name, handler, defer) {
if (definedEvents.has(name)) {
definedEvents.get(name).register.push(handler);
} else if (defer) {
tempRegister.push({
"handler": handler,
"name": name
});
} else {
H.events.dispatch(
"error",
{
"lvl": "warn",
"msg": "unknown Event \"" + name + "\" discarded"
}
);
}
}
if (H.handleEvent) {
eachKey(H.handleEvent, function add(name) {
/* eslint-disable security/detect-object-injection */
addListener(name, H.handleEvent[name], true);
/* eslint-enable security/detect-object-injection */
});
}
H.events = empty();
H.events.deferred = deferred;
H.events.tempRegister = tempRegister;
H.events.dispatch = dispatch;
H.events.define = define;
H.events.addListener = addListener;
}());
/**
* Feature test for wasm.
* @returns {boolean} support
*/
function runWasmTest() {
/*
* Wasm feature test with iOS bug detection
* (https://bugs.webkit.org/show_bug.cgi?id=181781)
*/
if (
typeof wa === "object" &&
typeof wa.Instance === "function"
) {
// #### BEGIN MODIFIED BY SAP
// modification adds try-catch block to enable fallback to asm.js in restricted environments
try {
/* eslint-disable array-element-newline */
var module = new wa.Module(Uint8Array.from([
0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127,
3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 5, 1, 1, 116, 0, 0,
10, 16, 1, 14, 0, 32, 0, 65, 1, 54, 2, 0, 32, 0, 40, 2,
0, 11
]));
/* eslint-enable array-element-newline */
return (new wa.Instance(module).exports.t(4) !== 0);
} catch (e) {
return false;
}
// #### END MODIFIED BY SAP
}
return false;
}
/**
* Load script by adding <script>-tag
* @param {string} path Where the script is stored
* @param {string} filename Filename of the script
* @returns {undefined}
*/
function loadScript(path, filename) {
var script = d.createElement("script");
script.src = path + filename;
if (filename === "hyphenEngine.asm.js") {
script.addEventListener("load", function listener() {
H.events.dispatch("engineLoaded", {"msg": "asm"});
});
}
d.head.appendChild(script);
}
var loadedBins = new Map();
/**
* Load binary files either with fetch (on new browsers that support wasm)
* or with xmlHttpRequest
* @param {string} path Where the script is stored
* @param {string} fne Filename of the script with extension
* @param {string} name Name of the ressource
* @param {Object} msg Message
* @returns {undefined}
*/
function loadBinary(path, fne, name, msg) {
/**
* Get bin file using fetch
* @param {string} p Where the script is stored
* @param {string} f Filename of the script with extension
* @param {string} n Name of the ressource
* @param {Object} m Message
* @returns {undefined}
*/
function fetchBinary(p, f, n, m) {
// #### BEGIN MODIFIED BY SAP
// modification is to not include credentials in the fetch request
w.fetch(p + f)
// #### END MODIFIED BY SAP
.then(
function resolve(response) {
if (response.ok) {
if (n === "hyphenEngine") {
H.bins.set(n, response.arrayBuffer().then(
function getModule(buf) {
return new wa.Module(buf);
}
));
H.events.dispatch("engineLoaded", {"msg": m});
} else {
var files = loadedBins.get(f);
files.forEach(function eachHpb(rn) {
H.bins.set(
rn,
(files.length > 1)
? response.clone().arrayBuffer()
: response.arrayBuffer()
);
H.events.dispatch(
"hpbLoaded",
{"msg": rn}
);
});
}
} else {
H.events.dispatch("loadError", {
"file": f,
"msg": m,
"name": n,
"path": p
});
}
}
);
}
/**
* Get bin file using XHR
* @param {string} p Where the script is stored
* @param {string} f Filename of the script with extension
* @param {string} n Name of the ressource
* @param {Object} m Message
* @returns {undefined}
*/
function requestBinary(p, f, n, m) {
var xhr = new XMLHttpRequest();
xhr.onload = function onload() {
if (xhr.status === 200) {
loadedBins.get(f).
forEach(function eachHpb(rn) {
H.bins.set(
rn,
xhr.response
);
H.events.dispatch(
"hpbLoaded",
{"msg": rn}
);
});
} else {
H.events.dispatch("loadError", {
"file": f,
"msg": m,
"name": n,
"path": p
});
}
};
xhr.open("GET", p + f);
xhr.responseType = "arraybuffer";
xhr.send();
}
if (!loadedBins.has(fne)) {
loadedBins.set(fne, [msg]);
if (H.cf.wasm) {
fetchBinary(path, fne, name, msg);
} else {
requestBinary(path, fne, name, msg);
}
} else if (name !== "hyphenEngine") {
loadedBins.get(fne).push(msg);
}
}
/**
* Pre-Allocate memory for (w)asm
* Default is 32 wasm Pages (). For languages with larger .hpb
* files a higher value is needed.
* Get the value from baseData.heapSize in Hyphenopoly.js
* @param {string} lang Language
* @returns {undefined}
*/
function allocateMemory(lang) {
var specVal = new Map(
[["de", 54], ["hu", 205], ["nb-no", 91], ["nl", 41]]
);
var wasmPages = specVal.get(lang) || 32;
H.specMems = H.specMems || new Map();
if (H.cf.wasm) {
H.specMems.set(lang, new wa.Memory({
"initial": wasmPages,
"maximum": 256
}));
} else {
/* eslint-disable no-bitwise */
var asmPages = (2 << Math.floor(
Math.log(wasmPages) * Math.LOG2E
)) << 16;
/* eslint-enable no-bitwise */
H.specMems.set(lang, new ArrayBuffer(asmPages));
}
}
(function testClientFeatures() {
var tester = (function tester() {
var fakeBody = null;
/* eslint-disable array-element-newline */
var css = [
"visibility:hidden",
"-moz-hyphens:auto",
"-webkit-hyphens:auto",
"-ms-hyphens:auto",
"hyphens:auto",
"width:48px",
"font-size:12px",
"line-height:12px",
"border:none",
"padding:0",
"word-wrap:normal"
].join(";");
/* eslint-enable array-element-newline */
/**
* Create and append div with CSS-hyphenated word
* @param {string} lang Language
* @returns {undefined}
*/
function create(lang) {
/* eslint-disable security/detect-object-injection */
if (H.cf.langs[lang]) {
return;
}
/* eslint-enable security/detect-object-injection */
fakeBody = fakeBody || d.createElement("body");
var testDiv = d.createElement("div");
testDiv.lang = lang;
testDiv.style.cssText = css;
testDiv.appendChild(
d.createTextNode(lcRequire.get(lang).toLowerCase())
);
fakeBody.appendChild(testDiv);
}
/**
* Append fakeBody with tests to target (document)
* @param {Object} target Where to append fakeBody
* @returns {Object|null} The body element or null, if no tests
*/
function append(target) {
if (fakeBody) {
target.appendChild(fakeBody);
return fakeBody;
}
return null;
}
/**
* Remove fakeBody
* @returns {undefined}
*/
function clear() {
if (fakeBody) {
fakeBody.parentNode.removeChild(fakeBody);
}
}
return {
"append": append,
"clear": clear,
"create": create
};
}());
/**
* Checks if hyphens (ev.prefixed) is set to auto for the element.
* @param {Object} elm - the element
* @returns {Boolean} result of the check
*/
function checkCSSHyphensSupport(elm) {
return (
elm.style.hyphens === "auto" ||
elm.style.webkitHyphens === "auto" ||
elm.style.msHyphens === "auto" ||
elm.style["-moz-hyphens"] === "auto"
);
}
/**
* Expose the hyphenate-function of a specific language to
* Hyphenopoly.hyphenators.<language>
*
* Hyphenopoly.hyphenators.<language> is a Promise that fullfills
* to hyphenate(entity, sel) as soon as the ressources are loaded
* and the engine is ready.
* If Promises aren't supported (e.g. IE11) a error message is produced.
*
* @param {string} lang - the language
* @returns {undefined}
*/
function exposeHyphenateFunction(lang) {
/* eslint-disable security/detect-object-injection */
H.hyphenators = H.hyphenators || empty();
if (!H.hyphenators[lang]) {
if (w.Promise) {
H.hyphenators[lang] = new Promise(function pro(rs, rj) {
H.events.addListener(
"engineReady",
function handler(e) {
if (e.msg === lang) {
rs(H.createHyphenator(e.msg));
}
},
true
);
H.events.addListener(
"loadError",
function handler(e) {
if (e.name === lang || e.name === "hyphenEngine") {
rj(new Error("File " + e.file + " can't be loaded from " + e.path));
}
},
false
);
});
H.hyphenators[lang].catch(function catchPromiseError(e) {
H.events.dispatch(
"error",
{
"lvl": "error",
"msg": e.message
}
);
});
} else {
H.hyphenators[lang] = {
/**
* Fires an error message, if then is called
* @returns {undefined}
*/
"then": function () {
H.events.dispatch(
"error",
{"msg": "Promises not supported in this engine. Use a polyfill."}
);
}
};
}
}
/* eslint-enable security/detect-object-injection */
}
/**
* Load .hpb files
* @param {string} lang The language
* @returns {undefined}
*/
function loadPattern(lang) {
var filename = lang + ".hpb";
var langFallback = lang;
H.cf.polyfill = true;
// eslint-disable-next-line security/detect-object-injection
H.cf.langs[lang] = "H9Y";
if (lcFallbacks && lcFallbacks.has(lang)) {
langFallback = lcFallbacks.get(lang);
filename = langFallback + ".hpb";
}
H.bins = H.bins || new Map();
loadBinary(H.paths.patterndir, filename, langFallback, lang);
}
if (H.cf.wasm === null) {
H.cf.wasm = runWasmTest();
}
lcRequire.forEach(function eachReq(value, lang) {
if (value === "FORCEHYPHENOPOLY" ||
// eslint-disable-next-line security/detect-object-injection
(H.cf.langs[lang] && H.cf.langs[lang] === "H9Y")
) {
loadPattern(lang);
} else {
tester.create(lang);
}
});
var testContainer = tester.append(d.documentElement);
if (testContainer !== null) {
var nl = testContainer.querySelectorAll("div");
Array.prototype.forEach.call(nl, function eachNode(n) {
if (checkCSSHyphensSupport(n) && n.offsetHeight > 12) {
H.cf.langs[n.lang] = "CSS";
} else {
loadPattern(n.lang);
}
});
tester.clear();
}
if (H.cf.polyfill) {
loadScript(H.paths.maindir, "Hyphenopoly.js");
if (H.cf.wasm) {
loadBinary(
H.paths.maindir,
"hyphenEngine.wasm",
"hyphenEngine",
"wasm"
);
} else {
loadScript(H.paths.maindir, "hyphenEngine.asm.js");
}
eachKey(H.cf.langs, function prepareEach(lang) {
/* eslint-disable security/detect-object-injection */
if (H.cf.langs[lang] === "H9Y") {
allocateMemory(lang);
exposeHyphenateFunction(lang);
}
/* eslint-enable security/detect-object-injection */
});
}
}());
/**
* Hides the specified elements and starts the process by
* dispatching a "contentLoaded"-event in Hyphenopoly
* @returns {undefined}
*/
function handleDCL() {
if (H.setup.hide.match(/^(?:element|text)$/)) {
H.toggle("off");
}
H.events.dispatch(
"contentLoaded",
{"msg": ["contentLoaded"]}
);
}
if (H.cf.polyfill) {
if (H.setup.hide === "all") {
H.toggle("off");
}
if (H.setup.hide !== "none") {
H.setup.timeOutHandler = w.setTimeout(function timedOut() {
H.toggle("on");
H.events.dispatch("timeout", {"delay": H.setup.timeout});
}, H.setup.timeout);
}
if (d.readyState === "loading") {
d.addEventListener(
"DOMContentLoaded",
handleDCL,
{
"once": true,
"passive": true
}
);
} else {
handleDCL();
}
} else {
H.events.dispatch("tearDown", {});
w.Hyphenopoly = null;
}
if (H.cacheFeatureTests) {
store.setItem(
"Hyphenopoly_Loader",
JSON.stringify(H.cf)
);
}
}(window, document, Hyphenopoly, Object));