From 832e2413c94cd5e7950ce18221a9c51a4b28de1a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 21 Feb 2023 12:49:43 +0700 Subject: [PATCH 1/5] refactor(web): cookie serialization class, type enforcement --- .../engine/dom-utils/src/cookieSerializer.ts | 91 ++++++++++++++++ web/src/engine/dom-utils/src/index.ts | 3 +- .../test/auto/dom/cases/dom-utils/cookies.js | 102 ++++++++++++++++++ 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 web/src/engine/dom-utils/src/cookieSerializer.ts create mode 100644 web/src/test/auto/dom/cases/dom-utils/cookies.js diff --git a/web/src/engine/dom-utils/src/cookieSerializer.ts b/web/src/engine/dom-utils/src/cookieSerializer.ts new file mode 100644 index 0000000000..a3454a81b0 --- /dev/null +++ b/web/src/engine/dom-utils/src/cookieSerializer.ts @@ -0,0 +1,91 @@ +type DecodedCookieFieldValue = string | number | boolean; + +type FilteredRecordEncoder = (value: DecodedCookieFieldValue, key: string) => string; +type FilteredRecordDecoder = (value: string, key: string) => DecodedCookieFieldValue; +const no_change = (val: string) => val as string; + +export default class CookieSerializer> { + readonly name: string; + + constructor(name: string) { + this.name = name; + } + + load(decoder?: FilteredRecordEncoder): Type { + return this.loadCookie(this.name, decoder || no_change) as Type; + } + + save(cookie: Type, encoder?: FilteredRecordEncoder) { + this.saveCookie(this.name, cookie, encoder || no_change); + } + + /** + * Document cookie parsing for use by kernel, OSK, UI etc. + * + * @return {Object} array of names and strings + */ + private _loadRawCookies(): Record { + let v: Record = {}; + if(typeof(document.cookie) != 'undefined' && document.cookie != '') { + let c = document.cookie.split(/;\s*/); + for(let i = 0; i < c.length; i++) { + let d = c[i].split('='); + if(d.length == 2) { + v[d[0]] = d[1]; + } + } + } + + return v; + } + + /** + * Document cookie parsing for use by kernel, OSK, UI etc. + * + * @param {string} cookieName cookie name + * @return {Object} array of variables and values + */ + private loadCookie(cookieName: string, decoder: FilteredRecordDecoder): Record { + let cookie: Record = {}; + let allCookies = this._loadRawCookies(); + const encodedCookie = allCookies[cookieName]; + + if(encodedCookie) { + let rawDecode = decodeURIComponent(encodedCookie).split(';'); + for(let i=0; i 1) { + const [key, value] = record; + // key, value + cookie[key] = decoder(value, key); + } else { + // key, + cookie[record[0]] = ''; + } + } + } + return cookie; + } + + /** + * Standard cookie saving for use by kernel, OSK, UI etc. + * + * @param {string} cookieName name of cookie + * @param {Object} cookieValueMap object with array of named arguments and values + */ + private saveCookie(cookieName: string, cookieValueMap: Record, encoder: FilteredRecordEncoder) { + let serialization=''; + for(let key in cookieValueMap) { + serialization += key + '=' + encoder(cookieValueMap[key], key) + ";"; + } + + let d = new Date(new Date().valueOf() + 1000 * 60 * 60 * 24 * 30).toUTCString(); + let cookieConfig = ' path=/; expires=' + d; //Fri, 31 Dec 2099 23:59:59 GMT;'; + document.cookie = `${cookieName}=${encodeURIComponent(serialization)}; ${cookieConfig}`; + } +} \ No newline at end of file diff --git a/web/src/engine/dom-utils/src/index.ts b/web/src/engine/dom-utils/src/index.ts index acd78a5b3b..cdaa75f06e 100644 --- a/web/src/engine/dom-utils/src/index.ts +++ b/web/src/engine/dom-utils/src/index.ts @@ -1,4 +1,5 @@ export { getAbsoluteX, getAbsoluteY } from './getAbsolute.js'; export { default as createUnselectableElement } from './createUnselectableElement.js'; export { createStyleSheet, StylesheetManager } from './stylesheets.js'; -export { default as landscapeView } from './landscapeView.js'; \ No newline at end of file +export { default as landscapeView } from './landscapeView.js'; +export { default as CookieSerializer } from './cookieSerializer.js'; \ No newline at end of file diff --git a/web/src/test/auto/dom/cases/dom-utils/cookies.js b/web/src/test/auto/dom/cases/dom-utils/cookies.js new file mode 100644 index 0000000000..5a7bffee3f --- /dev/null +++ b/web/src/test/auto/dom/cases/dom-utils/cookies.js @@ -0,0 +1,102 @@ +import Device from '/@keymanapp/keyman/build/engine/device-detect/lib/index.mjs'; +import { default as CookieSerializer } from '/@keymanapp/keyman/build/engine/dom-utils/obj/cookieSerializer.js'; + +let assert = chai.assert; + +const device = new Device(); +device.detect(); + +const RESET="max-age=0"; + +describe('CookieSerializer', function () { + describe('SimpleTestCookie', () => { + const COOKIE_ID = "SimpleTestCookie"; + + beforeEach(() => { + // Purge the cookie! + document.cookie = `${COOKIE_ID}=foobar; ${RESET}`; + + const cookieLoader = new CookieSerializer(COOKIE_ID); + assert.deepEqual(cookieLoader.load(), {}); + }); + + it('serializes & reloads when all values are strings', () => { + const cookieWriter = new CookieSerializer(COOKIE_ID); + const obj = { + foo: 'bar', + widget: 'sprog', + fruity: 'tooty', + flagProp: '' + } + cookieWriter.save(obj); + + const cookieReader = new CookieSerializer(COOKIE_ID); + const reloadedObj = cookieReader.load(); + + assert.deepEqual(reloadedObj, obj); + assert.notStrictEqual(reloadedObj, obj); + }); + + it('serializes all values to strings', () => { + const cookieWriter = new CookieSerializer(COOKIE_ID); + const obj = { + foo: 'bar', + two: 2, + true: true + } + const expectedObj = { + foo: 'bar', + two: `${2}`, + true: `${true}` + } + cookieWriter.save(obj); + + const cookieReader = new CookieSerializer(COOKIE_ID); + const reloadedObj = cookieReader.load(); + + assert.deepEqual(reloadedObj, expectedObj); + assert.notStrictEqual(reloadedObj, expectedObj); + }); + + it('accepts custom deserialization', () => { + const cookieWriter = new CookieSerializer(COOKIE_ID); + const obj = { + foo: 'bar', + two: 2, + true: true + } + cookieWriter.save(obj); + + const cookieReader = new CookieSerializer(COOKIE_ID); + const reloadedObj = cookieReader.load((value, key) => { + switch(key) { + case 'two': + return Number.parseInt(value, 10); + case 'true': + return value === 'true'; + default: + return value; + } + }); + + assert.deepEqual(reloadedObj, obj); + assert.notStrictEqual(reloadedObj, obj); + }); + + it('works with encodeURIComponent + decodeURIComponent', () => { + const cookieWriter = new CookieSerializer(COOKIE_ID); + const obj = { + foo: 'bar', + symbols: ':;+&?@ =\n', // Stuff that'll royally wreck cookies if not encoded. + star: '⭐' // Sure, why not test with an emoji? + } + cookieWriter.save(obj, encodeURIComponent); + + const cookieReader = new CookieSerializer(COOKIE_ID); + const reloadedObj = cookieReader.load(decodeURIComponent); + + assert.deepEqual(reloadedObj, obj); + assert.notStrictEqual(reloadedObj, obj); + }); + }); +}); \ No newline at end of file From a36c0f11caa8cf3d323622c393ccb67713830c11 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 21 Feb 2023 13:39:06 +0700 Subject: [PATCH 2/5] refactor(web): OSK cookie now uses new cookie serializer --- .../engine/dom-utils/src/cookieSerializer.ts | 2 +- web/src/engine/osk/src/index.ts | 1 - .../engine/osk/src/views/floatingOskCookie.ts | 35 ++++++++++++++- .../engine/osk/src/views/floatingOskView.ts | 44 +++++++------------ 4 files changed, 51 insertions(+), 31 deletions(-) diff --git a/web/src/engine/dom-utils/src/cookieSerializer.ts b/web/src/engine/dom-utils/src/cookieSerializer.ts index a3454a81b0..a7a0263027 100644 --- a/web/src/engine/dom-utils/src/cookieSerializer.ts +++ b/web/src/engine/dom-utils/src/cookieSerializer.ts @@ -11,7 +11,7 @@ export default class CookieSerializer> { + constructor() { + super('KeymanWeb_OnScreenKeyboard'); + } + + load() { + const cookie = super.load((value, key) => { + switch(key) { + case 'visible': + case 'userSet': + return Number.parseInt(value, 10); + default: + return value; + } + }); + + if(!cookie['width']) { + delete cookie['width']; // in case of a '' entry. + } + if(!cookie['height']) { + delete cookie['height']; // in case of a '' entry. + } + + return cookie; + } + + save(cookie: Required) { + super.save(cookie); + } } \ No newline at end of file diff --git a/web/src/engine/osk/src/views/floatingOskView.ts b/web/src/engine/osk/src/views/floatingOskView.ts index b0af747c68..c22d08f182 100644 --- a/web/src/engine/osk/src/views/floatingOskView.ts +++ b/web/src/engine/osk/src/views/floatingOskView.ts @@ -9,7 +9,7 @@ import MouseDragOperation from '../input/mouseDragOperation.js'; import { getViewportScale } from '../screenUtils.js'; import Configuration from '../config/viewConfiguration.js'; import TwoStateActivator from './twoStateActivator.js'; -import FloatingOSKCookie from './floatingOskCookie.js'; +import { FloatingOSKCookie, FloatingOSKCookieSerializer } from './floatingOskCookie.js'; /*** KeymanWeb 10.0 @@ -18,12 +18,6 @@ import FloatingOSKCookie from './floatingOskCookie.js'; interface FloatingOSKViewConfiguration extends Configuration { activator: TwoStateActivator; - - // Designed to replace util.saveCookie() within the OSK subproject. - saveViewLayout?: (data: FloatingOSKCookie) => void; - - // Designed to replace util.loadCookie() within the OSK subproject. - reloadViewLayout?: () => FloatingOSKCookie; } export default class FloatingOSKView extends OSKView { @@ -36,6 +30,8 @@ export default class FloatingOSKView extends OSKView { dfltX: string; dfltY: string; + layoutSerializer = new FloatingOSKCookieSerializer(); + private titleBar: TitleBar; private resizeBar: ResizeBar; @@ -46,20 +42,12 @@ export default class FloatingOSKView extends OSKView { public constructor(config: FloatingOSKViewConfiguration) { config.activator = config.activator || new TwoStateActivator(); - config.saveViewLayout = config.saveViewLayout || (() => {}); - - config.reloadViewLayout = config.reloadViewLayout || (() => { - return {} as unknown as FloatingOSKCookie; - }); - super(config); this.typedActivationModel.on('triggerChange', () => this.setDisplayPositioning()); document.body.appendChild(this._Box); - this.loadCookie(); - // Add header element to OSK only for desktop browsers this.titleBar = new TitleBar(this.titleDragHandler); this.titleBar.on('help', () => { @@ -77,6 +65,8 @@ export default class FloatingOSKView extends OSKView { this.resizeBar.on('showBuild', () => this.emit('showBuild')); this.headerView = this.titleBar; + + this.loadCookie(); } private get typedActivationModel(): TwoStateActivator { @@ -190,12 +180,11 @@ export default class FloatingOSKView extends OSKView { } if(this.vkbd) { - c['width'] = '' + this.width.val; - c['height'] = '' + this.height.val; + c.width = '' + this.width.val; + c.height = '' + this.height.val; } - const typedConfiguration = this.configuration as FloatingOSKViewConfiguration; - typedConfiguration.saveViewLayout(c); + this.layoutSerializer.save(c as Required); } /** @@ -209,21 +198,20 @@ export default class FloatingOSKView extends OSKView { return isNaN(val) ? fallback: val; } - const typedConfiguration = this.configuration as FloatingOSKViewConfiguration; - var c: FloatingOSKCookie = typedConfiguration.reloadViewLayout(); + let c = this.layoutSerializer.load(); - this.activationModel.enabled = parseIntWithDefault(c['visible'], 1) == 1; - this.userPositioned = parseIntWithDefault(c['userSet'], 0) == 1; - this.x = parseIntWithDefault(c['left'],-1); - this.y = parseIntWithDefault(c['top'],-1); - let cookieVersionString = c['_version']; + this.activationModel.enabled = parseIntWithDefault(c.visible, 1) == 1; + this.userPositioned = parseIntWithDefault(c.userSet, 0) == 1; + this.x = parseIntWithDefault(c.left,-1); + this.y = parseIntWithDefault(c.top,-1); + let cookieVersionString = c._version; // Restore OSK size - font size now fixed in relation to OSK height, unless overridden (in em) by keyboard let dfltWidth=0.3*screen.width; let dfltHeight=0.15*screen.height; - let newWidth = parseInt(c['width'], 10); - let newHeight = parseInt(c['height'], 10); + let newWidth = parseInt(c.width, 10); + let newHeight = parseInt(c.height, 10); let isNewCookie = isNaN(newHeight); newWidth = isNaN(newWidth) ? dfltWidth : newWidth; newHeight = isNaN(newHeight) ? dfltHeight : newHeight; From 85966e128bb4298363cea1324e4ab0c116b175a4 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 21 Feb 2023 13:39:30 +0700 Subject: [PATCH 3/5] change(web): osk cookie now leverages numeric typing, defaults --- .../engine/osk/src/views/floatingOskCookie.ts | 60 ++++++++++++++----- .../engine/osk/src/views/floatingOskView.ts | 48 +++++++-------- 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/web/src/engine/osk/src/views/floatingOskCookie.ts b/web/src/engine/osk/src/views/floatingOskCookie.ts index 4f6bb0bc7f..8a9c48ec49 100644 --- a/web/src/engine/osk/src/views/floatingOskCookie.ts +++ b/web/src/engine/osk/src/views/floatingOskCookie.ts @@ -1,13 +1,42 @@ import { CookieSerializer } from 'keyman/engine/dom-utils'; export interface FloatingOSKCookie { - visible: '0' | '1'; - userSet: '0' | '1'; - left: string; - top: string; - width?: string; - height?: string; + /** + * Notes whether or not the OSK was hidden at the end of the previous session. + */ + visible: 0 | 1; + /** + * Notes whether or not the OSK was pinned (located by the user) at the end + * of the previous session. + */ + userSet: 0 | 1; + + /** + * Denotes the left-position of the OSK at the end of the previous session if pinned. + * Defaults to -1 if the value was undefined. + */ + left: number; + + /** + * Denotes the left-position of the OSK at the end of the previous session if pinned. + * Defaults to -1 if the value was undefined. + */ + top: number; + + /** + * The previously-set OSK width. + */ + width?: number; + + /** + * The previously-set OSK height. + */ + height?: number; + + /** + * The version of KeymanWeb active when this cookie was generated. + */ _version: string; } @@ -16,22 +45,25 @@ export class FloatingOSKCookieSerializer extends CookieSerializer) { + return {...defaults, ...this.load()}; + } + load() { const cookie = super.load((value, key) => { switch(key) { - case 'visible': - case 'userSet': - return Number.parseInt(value, 10); - default: + case 'version': return value; + default: + return Number.parseInt(value, 10); } }); - if(!cookie['width']) { - delete cookie['width']; // in case of a '' entry. + if(!cookie.width) { + delete cookie.width; // in case of a '' entry. } - if(!cookie['height']) { - delete cookie['height']; // in case of a '' entry. + if(!cookie.height) { + delete cookie.height; // in case of a '' entry. } return cookie; diff --git a/web/src/engine/osk/src/views/floatingOskView.ts b/web/src/engine/osk/src/views/floatingOskView.ts index c22d08f182..60256ab20f 100644 --- a/web/src/engine/osk/src/views/floatingOskView.ts +++ b/web/src/engine/osk/src/views/floatingOskView.ts @@ -172,16 +172,16 @@ export default class FloatingOSKView extends OSKView { var p = this.getPos(); const c: FloatingOSKCookie = { - visible: this.displayIfActive ? '1' : '0', - userSet: this.userPositioned ? '1' : '0', - left: '' + p.left, - top: '' + p.top, + visible: this.displayIfActive ? 1 : 0, + userSet: this.userPositioned ? 1 : 0, + left: p.left, + top: p.top, _version: Version.CURRENT.toString() } if(this.vkbd) { - c.width = '' + this.width.val; - c.height = '' + this.height.val; + c.width = this.width.val; + c.height = this.height.val; } this.layoutSerializer.save(c as Required); @@ -193,28 +193,26 @@ export default class FloatingOSKView extends OSKView { * @return {boolean} */ loadCookie(): void { - function parseIntWithDefault(str: string, fallback: number) { - let val = Number.parseInt(str, 10); - return isNaN(val) ? fallback: val; - } + let c = this.layoutSerializer.loadWithDefaults({ + visible: 1, + userSet: 0, + left: -1, + top: -1, + _version: undefined, + width: 0.3*screen.width, + height: 0.15*screen.height + }); - let c = this.layoutSerializer.load(); - - this.activationModel.enabled = parseIntWithDefault(c.visible, 1) == 1; - this.userPositioned = parseIntWithDefault(c.userSet, 0) == 1; - this.x = parseIntWithDefault(c.left,-1); - this.y = parseIntWithDefault(c.top,-1); - let cookieVersionString = c._version; + this.activationModel.enabled = c.visible == 1; + this.userPositioned = c.userSet == 1; + this.x = c.left; + this.y = c.top; + const cookieVersionString = c._version; // Restore OSK size - font size now fixed in relation to OSK height, unless overridden (in em) by keyboard - let dfltWidth=0.3*screen.width; - let dfltHeight=0.15*screen.height; - - let newWidth = parseInt(c.width, 10); - let newHeight = parseInt(c.height, 10); - let isNewCookie = isNaN(newHeight); - newWidth = isNaN(newWidth) ? dfltWidth : newWidth; - newHeight = isNaN(newHeight) ? dfltHeight : newHeight; + const isNewCookie = cookieVersionString === undefined; + let newWidth = c.width; + let newHeight = c.height; // Limit the OSK dimensions to reasonable values if(newWidth < 0.2*screen.width) { From e53e938d15edc061c286bd8630829baf99eab393 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 21 Feb 2023 13:48:07 +0700 Subject: [PATCH 4/5] chore(web): cleanup --- web/src/engine/osk/src/index.ts | 2 ++ .../engine/osk/src/views/floatingOskView.ts | 22 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/web/src/engine/osk/src/index.ts b/web/src/engine/osk/src/index.ts index fdb5e10300..255218c54e 100644 --- a/web/src/engine/osk/src/index.ts +++ b/web/src/engine/osk/src/index.ts @@ -4,6 +4,8 @@ export { default as FloatingOSKView } from './views/floatingOskView.js'; export { default as AnchoredOSKView } from './views/anchoredOskView.js'; export { default as InlinedOSKView } from './views/inlinedOskView.js'; export { BannerController } from './banner/bannerView.js'; +// Is referenced by at least one desktop UI module. +export { FloatingOSKCookie as FloatingOSKViewCookie } from './views/floatingOskCookie.js'; export { default as VisualKeyboard } from './visualKeyboard.js'; export type { default as ViewConfiguration } from './config/viewConfiguration.js'; diff --git a/web/src/engine/osk/src/views/floatingOskView.ts b/web/src/engine/osk/src/views/floatingOskView.ts index 60256ab20f..a6ebee1efa 100644 --- a/web/src/engine/osk/src/views/floatingOskView.ts +++ b/web/src/engine/osk/src/views/floatingOskView.ts @@ -66,7 +66,7 @@ export default class FloatingOSKView extends OSKView { this.headerView = this.titleBar; - this.loadCookie(); + this.loadPersistedLayout(); } private get typedActivationModel(): TwoStateActivator { @@ -110,7 +110,7 @@ export default class FloatingOSKView extends OSKView { this.footerView = null; } - this.loadCookie(); + this.loadPersistedLayout(); this.setNeedsLayout(); } @@ -127,13 +127,13 @@ export default class FloatingOSKView extends OSKView { let dragPromise = new ManagedPromise(); this.emit('dragMove', dragPromise.corePromise); - this.loadCookie(); + this.loadPersistedLayout(); this.userPositioned=false; if(!keepDefaultPosition) { delete this.dfltX; delete this.dfltY; } - this.saveCookie(); + this.savePersistedLayout(); if(isVisible) { this.present(); @@ -168,7 +168,7 @@ export default class FloatingOSKView extends OSKView { /** * Save size, position, font size and visibility of OSK */ - saveCookie() { + private savePersistedLayout() { var p = this.getPos(); const c: FloatingOSKCookie = { @@ -192,7 +192,7 @@ export default class FloatingOSKView extends OSKView { * * @return {boolean} */ - loadCookie(): void { + private loadPersistedLayout(): void { let c = this.layoutSerializer.loadWithDefaults({ visible: 1, userSet: 0, @@ -406,7 +406,7 @@ export default class FloatingOSKView extends OSKView { this.movementEnabled = !this.noDrag; } // Save the user-defined OSK size - this.saveCookie(); + this.savePersistedLayout(); } /** @@ -539,7 +539,7 @@ export default class FloatingOSKView extends OSKView { super.startHide(hiddenByUser); if(hiddenByUser) { - this.saveCookie(); // Save current OSK state, size and position (desktop only) + this.savePersistedLayout(); // Save current OSK state, size and position (desktop only) } } @@ -549,7 +549,7 @@ export default class FloatingOSKView extends OSKView { } else { super['show'](); } - this.saveCookie(); + this.savePersistedLayout(); } /** @@ -651,7 +651,7 @@ export default class FloatingOSKView extends OSKView { this.dragPromise.then(() => { _this.userPositioned = true; _this.doResizeMove(); - _this.saveCookie(); + _this.savePersistedLayout(); }); this.dragPromise = null; } @@ -729,7 +729,7 @@ export default class FloatingOSKView extends OSKView { // Remainder should be done after anything else pending on the Promise. this.dragPromise.then(() => { _this.doResizeMove(); - _this.saveCookie(); + _this.savePersistedLayout(); }); this.dragPromise = null; } From a8bd8a1712bac282e160cd3e9027c68905a78083 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 21 Feb 2023 14:25:13 +0700 Subject: [PATCH 5/5] chore(web): removes converted code from kmwutils.ts --- web/src/engine/namespaced-main/kmwutils.ts | 284 --------------------- 1 file changed, 284 deletions(-) diff --git a/web/src/engine/namespaced-main/kmwutils.ts b/web/src/engine/namespaced-main/kmwutils.ts index c3cf9d1c79..9c454310fd 100644 --- a/web/src/engine/namespaced-main/kmwutils.ts +++ b/web/src/engine/namespaced-main/kmwutils.ts @@ -29,8 +29,6 @@ namespace com.keyman { activeDevice: Device; physicalDevice: Device; - linkedStylesheets: (HTMLLinkElement|HTMLStyleElement)[] = []; - waiting: HTMLDivElement; // The element displayed for util.wait and util.alert. // An object mapping event names to individual event lists. Maps strings to arrays. @@ -262,37 +260,6 @@ namespace com.keyman { setOption(optionName,value) { this.keyman.options[optionName] = value; } - - // Unofficial API used by our desktop UIs. - getAbsoluteX(Pobj: HTMLElement): number { - return dom.Utils.getAbsoluteX(Pobj); - } - - // Unofficial API used by our desktop UIs. - getAbsoluteY(Pobj: HTMLElement): number { - return dom.Utils.getAbsoluteY(Pobj); - } - - /** - * Function getAbsolute - * Scope Public - * @param {Object} Pobj HTML element - * @return {Object.} - * Description Returns absolute position of Pobj element with respect to page - */ - getAbsolute(Pobj: HTMLElement) { - var p={ - /* @ export */ - x: this.getAbsoluteX(Pobj), - /* @ export */ - y: this.getAbsoluteY(Pobj) - }; - return p; - } - - // Unofficial API used by our desktop UIs. - _GetAbsolute = this.getAbsolute; - /** * Select start handler (to replace multiple inline handlers) (Build 360) */ @@ -541,196 +508,6 @@ namespace com.keyman { return bgColor; } - /** - * Add a stylesheet to a page programmatically, for use by the OSK, the UI or the page creator - * - * @param {string} s style string - * @return {Object} returns the object reference - **/ - addStyleSheet(s: string): HTMLStyleElement { - var _ElemStyle: HTMLStyleElement = document.createElement<'style'>('style'); - - _ElemStyle.type = 'text/css'; - _ElemStyle.appendChild(document.createTextNode(s)); - - var _ElemHead=document.getElementsByTagName('HEAD'); - if(_ElemHead.length > 0) { - _ElemHead[0].appendChild(_ElemStyle); - } else { - document.body.appendChild(_ElemStyle); // Won't work on Chrome, ah well - } - - this.linkedStylesheets.push(_ElemStyle); - - return _ElemStyle; - } - - /** - * Remove a stylesheet element - * - * @param {Object} s style sheet reference - * @return {boolean} false if element is not a style sheet - **/ - removeStyleSheet(s: HTMLStyleElement) { - if(s == null || typeof(s) != 'object') { - return false; - } - - if(s.nodeName != 'STYLE') { - return false; - } - - if(typeof(s.parentNode) == 'undefined' || s.parentNode == null) { - return false; - } - - s.parentNode.removeChild(s); - return true; - } - - /** - * Add a reference to an external stylesheet file - * - * @param {string} s path to stylesheet file - */ - linkStyleSheet(s: string): void { - try { - if(document.querySelector("link[href="+JSON.stringify(s)+"]") != null) { - // We've already linked this stylesheet, don't do it again - return; - } - } catch(e) { - // We've built an invalid href, somehow? - return; - } - - var headElements=document.getElementsByTagName('head'); - if(headElements.length > 0) { - var linkElement=document.createElement('link'); - linkElement.type='text/css'; - linkElement.rel='stylesheet'; - linkElement.href=s; - this.linkedStylesheets.push(linkElement); - headElements[0].appendChild(linkElement); - } - } - - /** - * Add a stylesheet with a font-face CSS descriptor for the embedded font appropriate - * for the browser being used - * - * @param {Object} fd keymanweb font descriptor - **/ - addFontFaceStyleSheet(fd: any) { // TODO: Font descriptor object needs definition! - // Test if a valid font descriptor - if(typeof(fd) == 'undefined') return; - - if(typeof(fd['files']) == 'undefined') fd['files']=fd['source']; - if(typeof(fd['files']) == 'undefined') return; - - var i,ttf='',woff='',eot='',svg='',fList=[]; - - // TODO: 22 Aug 2014: check that font path passed from cloud is actually used! - - // Do not add a new font-face style sheet if already added for this font - for(i=0; i 0) ttf=fList[i]; - if(fList[i].toLowerCase().indexOf('.ttf') > 0) ttf=fList[i]; - if(fList[i].toLowerCase().indexOf('.woff') > 0) woff=fList[i]; - if(fList[i].toLowerCase().indexOf('.eot') > 0) eot=fList[i]; - if(fList[i].toLowerCase().indexOf('.svg') > 0) svg=fList[i]; - } - - // Font path qualified to support page-relative fonts (build 347) - if(ttf != '' && (ttf.indexOf('/') < 0)) { - ttf = this.keyman.options['fonts']+ttf; - } - - if(woff != '' && (woff.indexOf('/') < 0)) { - woff = this.keyman.options['fonts']+woff; - } - - if(eot != '' && (eot.indexOf('/') < 0)) { - eot = this.keyman.options['fonts']+eot; - } - - if(svg != '' && (svg.indexOf('/') < 0)) { - svg = this.keyman.options['fonts']+svg; - } - - // Build the font-face definition according to the browser being used - var s='@font-face {\nfont-family:' - +fd['family']+';\nfont-style:normal;\nfont-weight:normal;\n'; - - // Build the font source string according to the browser, - // but return without adding the style sheet if the required font type is unavailable - - // Modern browsers: use WOFF, TTF and fallback finally to SVG. Don't provide EOT - if(this.device.OS == 'iOS') { - if(ttf != '') { - // Modify the url if required to prevent caching - ttf = this.unCached(ttf); - s=s+'src:url(\''+ttf+'\') format(\'truetype\');'; - } else { - return; - } - } else { - var s0 = []; - - if(this.device.OS == 'Android') { - // Android 4.2 and 4.3 have bugs in their rendering for some scripts - // with embedded ttf or woff. svg mostly works so is a better initial - // choice on the Android browser. - if(svg != '') { - s0.push("url('"+svg+"') format('svg')"); - } - - if(woff != '') { - s0.push("url('"+woff+"') format('woff')"); - } - - if(ttf != '') { - s0.push("url('"+ttf+"') format('truetype')"); - } - } else { - if(woff != '') { - s0.push("url('"+woff+"') format('woff')"); - } - - if(ttf != '') { - s0.push("url('"+ttf+"') format('truetype')"); - } - - if(svg != '') { - s0.push("url('"+svg+"') format('svg')"); - } - } - - if(s0.length == 0) { - return; - } - - s += 'src:'+s0.join(',')+';'; - } - - s=s+'\n}\n'; - - this.addStyleSheet(s); - this.embeddedFonts.push(fd['family']); - } - /** * Allow forced reload if necessary (stub only here) * @@ -743,59 +520,6 @@ namespace com.keyman { return s; } - /** - * Document cookie parsing for use by kernel, OSK, UI etc. - * - * @param {string=} cn cookie name (optional) - * @return {Object} array of names and strings, or array of variables and values - */ - loadCookie(cn?: string) { - var v={}; - if(arguments.length > 0) { - var cx = this.loadCookie(); - for(var t in cx) { - if(t == cn) { - var d = decodeURIComponent(cx[t]).split(';'); - for(var i=0; i 1) { - v[xc[0]] = xc[1]; - } else { - v[xc[0]] = ''; - } - } - } - } - } else { - if(typeof(document.cookie) != 'undefined' && document.cookie != '') { - var c = document.cookie.split(/;\s*/); - for(var i = 0; i < c.length; i++) { - var d = c[i].split('='); - if(d.length == 2) { - v[d[0]] = d[1]; - } - } - } - } - return v; - } - - /** - * Standard cookie saving for use by kernel, OSK, UI etc. - * - * @param {string} cn name of cookie - * @param {Object} cv object with array of named arguments and values - */ - saveCookie(cn: string, cv) { - var s=''; - for(var v in cv) { - s = s + v+'='+cv[v]+";"; - } - - var d = new Date(new Date().valueOf() + 1000 * 60 * 60 * 24 * 30).toUTCString(); - document.cookie = cn+'='+encodeURIComponent(s)+'; path=/; expires='+d;//Fri, 31 Dec 2099 23:59:59 GMT;'; - } - /** * Function toNumber * Scope Public @@ -973,14 +697,6 @@ namespace com.keyman { // Remove any KMW-added DOM element clutter. this.waiting.parentNode.removeChild(this.waiting); - - for(let ss of this.linkedStylesheets) { - if(ss.remove) { - ss.remove(); - } else if(ss.parentNode) { - ss.parentNode.removeChild(ss); - } - } } /**