From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/inputmethod/forms.js | 1561 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1561 insertions(+) create mode 100644 dom/inputmethod/forms.js (limited to 'dom/inputmethod/forms.js') diff --git a/dom/inputmethod/forms.js b/dom/inputmethod/forms.js new file mode 100644 index 000000000..1884f2b4d --- /dev/null +++ b/dom/inputmethod/forms.js @@ -0,0 +1,1561 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +dump("###################################### forms.js loaded\n"); + +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +XPCOMUtils.defineLazyServiceGetter(Services, "fm", + "@mozilla.org/focus-manager;1", + "nsIFocusManager"); + +/* + * A WeakMap to map window to objects keeping it's TextInputProcessor instance. + */ +var WindowMap = { + // WeakMap of pairs. + _map: null, + + /* + * Set the object associated to the window and return it. + */ + _getObjForWin: function(win) { + if (!this._map) { + this._map = new WeakMap(); + } + if (this._map.has(win)) { + return this._map.get(win); + } else { + let obj = { + tip: null + }; + this._map.set(win, obj); + + return obj; + } + }, + + getTextInputProcessor: function(win) { + if (!win) { + return; + } + let obj = this._getObjForWin(win); + let tip = obj.tip + + if (!tip) { + tip = obj.tip = Cc["@mozilla.org/text-input-processor;1"] + .createInstance(Ci.nsITextInputProcessor); + } + + if (!tip.beginInputTransaction(win, textInputProcessorCallback)) { + tip = obj.tip = null; + } + return tip; + } +}; + +const RESIZE_SCROLL_DELAY = 20; +// In content editable node, when there are hidden elements such as
, it +// may need more than one (usually less than 3 times) move/extend operations +// to change the selection range. If we cannot change the selection range +// with more than 20 opertations, we are likely being blocked and cannot change +// the selection range any more. +const MAX_BLOCKED_COUNT = 20; + +var HTMLDocument = Ci.nsIDOMHTMLDocument; +var HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement; +var HTMLBodyElement = Ci.nsIDOMHTMLBodyElement; +var HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement; +var HTMLInputElement = Ci.nsIDOMHTMLInputElement; +var HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement; +var HTMLSelectElement = Ci.nsIDOMHTMLSelectElement; +var HTMLOptGroupElement = Ci.nsIDOMHTMLOptGroupElement; +var HTMLOptionElement = Ci.nsIDOMHTMLOptionElement; + +function guessKeyNameFromKeyCode(KeyboardEvent, aKeyCode) { + switch (aKeyCode) { + case KeyboardEvent.DOM_VK_CANCEL: + return "Cancel"; + case KeyboardEvent.DOM_VK_HELP: + return "Help"; + case KeyboardEvent.DOM_VK_BACK_SPACE: + return "Backspace"; + case KeyboardEvent.DOM_VK_TAB: + return "Tab"; + case KeyboardEvent.DOM_VK_CLEAR: + return "Clear"; + case KeyboardEvent.DOM_VK_RETURN: + return "Enter"; + case KeyboardEvent.DOM_VK_SHIFT: + return "Shift"; + case KeyboardEvent.DOM_VK_CONTROL: + return "Control"; + case KeyboardEvent.DOM_VK_ALT: + return "Alt"; + case KeyboardEvent.DOM_VK_PAUSE: + return "Pause"; + case KeyboardEvent.DOM_VK_EISU: + return "Eisu"; + case KeyboardEvent.DOM_VK_ESCAPE: + return "Escape"; + case KeyboardEvent.DOM_VK_CONVERT: + return "Convert"; + case KeyboardEvent.DOM_VK_NONCONVERT: + return "NonConvert"; + case KeyboardEvent.DOM_VK_ACCEPT: + return "Accept"; + case KeyboardEvent.DOM_VK_MODECHANGE: + return "ModeChange"; + case KeyboardEvent.DOM_VK_PAGE_UP: + return "PageUp"; + case KeyboardEvent.DOM_VK_PAGE_DOWN: + return "PageDown"; + case KeyboardEvent.DOM_VK_END: + return "End"; + case KeyboardEvent.DOM_VK_HOME: + return "Home"; + case KeyboardEvent.DOM_VK_LEFT: + return "ArrowLeft"; + case KeyboardEvent.DOM_VK_UP: + return "ArrowUp"; + case KeyboardEvent.DOM_VK_RIGHT: + return "ArrowRight"; + case KeyboardEvent.DOM_VK_DOWN: + return "ArrowDown"; + case KeyboardEvent.DOM_VK_SELECT: + return "Select"; + case KeyboardEvent.DOM_VK_PRINT: + return "Print"; + case KeyboardEvent.DOM_VK_EXECUTE: + return "Execute"; + case KeyboardEvent.DOM_VK_PRINTSCREEN: + return "PrintScreen"; + case KeyboardEvent.DOM_VK_INSERT: + return "Insert"; + case KeyboardEvent.DOM_VK_DELETE: + return "Delete"; + case KeyboardEvent.DOM_VK_WIN: + return "OS"; + case KeyboardEvent.DOM_VK_CONTEXT_MENU: + return "ContextMenu"; + case KeyboardEvent.DOM_VK_SLEEP: + return "Standby"; + case KeyboardEvent.DOM_VK_F1: + return "F1"; + case KeyboardEvent.DOM_VK_F2: + return "F2"; + case KeyboardEvent.DOM_VK_F3: + return "F3"; + case KeyboardEvent.DOM_VK_F4: + return "F4"; + case KeyboardEvent.DOM_VK_F5: + return "F5"; + case KeyboardEvent.DOM_VK_F6: + return "F6"; + case KeyboardEvent.DOM_VK_F7: + return "F7"; + case KeyboardEvent.DOM_VK_F8: + return "F8"; + case KeyboardEvent.DOM_VK_F9: + return "F9"; + case KeyboardEvent.DOM_VK_F10: + return "F10"; + case KeyboardEvent.DOM_VK_F11: + return "F11"; + case KeyboardEvent.DOM_VK_F12: + return "F12"; + case KeyboardEvent.DOM_VK_F13: + return "F13"; + case KeyboardEvent.DOM_VK_F14: + return "F14"; + case KeyboardEvent.DOM_VK_F15: + return "F15"; + case KeyboardEvent.DOM_VK_F16: + return "F16"; + case KeyboardEvent.DOM_VK_F17: + return "F17"; + case KeyboardEvent.DOM_VK_F18: + return "F18"; + case KeyboardEvent.DOM_VK_F19: + return "F19"; + case KeyboardEvent.DOM_VK_F20: + return "F20"; + case KeyboardEvent.DOM_VK_F21: + return "F21"; + case KeyboardEvent.DOM_VK_F22: + return "F22"; + case KeyboardEvent.DOM_VK_F23: + return "F23"; + case KeyboardEvent.DOM_VK_F24: + return "F24"; + case KeyboardEvent.DOM_VK_NUM_LOCK: + return "NumLock"; + case KeyboardEvent.DOM_VK_SCROLL_LOCK: + return "ScrollLock"; + case KeyboardEvent.DOM_VK_VOLUME_MUTE: + return "AudioVolumeMute"; + case KeyboardEvent.DOM_VK_VOLUME_DOWN: + return "AudioVolumeDown"; + case KeyboardEvent.DOM_VK_VOLUME_UP: + return "AudioVolumeUp"; + case KeyboardEvent.DOM_VK_META: + return "Meta"; + case KeyboardEvent.DOM_VK_ALTGR: + return "AltGraph"; + case KeyboardEvent.DOM_VK_ATTN: + return "Attn"; + case KeyboardEvent.DOM_VK_CRSEL: + return "CrSel"; + case KeyboardEvent.DOM_VK_EXSEL: + return "ExSel"; + case KeyboardEvent.DOM_VK_EREOF: + return "EraseEof"; + case KeyboardEvent.DOM_VK_PLAY: + return "Play"; + default: + return "Unidentified"; + } +} + +var FormVisibility = { + /** + * Searches upwards in the DOM for an element that has been scrolled. + * + * @param {HTMLElement} node element to start search at. + * @return {Window|HTMLElement|Null} null when none are found window/element otherwise. + */ + findScrolled: function fv_findScrolled(node) { + let win = node.ownerDocument.defaultView; + + while (!(node instanceof HTMLBodyElement)) { + + // We can skip elements that have not been scrolled. + // We only care about top now remember to add the scrollLeft + // check if we decide to care about the X axis. + if (node.scrollTop !== 0) { + // the element has been scrolled so we may need to adjust + // where we think the root element is located. + // + // Otherwise it may seem visible but be scrolled out of the viewport + // inside this scrollable node. + return node; + } else { + // this node does not effect where we think + // the node is even if it is scrollable it has not hidden + // the element we are looking for. + node = node.parentNode; + continue; + } + } + + // we also care about the window this is the more + // common case where the content is larger then + // the viewport/screen. + if (win.scrollMaxX != win.scrollMinX || win.scrollMaxY != win.scrollMinY) { + return win; + } + + return null; + }, + + /** + * Checks if "top and "bottom" points of the position is visible. + * + * @param {Number} top position. + * @param {Number} height of the element. + * @param {Number} maxHeight of the window. + * @return {Boolean} true when visible. + */ + yAxisVisible: function fv_yAxisVisible(top, height, maxHeight) { + return (top > 0 && (top + height) < maxHeight); + }, + + /** + * Searches up through the dom for scrollable elements + * which are not currently visible (relative to the viewport). + * + * @param {HTMLElement} element to start search at. + * @param {Object} pos .top, .height and .width of element. + */ + scrollablesVisible: function fv_scrollablesVisible(element, pos) { + while ((element = this.findScrolled(element))) { + if (element.window && element.self === element) + break; + + // remember getBoundingClientRect does not care + // about scrolling only where the element starts + // in the document. + let offset = element.getBoundingClientRect(); + + // the top of both the scrollable area and + // the form element itself are in the same document. + // We adjust the "top" so if the elements coordinates + // are relative to the viewport in the current document. + let adjustedTop = pos.top - offset.top; + + let visible = this.yAxisVisible( + adjustedTop, + pos.height, + offset.height + ); + + if (!visible) + return false; + + element = element.parentNode; + } + + return true; + }, + + /** + * Verifies the element is visible in the viewport. + * Handles scrollable areas, frames and scrollable viewport(s) (windows). + * + * @param {HTMLElement} element to verify. + * @return {Boolean} true when visible. + */ + isVisible: function fv_isVisible(element) { + // scrollable frames can be ignored we just care about iframes... + let rect = element.getBoundingClientRect(); + let parent = element.ownerDocument.defaultView; + + // used to calculate the inner position of frames / scrollables. + // The intent was to use this information to scroll either up or down. + // scrollIntoView(true) will _break_ some web content so we can't do + // this today. If we want that functionality we need to manually scroll + // the individual elements. + let pos = { + top: rect.top, + height: rect.height, + width: rect.width + }; + + let visible = true; + + do { + let frame = parent.frameElement; + visible = visible && + this.yAxisVisible(pos.top, pos.height, parent.innerHeight) && + this.scrollablesVisible(element, pos); + + // nothing we can do about this now... + // In the future we can use this information to scroll + // only the elements we need to at this point as we should + // have all the details we need to figure out how to scroll. + if (!visible) + return false; + + if (frame) { + let frameRect = frame.getBoundingClientRect(); + + pos.top += frameRect.top + frame.clientTop; + } + } while ( + (parent !== parent.parent) && + (parent = parent.parent) + ); + + return visible; + } +}; + +// This object implements nsITextInputProcessorCallback +var textInputProcessorCallback = { + onNotify: function(aTextInputProcessor, aNotification) { + try { + switch (aNotification.type) { + case "request-to-commit": + // TODO: Send a notification through asyncMessage to the keyboard here. + aTextInputProcessor.commitComposition(); + + break; + case "request-to-cancel": + // TODO: Send a notification through asyncMessage to the keyboard here. + aTextInputProcessor.cancelComposition(); + + break; + + case "notify-detached": + // TODO: Send a notification through asyncMessage to the keyboard here. + break; + + // TODO: Manage _focusedElement for text input from here instead. + // (except for has a nested anonymous element that + // takes focus on behalf of the number control when someone tries to focus + // the number control. If |element| is such an anonymous text control then we + // need it's number control here in order to get the correct 'type' etc.: + element = element.ownerNumberControl || element; + + let type = element.tagName.toLowerCase(); + let inputType = (element.type || "").toLowerCase(); + let value = element.value || ""; + let max = element.max || ""; + let min = element.min || ""; + + // Treat contenteditable element as a special text area field + if (isContentEditable(element)) { + type = "contenteditable"; + inputType = "textarea"; + value = getContentEditableText(element); + } + + // Until the input type=date/datetime/range have been implemented + // let's return their real type even if the platform returns 'text' + let attributeInputType = element.getAttribute("type") || ""; + + if (attributeInputType) { + let inputTypeLowerCase = attributeInputType.toLowerCase(); + switch (inputTypeLowerCase) { + case "datetime": + case "datetime-local": + case "month": + case "week": + case "range": + inputType = inputTypeLowerCase; + break; + } + } + + // Gecko has some support for @inputmode but behind a preference and + // it is disabled by default. + // Gaia is then using @x-inputmode has its proprietary way to set + // inputmode for fields. This shouldn't be used outside of pre-installed + // apps because the attribute is going to disappear as soon as a definitive + // solution will be find. + let inputMode = element.getAttribute('x-inputmode'); + if (inputMode) { + inputMode = inputMode.toLowerCase(); + } else { + inputMode = ''; + } + + let range = getSelectionRange(element); + + return { + "contextId": focusCounter, + + "type": type, + "inputType": inputType, + "inputMode": inputMode, + + "choices": getListForElement(element), + "value": value, + "selectionStart": range[0], + "selectionEnd": range[1], + "max": max, + "min": min, + "lang": element.lang || "" + }; +} + +function getListForElement(element) { + if (!(element instanceof HTMLSelectElement)) + return null; + + let optionIndex = 0; + let result = { + "multiple": element.multiple, + "choices": [] + }; + + // Build up a flat JSON array of the choices. + // In HTML, it's possible for select element choices to be under a + // group header (but not recursively). We distinguish between headers + // and entries using the boolean "list.group". + let children = element.children; + for (let i = 0; i < children.length; i++) { + let child = children[i]; + + if (child instanceof HTMLOptGroupElement) { + result.choices.push({ + "group": true, + "text": child.label || child.firstChild.data, + "disabled": child.disabled + }); + + let subchildren = child.children; + for (let j = 0; j < subchildren.length; j++) { + let subchild = subchildren[j]; + result.choices.push({ + "group": false, + "inGroup": true, + "text": subchild.text, + "disabled": child.disabled || subchild.disabled, + "selected": subchild.selected, + "optionIndex": optionIndex++ + }); + } + } else if (child instanceof HTMLOptionElement) { + result.choices.push({ + "group": false, + "inGroup": false, + "text": child.text, + "disabled": child.disabled, + "selected": child.selected, + "optionIndex": optionIndex++ + }); + } + } + + return result; +}; + +// Create a plain text document encode from the focused element. +function getDocumentEncoder(element) { + let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"] + .createInstance(Ci.nsIDocumentEncoder); + let flags = Ci.nsIDocumentEncoder.SkipInvisibleContent | + Ci.nsIDocumentEncoder.OutputRaw | + Ci.nsIDocumentEncoder.OutputDropInvisibleBreak | + // Bug 902847. Don't trim trailing spaces of a line. + Ci.nsIDocumentEncoder.OutputDontRemoveLineEndingSpaces | + Ci.nsIDocumentEncoder.OutputLFLineBreak | + Ci.nsIDocumentEncoder.OutputNonTextContentAsPlaceholder; + encoder.init(element.ownerDocument, "text/plain", flags); + return encoder; +} + +// Get the visible content text of a content editable element +function getContentEditableText(element) { + if (!element || !isContentEditable(element)) { + return null; + } + + let doc = element.ownerDocument; + let range = doc.createRange(); + range.selectNodeContents(element); + let encoder = FormAssistant.documentEncoder; + encoder.setRange(range); + return encoder.encodeToString(); +} + +function getSelectionRange(element) { + let start = 0; + let end = 0; + if (isPlainTextField(element)) { + // Get the selection range of and