Files
palemoon27/dom/inputmethod/forms.js
T
roytam1 09bea9b394 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1215921 - Range Analysis: Recover observable operands if they are recoverable. r=jandem,sunfish (5d0387ba66)
- Bug 1221923 - Remove pointless "initialized" static in VMFunction::addToFunctions; r=Waldo (fbf61fff54)
- Bug 12228397 - Bail out when dividing large unsigned integer by one. r=sunfish (cbd9d34bcf)
- Bug 1229292 - IonMonkey: MIPS64: Fix skip nops in conditional branches. r=huangwenjun06 (2244600084)
- Bug 1090957 - IonMonkey: MIPS: Implement atomics in MacroAssembler. r=lth (29399e4a33)
- Bug 1229989 - IonMonkey: MIPS: Fix patchCall for mips32. r=luke (1ae6f20686)
- Bug 1213747 - IonMonkey: MIPS: Import MIPS64 support into MoveEmitter-mips-shared. r=nbp f=rankov (16d46fe32e)
- Bug 1233863 - ARM64: Record the correct branch offset. r=sstangl (b2f940056e)
- Bug 1233884 - Remove unused CompactBufferWriters from MozBaseAssembler. r=jolesen (e37860be67)
- Bug 1233863 - ARM64: Avoid BumpSystemStackPointer(). r=sstangl (5c06e601c1)
- Bug 1229802 - Simplify MacroAssemblerARMCompat::decBranchPtr. r=nbp (a908c62e5b)
- bit of Bug 1217515 - Clean up the way touch event support is configured (2cfd0e4c65)
- Bug 1221459 - Remove SelectionStateChangedEvent. r=mtseng, r=smaug (9a827ab9fc)
- Bug 1209479 - Map Android back button to nsIWebNavigation::GoBack() for the current active frame r=fabrice (be41e7b8da)
- Bug 1165841 - Part 1: add window into nsINfcEventListener. r=dimi (475a56ab28)
- Bug 1165841 - Part 2: move window-related code to nsNfc.js. r=dimi (e2a0487b75)
- Bug 1165841 - Part 3: remove _rfState and init from NfcContentHelper. r=dimi (b8be95012d)
- Bug 1165841 - Part 4: using eventListeners. r=dimi (a22ff6d56c)
- Bug 1165841 - Part 5: remove listener when window is destroyed. r=dimi (48716e2ba1)
- Bug 1168292 - Part 1: update SYSTEM_APP_ID. r=dimi (ecdbbefb03)
- Bug 1168292 - Part 2: notify listener with correct tabId. r=dimi (4fb26c705e)
- Bug 1168292 - Part 3: add tabId in notifyUserAccepted. r=dimi (1b36a15b65)
- Bug 1170053 - [Aries] Not able to share a picture using NFC. r=yoshi (0b80df2866)
- Bug 1172159 - [NFC] Introduce getFocusTabId function. r=allstars.chh (b917d0dac7)
- Bug 1168704 - Rename setFocusApp in nslNfcContentHelper. r=yoshi (9918707442)
- more missing of Bug 1209479 (6480ccc315)
- Bug 1166210 - Remove ServicesSetting changes listener in NfcContentHelper. r=yoshi (6e8f388637)
- Bug 1121900 - [flatfish] build break by "error: undefined reference to 'mozilla::dom::MozIsoDepTech::mTechnology'". r=yoshi (b5aaaada08)
- Bug 976457 - B2G NFC: API for supporting NFC_A tags. r=yoshi r=smaug (b908a5d60a)
- Bug 1156208 - this.nfc.getErrorMessage is not a function. r=dimi (42e33fcfc9)
- Bug 1225701 - Add comments in TouchCaret and SelectionCarets for their obsolescence. r=mtseng (ba85234c0e)
- Bug 1210341 - (v3) Reduce accessiblecaret size. r=TYLin Bug 1221459 - Remove TouchCaret and SelectionCarets. r=mtseng, r=roc (ad6282ff96)
- Bug 1216986 - Fix usage of nsIURI.host in password manager and prompt code to support IPv6. r=dolske,liuche,kanru (cec90a6251)
- Bug 962249 part 1 - Add Element.scroll{Top,Left}Min (chrome-only) and convert most of scrollLeftMax uses and part of scrollTopMax uses to combinations with the new properties. r=roc,bz (13ed8ee58f)
- Bug 962249 part 3 - Add Window.scrollMin{X,Y} (chrome-only) and conve… (b9b6e6aa8f)
- Bug 918771 - Part 1: Move Fragment Serialization logic into nsContentUtils::SerializeNodeToMarkup, r=bz (865ea1d334)
- Bug 918771 - Part 2: Send text/html as MIME type for XHR send() of HTML document, r=bz (129b8d4115)
- Bug 918771 - Part 3: Update Web Platform tests to check for correct behavior, r=bz (ed0536f5b8)
- Bug 1085284 - Implement URLSearchParams iterable<>, r=bz (db1edd6d74)
- Bug 1224643. Fix handling of U+0000 in URLSearchParams serialization. r=baku (55856e5353)
- Bug 1224580. Copy of URLSearchParams should not copy its observer pointer. r=baku (1a5d859de5)
- Bug 1153145 - Ensure trusted focus events from web content focus plugins. r=smaug (d79fb35d4b)
- Bug 1183901, properly support WrapperCache on DistributedContentList, r=wchen (f15879c8ca)
- Bug 1188887: P1. Allow seeking when readyState is HAVE_NOTHING. r=jwwang (aa33721a44)
- Bug 1188887: P2. Add mochitest testing new seek behavior. r=jwwang (0520088343)
- Bug 1232223 - Part 6: Remove function prototype for undefined function IsAudioAPIEnabled(). r=jya (5126eb83ed)
- Bug 1232223 - Part 5: Run wave tests unconditionally. r=jya (668e6b9b77)
- Bug 1232223 - Part 1: Remove MOZ_WEBM #ifdefs because WebM is always supported, though not necessarily enabled. r=jya r=glandium (b3ec44f5bc)
- Bug 1232223 - Part 2: Remove MOZ_VPX #ifdefs because VPx is always supported, though not necessarily enabled. r=jya (c8853a16c4)
- Bug 1232223 - Part 3: Remove MOZ_WAVE #ifdefs because Wave is always supported. r=jya (e5c18f912c)
- Bug 1232223 - Part 4: Run webm tests unconditionally. r=jya (a81ab83b00)
- fix (9d9acad7f8)
- bug 1169640 - use nsIPrompt instead of nsIPromptService for insecure form submission r=mrbkap (1091d0d839)
- Bug 1231919 - crash in mozilla::a11y::DocAccessible::ValidateARIAOwned, r=davidb (3466d77e14)
- bug 1172538 - make sure a document has an IPC actor before shutting it down r=davidb, lsocks (823a447f7f)
- Bug 1147646 - Crash in mozilla::a11y::DocAccessible::ProcessContentInserted while stability testing, r=davidb (c9e4e7ef2f)
- Bug 1230118 - added asserts on aOwner and aOwner->Elm() r=surkov (94295ffd61)
- Bug 1213281 - crash in mozilla::a11y::DocAccessible::UpdateTreeOnInsertion, r=davidb (6442f927cd)
- bug 1196460 - add method to get wrapper of proxy for document containing this proxied accessible r=surkov (0cc7ff8758)
- Bug 1222174 - add override declarations in AccessibleWrap.h; r=tbsaunde (a8c1d0d3f3)
- bug 1187055 - check the proxy being destroyed has a wrapper before cleaning it up r=davidb (b73966446c)
- bug 1196460 - remove proxy's ids when they are destroyed r=surkov (3cbc9a8536)
- bug 1196372 - make GetHWNDFor() work with proxied accessibles r=surkov (28a6057913)
- Bug 1196460 - teach GetChildIDFor() to deal with proxied accessibles r=surkov (4746bfa128)
- bug 1199735 - remove event logging from the windows AccessibleWrap::HandleAccEvent r=davidb (869e18d13f)
- bug 1199735 - factor win event dispatch logic into its own function r=davidb (0a9704aa9d)
- bug 1196460 - create different proxy wrappers depending on the type of the proxy r=surkov (cd2d18ebdc)
- bug 1213327 - store interfaces a proxy implements on the Accessible wrapping it (406c9a3715)
- bug 1213606 - work around proxy's that don't have a wrapper for their document r=davidb (252a3ff2e7)
- bug 1192330 - update text change data for proxied text change events r=surkov (4deb371062)
- bug 1199735 - fire windows events on proxies r=davidb (6400387e71)
- bug 1186536 - Bail out of ProxyTextChangeEvent() if the proxy doesn't have a wrapper r=davidb (b40f69c77b)
- bug 1207862 - make WrapperFor() take a const ProxyAccessible * r=davidb (8e79dc6d75)
- bug 1213402 - fixup silly array , on windows r=me landing on a CLOSED TREE (33368af3af)
- bug 606080 - add class to generate unique 32 bit ids r=froydnj (ebf0888dcc)
- Bug 1231148 - Avoid requiring IDL files installed in $DIST/idl to run AccEventGen.py. r=ted (22078343ce)
- Bug 1229838 - rel=apple-touch-icon-precomposed should fire mozbrowsericonchange. r=fabrice (2a3e93da96)
- Bug 1231040 - Check for premulting better. - r=jrmuizel (811609bbb3)
- Bug 1224475 - Start agent after seeking. r=baku (a009b58f2c)
- Bug 1231557 - Use the URL Classifier in Media elements. r=cpearce,r=gcp (9f5503581d)
- bug 1228484 throw instead of capturing a second stream to a different graph r=roc (e762799424)
- Bug 1198435 - MediaElementTableCount now checks that the element is not present for URIs other than the expected one. r=rillian (838b1b886b)
- Bug 1224991 - Log format string mismatch the number of parameters. r=cpearce. (9cd2770bd2)
- Bug 1144409 - Encrypted event should be fired once per initData. r=cpearce (e50b380feb)
- Bug 1213589 part.1 Make ContentEventHandler::GetTextLength() and GetNativeTextLength() called only with a text node r=smaug (10955440a9)
- Bug 1213589 part.2 Clean up GenerateFlatTextContent(), GelerateFlatFontRanges() and GetFlatTextOffsetOfRange() of ContentEventHandler r=smaug (3f6bf987ff)
- Bug 1213589 part.3 ContentEventHandler::GetFlatTextLengthInRange() should handle specially when it's called by nsIMutationObserver::ContentRemoved() r=smaug (ed03499803)
- Bug 1213859 part.4 ContentEventHandler should use NS_NewPreContentIterator rathr than NS_NewContentIterator at converting between DOM tree and flattened text r=smaug (fe6e3b1114)
- Bug 1213589 part.5 Redesign the rules to create range in ContentEventHandler::SetRangeFromFlatTextOffset() r=smaug (edaa7baf87)
- Bug 1213589 part.6 ContentEventHandler should insert line breaks at open tag of elements except non-replaced inline elements r=smaug (c2e9cc11dc)
- Bug 1213589 part.7 Add new testcases to runSetSelectionEventTest() and runQueryTextContentEventTest() for checking the behavior with open tag r=smaug (bcea465e5e)
- Bug 1213589 part.8 When there are no nodes causing text, ContentEventHandler should set start of the editor root to start of the range r=smaug (a6d6b4d8d5)
- Bug 1213589 part.9 ContentEventHandler::ShouldBreakLineBefore() should return false if the content is unknown HTML element r=smaug (960936477f)
- Bug 1214164 - Don't honor all <option> descendants of <select>; r=bz (188bfe2b89)
- Bug 1228876. Correctly ignore nested optgroups even if an optgroup is inserted into another, existing, optgroup. r=mats (f2058c8b68)
- Bug 1193637 - Use IsMap() in HTMLImageElement::PreHandleEvent; r=mrbkap (6f2ef9da61)
- Bug 1160819 - ResponsiveImageSelector - improve some over-aggressive assertions (b5d40119ac)
- Bug 1233259, only in-document images should respond to viewport changes, r=mystor (eb448e258c)
- Bug 773429 - Remove dom.disable_image_src_set. r=khuey (fa06677726)
- Bug 1230110 - HTMLImageElement should call its superclass's DestroyContent(). r=smaug (bac1d12c65)
- Bug 1231245 - Fix logging of Matrix4x4. r=botond (f5c93b67cb)
- Bug 1208661 - Remove some no-longer-used debugging code. r=BenWa (2c86e3efd2)
- Bug 1228602 - Ensure that we pick up the presShell resolution from the layer if there isn't a metrics. r=botond (3c1835a022)
- Annotate FrameMetrics that are created for ScrollInfoLayers. (bug 1192919 part 1, r=kats) (5be0234333)
- Minimize the displayport for frames that must animate synchronously.  (bug 1192919 part 2, r=kats) (5abc63e72d)
- Force a paint to be scheduled for scroll info layers. (bug 1192919 part 3, r=kats) (4ed61248c7)
- Bug 1141127 - When inside a slop area around the first touchstart, prevent touchmove events from going to content. r=botond (18fa91d74e)
- Bug 1174532 - Even if the APZC is not pannable, have a small slop area to consume touchmove events. r=botond (ff8e6f46fe)
- Bug 1208636 - Adjust displayport size based on available system memory. r=kats (d1ff9798b2)
- Bug 1232094 - Make some APZ prefs live. r=botond (d45207bc5b)
- Bug 1231168 - Disable memory-based displayport size adjustment by default. r=mchang (c17b445171)
- Turn apz.use_paint_duration off. (bug 1192919 part 4, r=kats) (dc094c9617)
- Bug 1170325 - Undo an unnecessary signature change to AsyncPanZoomAnimation::TakeDeferredTasks(). r=Waldo (62eafb38ef)
- Bug 1216355 - Ensure that if a repaint request is ignored due to a stale generation number the next repaint request doesn't get dropped. r=botond (c070172b48)
- Bug 1228407 - Rearrange some code by extracting a helper function. r=botond (ae53af7449)
- Bug 1228407 - Update existing smooth scroll animations with new destinations when possible, rather than canceling and restarting them. r=kip (c15022d920)
- Dispatch all APZ repaint requests immediately, and rely on content vsync. (bug 1192919 part 5, r=kats) (53ea2674c8)
- Bug 1230552 - Update some out-of-date comments and remove an old #undef. r=kats (16ba5a82e1)
- Bug 1230552 - Introduce a structure to group fling handoff state. r=kats (4f98893742)
- Bug 1230552 - Extend the immediate scroll handoff pref to apply to flings. r=kats (6b3d4fb0e5)
- Bug 1231228 - Correctly check for whether an APZC along the handoff chain is panned into overscroll. r=kats (bbaeb881af)
- Bug 1226920 - For instances of ForEachNode where we want to traverse the entire tree, use an action that returns void. r=botond (e42de54aea)
- Bug 1230552 - Make immediate scroll handoff for panning prefable. r=kats (89ddb8853a)
- Bug 1230552 - Fuzz a floating-point comparison to avoid an intermittent test failure. r=bustage (839bee076d)
- Bug 1231228 - Gtest. r=kats (e141ee97ef)
- Bug 1208973 - Move function bodies into .cpp file, and remove an unused one. r=botond (ef89b08738)
- Bug 1208973 - Refactor and add logging to TaskThrottler. r=botond (0a88091e20)
- Bug 1208973 - Ensure all code in TaskThrottler is threadsafe since it gets called from various threads. r=botond (9735621fd1)
- Bug 1213273 - Use a chromium Task instead of an nsITimer for the timeout in TaskThrottler. r=mstange (6cc9a9369e)
- Bug 1217251 - In TaskThrottler, dispatch the timeout task correctly from the Java UI thread on Android. r=kats (ee64d9458e)
- Bug 1230552 - Const-correctness improvements. r=kats (cc56f3404a)
- Bug 1230552 - Introduce a helper AsyncPanZoomController::CurrentInputBlock(). r=kats (4cef12a744)
- Remove the TaskThrottler. (bug 1192919 part 6, r=kats) (534312c146)
- Bug 1169695 - Advance the stored time when sampling the transform for a future frame. r=botod (3b08c07827)
- Bug 1169695 - Remove as many manual calls to spin the task queue as possible. r=botond (80c390ec7a)
- Bug 1218618 - Assert that the hit-testing tree's root node doesn't have siblings. r=kats (95ec1d07a7)
- Bug 1225178 - Don't start APZ drag if we don't have a displayport to handle it. r=kats (c29d4f962d)
- bit of  End APZ wheel transactions when the mouse moves out of frame. (bug 1142866 part 2, r=kats,botond) (5a0c79d052)
- Bug 1226320 - Fix a refactoring in APZCTreeManager that didn't preserve semantics correctly. r=kats (35b92bd4a1)
2023-06-27 11:54:26 +08:00

1605 lines
48 KiB
JavaScript

/* -*- 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 <window, object> 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 <br>, 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 "VolumeMute";
case KeyboardEvent.DOM_VK_VOLUME_DOWN:
return "VolumeDown";
case KeyboardEvent.DOM_VK_VOLUME_UP:
return "VolumeUp";
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 <select> which will be need to handled elsewhere)
case "notify-focus":
break;
case "notify-blur":
break;
}
} catch (e) {
return false;
}
return true;
}
};
var FormAssistant = {
init: function fa_init() {
addEventListener("focus", this, true, false);
addEventListener("blur", this, true, false);
addEventListener("resize", this, true, false);
addEventListener("submit", this, true, false);
addEventListener("pagehide", this, true, false);
addEventListener("beforeunload", this, true, false);
addEventListener("input", this, true, false);
addEventListener("keydown", this, true, false);
addEventListener("keyup", this, true, false);
addMessageListener("Forms:Select:Choice", this);
addMessageListener("Forms:Input:Value", this);
addMessageListener("Forms:Select:Blur", this);
addMessageListener("Forms:SetSelectionRange", this);
addMessageListener("Forms:ReplaceSurroundingText", this);
addMessageListener("Forms:GetText", this);
addMessageListener("Forms:Input:SendKey", this);
addMessageListener("Forms:GetContext", this);
addMessageListener("Forms:SetComposition", this);
addMessageListener("Forms:EndComposition", this);
},
ignoredInputTypes: new Set([
'button', 'file', 'checkbox', 'radio', 'reset', 'submit', 'image',
'range'
]),
isHandlingFocus: false,
selectionStart: -1,
selectionEnd: -1,
textBeforeCursor: "",
textAfterCursor: "",
scrollIntoViewTimeout: null,
_focusedElement: null,
_focusCounter: 0, // up one for every time we focus a new element
_focusDeleteObserver: null,
_focusContentObserver: null,
_documentEncoder: null,
_editor: null,
_editing: false,
_selectionPrivate: null,
get focusedElement() {
if (this._focusedElement && Cu.isDeadWrapper(this._focusedElement))
this._focusedElement = null;
return this._focusedElement;
},
set focusedElement(val) {
this._focusCounter++;
this._focusedElement = val;
},
setFocusedElement: function fa_setFocusedElement(element) {
let self = this;
if (element === this.focusedElement)
return;
if (this.focusedElement) {
this.focusedElement.removeEventListener('compositionend', this);
if (this._focusDeleteObserver) {
this._focusDeleteObserver.disconnect();
this._focusDeleteObserver = null;
}
if (this._focusContentObserver) {
this._focusContentObserver.disconnect();
this._focusContentObserver = null;
}
if (this._selectionPrivate) {
this._selectionPrivate.removeSelectionListener(this);
this._selectionPrivate = null;
}
}
this._documentEncoder = null;
if (this._editor) {
// When the nsIFrame of the input element is reconstructed by
// CSS restyling, the editor observers are removed. Catch
// [nsIEditor.removeEditorObserver] failure exception if that
// happens.
try {
this._editor.removeEditorObserver(this);
} catch (e) {}
this._editor = null;
}
if (element) {
element.addEventListener('compositionend', this);
if (isContentEditable(element)) {
this._documentEncoder = getDocumentEncoder(element);
}
this._editor = getPlaintextEditor(element);
if (this._editor) {
// Add a nsIEditorObserver to monitor the text content of the focused
// element.
this._editor.addEditorObserver(this);
let selection = this._editor.selection;
if (selection) {
this._selectionPrivate = selection.QueryInterface(Ci.nsISelectionPrivate);
this._selectionPrivate.addSelectionListener(this);
}
}
// If our focusedElement is removed from DOM we want to handle it properly
let MutationObserver = element.ownerDocument.defaultView.MutationObserver;
this._focusDeleteObserver = new MutationObserver(function(mutations) {
var del = [].some.call(mutations, function(m) {
return [].some.call(m.removedNodes, function(n) {
return n.contains(element);
});
});
if (del && element === self.focusedElement) {
self.unhandleFocus();
self.selectionStart = -1;
self.selectionEnd = -1;
}
});
this._focusDeleteObserver.observe(element.ownerDocument.body, {
childList: true,
subtree: true
});
// If contenteditable, also add a mutation observer on its content and
// call selectionChanged when a change occurs
if (isContentEditable(element)) {
this._focusContentObserver = new MutationObserver(function() {
this.updateSelection();
}.bind(this));
this._focusContentObserver.observe(element, {
childList: true,
subtree: true
});
}
}
this.focusedElement = element;
},
notifySelectionChanged: function(aDocument, aSelection, aReason) {
this.updateSelection();
},
get documentEncoder() {
return this._documentEncoder;
},
// Get the nsIPlaintextEditor object of current input field.
get editor() {
return this._editor;
},
// Implements nsIEditorObserver get notification when the text content of
// current input field has changed.
EditAction: function fa_editAction() {
if (this._editing || !this.isHandlingFocus) {
return;
}
this.sendInputState(this.focusedElement);
},
handleEvent: function fa_handleEvent(evt) {
let target = evt.composedTarget;
let range = null;
switch (evt.type) {
case "focus":
if (!target) {
break;
}
// Focusing on Window, Document or iFrame should focus body
if (target instanceof HTMLHtmlElement) {
target = target.document.body;
} else if (target instanceof HTMLDocument) {
target = target.body;
} else if (target instanceof HTMLIFrameElement) {
target = target.contentDocument ? target.contentDocument.body
: null;
}
if (!target) {
break;
}
if (isContentEditable(target)) {
this.handleFocus(this.getTopLevelEditable(target));
this.updateSelection();
break;
}
if (this.isFocusableElement(target)) {
this.handleFocus(target);
this.updateSelection();
}
break;
case "pagehide":
case "beforeunload":
// We are only interested to the pagehide and beforeunload events from
// the root document.
if (target && target != content.document) {
break;
}
// fall through
case "submit":
if (this.focusedElement) {
this.focusedElement.blur();
}
break;
case "blur":
if (this.focusedElement) {
this.unhandleFocus();
this.selectionStart = -1;
this.selectionEnd = -1;
}
break;
case "resize":
if (!this.isHandlingFocus)
return;
if (this.scrollIntoViewTimeout) {
content.clearTimeout(this.scrollIntoViewTimeout);
this.scrollIntoViewTimeout = null;
}
// We may receive multiple resize events in quick succession, so wait
// a bit before scrolling the input element into view.
if (this.focusedElement) {
this.scrollIntoViewTimeout = content.setTimeout(function () {
this.scrollIntoViewTimeout = null;
if (this.focusedElement && !FormVisibility.isVisible(this.focusedElement)) {
scrollSelectionOrElementIntoView(this.focusedElement);
}
}.bind(this), RESIZE_SCROLL_DELAY);
}
break;
case "keydown":
if (!this.focusedElement || this._editing) {
break;
}
CompositionManager.endComposition('');
break;
case "keyup":
if (!this.focusedElement || this._editing) {
break;
}
CompositionManager.endComposition('');
break;
case "compositionend":
if (!this.focusedElement) {
break;
}
CompositionManager.onCompositionEnd();
break;
}
},
receiveMessage: function fa_receiveMessage(msg) {
let target = this.focusedElement;
let json = msg.json;
// To not break mozKeyboard contextId is optional
if ('contextId' in json &&
json.contextId !== this._focusCounter &&
json.requestId) {
// Ignore messages that are meant for a previously focused element
sendAsyncMessage("Forms:SequenceError", {
requestId: json.requestId,
error: "Expected contextId " + this._focusCounter +
" but was " + json.contextId
});
return;
}
if (!target) {
switch (msg.name) {
case "Forms:GetText":
sendAsyncMessage("Forms:GetText:Result:Error", {
requestId: json.requestId,
error: "No focused element"
});
break;
}
return;
}
this._editing = true;
switch (msg.name) {
case "Forms:Input:Value": {
CompositionManager.endComposition('');
target.value = json.value;
let event = target.ownerDocument.createEvent('HTMLEvents');
event.initEvent('input', true, false);
target.dispatchEvent(event);
break;
}
case "Forms:Input:SendKey":
CompositionManager.endComposition('');
let win = target.ownerDocument.defaultView;
let tip = WindowMap.getTextInputProcessor(win);
if (!tip) {
if (json.requestId) {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "Unable to start input transaction."
});
}
break;
}
// If we receive a keyboardEventDict from json, that means the user
// is calling the method with the new arguments.
// Otherwise, we would have to construct our own keyboardEventDict
// based on legacy values we have received.
let keyboardEventDict = json.keyboardEventDict;
let flags = 0;
if (keyboardEventDict) {
if ('flags' in keyboardEventDict) {
flags = keyboardEventDict.flags;
}
} else {
// The naive way to figure out if the key to dispatch is printable.
let printable = !!json.charCode;
// For printable keys, the value should be the actual character.
// For non-printable keys, it should be a value in the D3E spec.
// Here we make some educated guess for it.
let key = printable ?
String.fromCharCode(json.charCode) :
guessKeyNameFromKeyCode(win.KeyboardEvent, json.keyCode);
// keyCode from content is only respected when the key is not an
// an alphanumeric character. We also ask TextInputProcessor not to
// infer this value for non-printable keys to keep the original
// behavior.
let keyCode = (printable && /^[a-zA-Z0-9]$/.test(key)) ?
key.toUpperCase().charCodeAt(0) :
json.keyCode;
keyboardEventDict = {
key: key,
keyCode: keyCode,
// We don't have any information to tell the virtual key the
// user have interacted with.
code: "",
// We do not have the information to infer location of the virtual key
// either (and we would need TextInputProcessor not to compute it).
location: 0,
// This indicates the key is triggered for repeats.
repeat: json.repeat
};
flags = tip.KEY_KEEP_KEY_LOCATION_STANDARD;
if (!printable) {
flags |= tip.KEY_NON_PRINTABLE_KEY;
}
if (!keyboardEventDict.keyCode) {
flags |= tip.KEY_KEEP_KEYCODE_ZERO;
}
}
let keyboardEvent = new win.KeyboardEvent("", keyboardEventDict);
let keydownDefaultPrevented = false;
try {
switch (json.method) {
case 'sendKey': {
let consumedFlags = tip.keydown(keyboardEvent, flags);
keydownDefaultPrevented =
!!(tip.KEYDOWN_IS_CONSUMED & consumedFlags);
if (!keyboardEventDict.repeat) {
tip.keyup(keyboardEvent, flags);
}
break;
}
case 'keydown': {
let consumedFlags = tip.keydown(keyboardEvent, flags);
keydownDefaultPrevented =
!!(tip.KEYDOWN_IS_CONSUMED & consumedFlags);
break;
}
case 'keyup': {
tip.keyup(keyboardEvent, flags);
break;
}
}
} catch (err) {
dump("forms.js:" + err.toString() + "\n");
if (json.requestId) {
if (err instanceof Ci.nsIException &&
err.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "The values specified are illegal."
});
} else {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "Unable to type into destroyed input."
});
}
}
break;
}
if (json.requestId) {
if (keydownDefaultPrevented) {
sendAsyncMessage("Forms:SendKey:Result:Error", {
requestId: json.requestId,
error: "Key event(s) was cancelled."
});
} else {
sendAsyncMessage("Forms:SendKey:Result:OK", {
requestId: json.requestId,
selectioninfo: this.getSelectionInfo()
});
}
}
break;
case "Forms:Select:Choice":
let options = target.options;
let valueChanged = false;
if ("index" in json) {
if (options.selectedIndex != json.index) {
options.selectedIndex = json.index;
valueChanged = true;
}
} else if ("indexes" in json) {
for (let i = 0; i < options.length; i++) {
let newValue = (json.indexes.indexOf(i) != -1);
if (options.item(i).selected != newValue) {
options.item(i).selected = newValue;
valueChanged = true;
}
}
}
// only fire onchange event if any selected option is changed
if (valueChanged) {
let event = target.ownerDocument.createEvent('HTMLEvents');
event.initEvent('change', true, true);
target.dispatchEvent(event);
}
break;
case "Forms:Select:Blur": {
if (this.focusedElement) {
this.focusedElement.blur();
}
break;
}
case "Forms:SetSelectionRange": {
CompositionManager.endComposition('');
let start = json.selectionStart;
let end = json.selectionEnd;
if (!setSelectionRange(target, start, end)) {
if (json.requestId) {
sendAsyncMessage("Forms:SetSelectionRange:Result:Error", {
requestId: json.requestId,
error: "failed"
});
}
break;
}
if (json.requestId) {
sendAsyncMessage("Forms:SetSelectionRange:Result:OK", {
requestId: json.requestId,
selectioninfo: this.getSelectionInfo()
});
}
break;
}
case "Forms:ReplaceSurroundingText": {
CompositionManager.endComposition('');
if (!replaceSurroundingText(target,
json.text,
json.offset,
json.length)) {
if (json.requestId) {
sendAsyncMessage("Forms:ReplaceSurroundingText:Result:Error", {
requestId: json.requestId,
error: "failed"
});
}
break;
}
if (json.requestId) {
sendAsyncMessage("Forms:ReplaceSurroundingText:Result:OK", {
requestId: json.requestId,
selectioninfo: this.getSelectionInfo()
});
}
break;
}
case "Forms:GetText": {
let value = isContentEditable(target) ? getContentEditableText(target)
: target.value;
if (json.offset && json.length) {
value = value.substr(json.offset, json.length);
}
else if (json.offset) {
value = value.substr(json.offset);
}
sendAsyncMessage("Forms:GetText:Result:OK", {
requestId: json.requestId,
text: value
});
break;
}
case "Forms:GetContext": {
let obj = getJSON(target, this._focusCounter);
sendAsyncMessage("Forms:GetContext:Result:OK", obj);
break;
}
case "Forms:SetComposition": {
CompositionManager.setComposition(target, json.text, json.cursor,
json.clauses, json.keyboardEventDict);
sendAsyncMessage("Forms:SetComposition:Result:OK", {
requestId: json.requestId,
selectioninfo: this.getSelectionInfo()
});
break;
}
case "Forms:EndComposition": {
CompositionManager.endComposition(json.text, json.keyboardEventDict);
sendAsyncMessage("Forms:EndComposition:Result:OK", {
requestId: json.requestId,
selectioninfo: this.getSelectionInfo()
});
break;
}
}
this._editing = false;
},
handleFocus: function fa_handleFocus(target) {
if (this.focusedElement === target)
return;
if (target instanceof HTMLOptionElement)
target = target.parentNode;
this.setFocusedElement(target);
this.sendInputState(target);
this.isHandlingFocus = true;
},
unhandleFocus: function fa_unhandleFocus() {
this.setFocusedElement(null);
this.isHandlingFocus = false;
sendAsyncMessage("Forms:Input", { "type": "blur" });
},
isFocusableElement: function fa_isFocusableElement(element) {
if (element instanceof HTMLSelectElement ||
element instanceof HTMLTextAreaElement)
return true;
if (element instanceof HTMLOptionElement &&
element.parentNode instanceof HTMLSelectElement)
return true;
return (element instanceof HTMLInputElement &&
!this.ignoredInputTypes.has(element.type) &&
!element.readOnly);
},
getTopLevelEditable: function fa_getTopLevelEditable(element) {
function retrieveTopLevelEditable(element) {
while (element && !isContentEditable(element))
element = element.parentNode;
return element;
}
return retrieveTopLevelEditable(element) || element;
},
sendInputState: function(element) {
sendAsyncMessage("Forms:Input", getJSON(element, this._focusCounter));
},
getSelectionInfo: function fa_getSelectionInfo() {
let element = this.focusedElement;
let range = getSelectionRange(element);
let text = isContentEditable(element) ? getContentEditableText(element)
: element.value;
let textAround = getTextAroundCursor(text, range);
let changed = this.selectionStart !== range[0] ||
this.selectionEnd !== range[1] ||
this.textBeforeCursor !== textAround.before ||
this.textAfterCursor !== textAround.after;
this.selectionStart = range[0];
this.selectionEnd = range[1];
this.textBeforeCursor = textAround.before;
this.textAfterCursor = textAround.after;
return {
selectionStart: range[0],
selectionEnd: range[1],
textBeforeCursor: textAround.before,
textAfterCursor: textAround.after,
changed: changed
};
},
_selectionTimeout: null,
// Notify when the selection range changes
updateSelection: function fa_updateSelection() {
// A call to setSelectionRange on input field causes 2 selection changes
// one to [0,0] and one to actual value. Both are sent in same tick.
// Prevent firing two events in that scenario, always only use the last 1.
//
// It is also a workaround for Bug 1053048, which prevents
// getSelectionInfo() accessing selectionStart or selectionEnd in the
// callback function of nsISelectionListener::NotifySelectionChanged().
if (this._selectionTimeout) {
content.clearTimeout(this._selectionTimeout);
}
this._selectionTimeout = content.setTimeout(function() {
if (!this.focusedElement) {
return;
}
let selectionInfo = this.getSelectionInfo();
if (selectionInfo.changed) {
sendAsyncMessage("Forms:SelectionChange", selectionInfo);
}
}.bind(this), 0);
}
};
FormAssistant.init();
function isContentEditable(element) {
if (!element) {
return false;
}
if (element.isContentEditable || element.designMode == "on")
return true;
return element.ownerDocument && element.ownerDocument.designMode == "on";
}
function isPlainTextField(element) {
if (!element) {
return false;
}
return element instanceof HTMLTextAreaElement ||
(element instanceof HTMLInputElement &&
element.mozIsTextField(false));
}
function getJSON(element, focusCounter) {
// <input type=number> has a nested anonymous <input type=text> 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.type || "";
let value = element.value || "";
let max = element.max || "";
let min = element.min || "";
// Treat contenteditble element as a special text area field
if (isContentEditable(element)) {
type = "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 attributeType = element.getAttribute("type") || "";
if (attributeType) {
var typeLowerCase = attributeType.toLowerCase();
switch (typeLowerCase) {
case "datetime":
case "datetime-local":
case "range":
type = typeLowerCase;
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);
let textAround = getTextAroundCursor(value, range);
return {
"contextId": focusCounter,
"type": type.toLowerCase(),
"choices": getListForElement(element),
"value": value,
"inputmode": inputmode,
"selectionStart": range[0],
"selectionEnd": range[1],
"max": max,
"min": min,
"lang": element.lang || "",
"textBeforeCursor": textAround.before,
"textAfterCursor": textAround.after
};
}
function getTextAroundCursor(value, range) {
let textBeforeCursor = range[0] < 100 ?
value.substr(0, range[0]) :
value.substr(range[0] - 100, 100);
let textAfterCursor = range[1] + 100 > value.length ?
value.substr(range[0], value.length) :
value.substr(range[0], range[1] - range[0] + 100);
return {
before: textBeforeCursor,
after: textAfterCursor
};
}
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 <input> and <textarea> elements
start = element.selectionStart;
end = element.selectionEnd;
} else if (isContentEditable(element)){
// Get the selection range of contenteditable elements
let win = element.ownerDocument.defaultView;
let sel = win.getSelection();
if (sel && sel.rangeCount > 0) {
start = getContentEditableSelectionStart(element, sel);
end = start + getContentEditableSelectionLength(element, sel);
} else {
dump("Failed to get window.getSelection()\n");
}
}
return [start, end];
}
function getContentEditableSelectionStart(element, selection) {
let doc = element.ownerDocument;
let range = doc.createRange();
range.setStart(element, 0);
range.setEnd(selection.anchorNode, selection.anchorOffset);
let encoder = FormAssistant.documentEncoder;
encoder.setRange(range);
return encoder.encodeToString().length;
}
function getContentEditableSelectionLength(element, selection) {
let encoder = FormAssistant.documentEncoder;
encoder.setRange(selection.getRangeAt(0));
return encoder.encodeToString().length;
}
function setSelectionRange(element, start, end) {
let isTextField = isPlainTextField(element);
// Check the parameters
if (!isTextField && !isContentEditable(element)) {
// Skip HTMLOptionElement and HTMLSelectElement elements, as they don't
// support the operation of setSelectionRange
return false;
}
let text = isTextField ? element.value : getContentEditableText(element);
let length = text.length;
if (start < 0) {
start = 0;
}
if (end > length) {
end = length;
}
if (start > end) {
start = end;
}
if (isTextField) {
// Set the selection range of <input> and <textarea> elements
element.setSelectionRange(start, end, "forward");
return true;
} else {
// set the selection range of contenteditable elements
let win = element.ownerDocument.defaultView;
let sel = win.getSelection();
// Move the caret to the start position
sel.collapse(element, 0);
for (let i = 0; i < start; i++) {
sel.modify("move", "forward", "character");
}
// Avoid entering infinite loop in case we cannot change the selection
// range. See bug https://bugzilla.mozilla.org/show_bug.cgi?id=978918
let oldStart = getContentEditableSelectionStart(element, sel);
let counter = 0;
while (oldStart < start) {
sel.modify("move", "forward", "character");
let newStart = getContentEditableSelectionStart(element, sel);
if (oldStart == newStart) {
counter++;
if (counter > MAX_BLOCKED_COUNT) {
return false;
}
} else {
counter = 0;
oldStart = newStart;
}
}
// Extend the selection to the end position
for (let i = start; i < end; i++) {
sel.modify("extend", "forward", "character");
}
// Avoid entering infinite loop in case we cannot change the selection
// range. See bug https://bugzilla.mozilla.org/show_bug.cgi?id=978918
counter = 0;
let selectionLength = end - start;
let oldSelectionLength = getContentEditableSelectionLength(element, sel);
while (oldSelectionLength < selectionLength) {
sel.modify("extend", "forward", "character");
let newSelectionLength = getContentEditableSelectionLength(element, sel);
if (oldSelectionLength == newSelectionLength ) {
counter++;
if (counter > MAX_BLOCKED_COUNT) {
return false;
}
} else {
counter = 0;
oldSelectionLength = newSelectionLength;
}
}
return true;
}
}
/**
* Scroll the given element into view.
*
* Calls scrollSelectionIntoView for contentEditable elements.
*/
function scrollSelectionOrElementIntoView(element) {
let editor = getPlaintextEditor(element);
if (editor) {
editor.selectionController.scrollSelectionIntoView(
Ci.nsISelectionController.SELECTION_NORMAL,
Ci.nsISelectionController.SELECTION_FOCUS_REGION,
Ci.nsISelectionController.SCROLL_SYNCHRONOUS);
} else {
element.scrollIntoView(false);
}
}
// Get nsIPlaintextEditor object from an input field
function getPlaintextEditor(element) {
let editor = null;
// Get nsIEditor
if (isPlainTextField(element)) {
// Get from the <input> and <textarea> elements
editor = element.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
} else if (isContentEditable(element)) {
// Get from content editable element
let win = element.ownerDocument.defaultView;
let editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIEditingSession);
if (editingSession) {
editor = editingSession.getEditorForWindow(win);
}
}
if (editor) {
editor.QueryInterface(Ci.nsIPlaintextEditor);
}
return editor;
}
function replaceSurroundingText(element, text, offset, length) {
let editor = FormAssistant.editor;
if (!editor) {
return false;
}
// Check the parameters.
if (length < 0) {
length = 0;
}
// Change selection range before replacing. For content editable element,
// searching the node for setting selection range is not needed when the
// selection is collapsed within a text node.
let fastPathHit = false;
if (!isPlainTextField(element)) {
let sel = element.ownerDocument.defaultView.getSelection();
let node = sel.anchorNode;
if (sel.isCollapsed && node && node.nodeType == 3 /* TEXT_NODE */) {
let start = sel.anchorOffset + offset;
let end = start + length;
// Fallback to setSelectionRange() if the replacement span multiple nodes.
if (start >= 0 && end <= node.textContent.length) {
fastPathHit = true;
sel.collapse(node, start);
sel.extend(node, end);
}
}
}
if (!fastPathHit) {
let range = getSelectionRange(element);
let start = range[0] + offset;
if (start < 0) {
start = 0;
}
let end = start + length;
if (start != range[0] || end != range[1]) {
if (!setSelectionRange(element, start, end)) {
return false;
}
}
}
if (length) {
// Delete the selected text.
editor.deleteSelection(Ci.nsIEditor.ePrevious, Ci.nsIEditor.eStrip);
}
if (text) {
// We don't use CR but LF
// see https://bugzilla.mozilla.org/show_bug.cgi?id=902847
text = text.replace(/\r/g, '\n');
// Insert the text to be replaced with.
editor.insertText(text);
}
return true;
}
let CompositionManager = {
_isStarted: false,
_tip: null,
_KeyboardEventForWin: null,
_clauseAttrMap: {
'raw-input':
Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE,
'selected-raw-text':
Ci.nsITextInputProcessor.ATTR_SELECTED_RAW_CLAUSE,
'converted-text':
Ci.nsITextInputProcessor.ATTR_CONVERTED_CLAUSE,
'selected-converted-text':
Ci.nsITextInputProcessor.ATTR_SELECTED_CLAUSE
},
setComposition: function cm_setComposition(element, text, cursor, clauses, dict) {
// Check parameters.
if (!element) {
return;
}
let len = text.length;
if (cursor > len) {
cursor = len;
}
let clauseLens = [];
let clauseAttrs = [];
if (clauses) {
let remainingLength = len;
for (let i = 0; i < clauses.length; i++) {
if (clauses[i]) {
let clauseLength = clauses[i].length || 0;
// Make sure the total clauses length is not bigger than that of the
// composition string.
if (clauseLength > remainingLength) {
clauseLength = remainingLength;
}
remainingLength -= clauseLength;
clauseLens.push(clauseLength);
clauseAttrs.push(this._clauseAttrMap[clauses[i].selectionType] ||
Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE);
}
}
// If the total clauses length is less than that of the composition
// string, extend the last clause to the end of the composition string.
if (remainingLength > 0) {
clauseLens[clauseLens.length - 1] += remainingLength;
}
} else {
clauseLens.push(len);
clauseAttrs.push(Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE);
}
let win = element.ownerDocument.defaultView;
let tip = WindowMap.getTextInputProcessor(win);
if (!tip) {
return;
}
// Update the composing text.
tip.setPendingCompositionString(text);
for (var i = 0; i < clauseLens.length; i++) {
if (!clauseLens[i]) {
continue;
}
tip.appendClauseToPendingComposition(clauseLens[i], clauseAttrs[i]);
}
if (cursor >= 0) {
tip.setCaretInPendingComposition(cursor);
}
if (!dict) {
this._isStarted = tip.flushPendingComposition();
} else {
let keyboardEvent = new win.KeyboardEvent("", dict);
let flags = dict.flags;
this._isStarted = tip.flushPendingComposition(keyboardEvent, flags);
}
if (this._isStarted) {
this._tip = tip;
this._KeyboardEventForWin = win.KeyboardEvent;
}
},
endComposition: function cm_endComposition(text, dict) {
if (!this._isStarted) {
return;
}
let tip = this._tip;
if (!tip) {
return;
}
text = text || "";
if (!dict) {
tip.commitCompositionWith(text);
} else {
let keyboardEvent = new this._KeyboardEventForWin("", dict);
let flags = dict.flags;
tip.commitCompositionWith(text, keyboardEvent, flags);
}
this._isStarted = false;
this._tip = null;
this._KeyboardEventForWin = null;
},
// Composition ends due to external actions.
onCompositionEnd: function cm_onCompositionEnd() {
if (!this._isStarted) {
return;
}
this._isStarted = false;
this._tip = null;
this._KeyboardEventForWin = null;
}
};