forms.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  4. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. dump("###################################### forms.js loaded\n");
  7. var Ci = Components.interfaces;
  8. var Cc = Components.classes;
  9. var Cu = Components.utils;
  10. var Cr = Components.results;
  11. Cu.import("resource://gre/modules/Services.jsm");
  12. Cu.import('resource://gre/modules/XPCOMUtils.jsm');
  13. XPCOMUtils.defineLazyServiceGetter(Services, "fm",
  14. "@mozilla.org/focus-manager;1",
  15. "nsIFocusManager");
  16. /*
  17. * A WeakMap to map window to objects keeping it's TextInputProcessor instance.
  18. */
  19. var WindowMap = {
  20. // WeakMap of <window, object> pairs.
  21. _map: null,
  22. /*
  23. * Set the object associated to the window and return it.
  24. */
  25. _getObjForWin: function(win) {
  26. if (!this._map) {
  27. this._map = new WeakMap();
  28. }
  29. if (this._map.has(win)) {
  30. return this._map.get(win);
  31. } else {
  32. let obj = {
  33. tip: null
  34. };
  35. this._map.set(win, obj);
  36. return obj;
  37. }
  38. },
  39. getTextInputProcessor: function(win) {
  40. if (!win) {
  41. return;
  42. }
  43. let obj = this._getObjForWin(win);
  44. let tip = obj.tip
  45. if (!tip) {
  46. tip = obj.tip = Cc["@mozilla.org/text-input-processor;1"]
  47. .createInstance(Ci.nsITextInputProcessor);
  48. }
  49. if (!tip.beginInputTransaction(win, textInputProcessorCallback)) {
  50. tip = obj.tip = null;
  51. }
  52. return tip;
  53. }
  54. };
  55. const RESIZE_SCROLL_DELAY = 20;
  56. // In content editable node, when there are hidden elements such as <br>, it
  57. // may need more than one (usually less than 3 times) move/extend operations
  58. // to change the selection range. If we cannot change the selection range
  59. // with more than 20 opertations, we are likely being blocked and cannot change
  60. // the selection range any more.
  61. const MAX_BLOCKED_COUNT = 20;
  62. var HTMLDocument = Ci.nsIDOMHTMLDocument;
  63. var HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
  64. var HTMLBodyElement = Ci.nsIDOMHTMLBodyElement;
  65. var HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
  66. var HTMLInputElement = Ci.nsIDOMHTMLInputElement;
  67. var HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
  68. var HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
  69. var HTMLOptGroupElement = Ci.nsIDOMHTMLOptGroupElement;
  70. var HTMLOptionElement = Ci.nsIDOMHTMLOptionElement;
  71. function guessKeyNameFromKeyCode(KeyboardEvent, aKeyCode) {
  72. switch (aKeyCode) {
  73. case KeyboardEvent.DOM_VK_CANCEL:
  74. return "Cancel";
  75. case KeyboardEvent.DOM_VK_HELP:
  76. return "Help";
  77. case KeyboardEvent.DOM_VK_BACK_SPACE:
  78. return "Backspace";
  79. case KeyboardEvent.DOM_VK_TAB:
  80. return "Tab";
  81. case KeyboardEvent.DOM_VK_CLEAR:
  82. return "Clear";
  83. case KeyboardEvent.DOM_VK_RETURN:
  84. return "Enter";
  85. case KeyboardEvent.DOM_VK_SHIFT:
  86. return "Shift";
  87. case KeyboardEvent.DOM_VK_CONTROL:
  88. return "Control";
  89. case KeyboardEvent.DOM_VK_ALT:
  90. return "Alt";
  91. case KeyboardEvent.DOM_VK_PAUSE:
  92. return "Pause";
  93. case KeyboardEvent.DOM_VK_EISU:
  94. return "Eisu";
  95. case KeyboardEvent.DOM_VK_ESCAPE:
  96. return "Escape";
  97. case KeyboardEvent.DOM_VK_CONVERT:
  98. return "Convert";
  99. case KeyboardEvent.DOM_VK_NONCONVERT:
  100. return "NonConvert";
  101. case KeyboardEvent.DOM_VK_ACCEPT:
  102. return "Accept";
  103. case KeyboardEvent.DOM_VK_MODECHANGE:
  104. return "ModeChange";
  105. case KeyboardEvent.DOM_VK_PAGE_UP:
  106. return "PageUp";
  107. case KeyboardEvent.DOM_VK_PAGE_DOWN:
  108. return "PageDown";
  109. case KeyboardEvent.DOM_VK_END:
  110. return "End";
  111. case KeyboardEvent.DOM_VK_HOME:
  112. return "Home";
  113. case KeyboardEvent.DOM_VK_LEFT:
  114. return "ArrowLeft";
  115. case KeyboardEvent.DOM_VK_UP:
  116. return "ArrowUp";
  117. case KeyboardEvent.DOM_VK_RIGHT:
  118. return "ArrowRight";
  119. case KeyboardEvent.DOM_VK_DOWN:
  120. return "ArrowDown";
  121. case KeyboardEvent.DOM_VK_SELECT:
  122. return "Select";
  123. case KeyboardEvent.DOM_VK_PRINT:
  124. return "Print";
  125. case KeyboardEvent.DOM_VK_EXECUTE:
  126. return "Execute";
  127. case KeyboardEvent.DOM_VK_PRINTSCREEN:
  128. return "PrintScreen";
  129. case KeyboardEvent.DOM_VK_INSERT:
  130. return "Insert";
  131. case KeyboardEvent.DOM_VK_DELETE:
  132. return "Delete";
  133. case KeyboardEvent.DOM_VK_WIN:
  134. return "OS";
  135. case KeyboardEvent.DOM_VK_CONTEXT_MENU:
  136. return "ContextMenu";
  137. case KeyboardEvent.DOM_VK_SLEEP:
  138. return "Standby";
  139. case KeyboardEvent.DOM_VK_F1:
  140. return "F1";
  141. case KeyboardEvent.DOM_VK_F2:
  142. return "F2";
  143. case KeyboardEvent.DOM_VK_F3:
  144. return "F3";
  145. case KeyboardEvent.DOM_VK_F4:
  146. return "F4";
  147. case KeyboardEvent.DOM_VK_F5:
  148. return "F5";
  149. case KeyboardEvent.DOM_VK_F6:
  150. return "F6";
  151. case KeyboardEvent.DOM_VK_F7:
  152. return "F7";
  153. case KeyboardEvent.DOM_VK_F8:
  154. return "F8";
  155. case KeyboardEvent.DOM_VK_F9:
  156. return "F9";
  157. case KeyboardEvent.DOM_VK_F10:
  158. return "F10";
  159. case KeyboardEvent.DOM_VK_F11:
  160. return "F11";
  161. case KeyboardEvent.DOM_VK_F12:
  162. return "F12";
  163. case KeyboardEvent.DOM_VK_F13:
  164. return "F13";
  165. case KeyboardEvent.DOM_VK_F14:
  166. return "F14";
  167. case KeyboardEvent.DOM_VK_F15:
  168. return "F15";
  169. case KeyboardEvent.DOM_VK_F16:
  170. return "F16";
  171. case KeyboardEvent.DOM_VK_F17:
  172. return "F17";
  173. case KeyboardEvent.DOM_VK_F18:
  174. return "F18";
  175. case KeyboardEvent.DOM_VK_F19:
  176. return "F19";
  177. case KeyboardEvent.DOM_VK_F20:
  178. return "F20";
  179. case KeyboardEvent.DOM_VK_F21:
  180. return "F21";
  181. case KeyboardEvent.DOM_VK_F22:
  182. return "F22";
  183. case KeyboardEvent.DOM_VK_F23:
  184. return "F23";
  185. case KeyboardEvent.DOM_VK_F24:
  186. return "F24";
  187. case KeyboardEvent.DOM_VK_NUM_LOCK:
  188. return "NumLock";
  189. case KeyboardEvent.DOM_VK_SCROLL_LOCK:
  190. return "ScrollLock";
  191. case KeyboardEvent.DOM_VK_VOLUME_MUTE:
  192. return "AudioVolumeMute";
  193. case KeyboardEvent.DOM_VK_VOLUME_DOWN:
  194. return "AudioVolumeDown";
  195. case KeyboardEvent.DOM_VK_VOLUME_UP:
  196. return "AudioVolumeUp";
  197. case KeyboardEvent.DOM_VK_META:
  198. return "Meta";
  199. case KeyboardEvent.DOM_VK_ALTGR:
  200. return "AltGraph";
  201. case KeyboardEvent.DOM_VK_ATTN:
  202. return "Attn";
  203. case KeyboardEvent.DOM_VK_CRSEL:
  204. return "CrSel";
  205. case KeyboardEvent.DOM_VK_EXSEL:
  206. return "ExSel";
  207. case KeyboardEvent.DOM_VK_EREOF:
  208. return "EraseEof";
  209. case KeyboardEvent.DOM_VK_PLAY:
  210. return "Play";
  211. default:
  212. return "Unidentified";
  213. }
  214. }
  215. var FormVisibility = {
  216. /**
  217. * Searches upwards in the DOM for an element that has been scrolled.
  218. *
  219. * @param {HTMLElement} node element to start search at.
  220. * @return {Window|HTMLElement|Null} null when none are found window/element otherwise.
  221. */
  222. findScrolled: function fv_findScrolled(node) {
  223. let win = node.ownerDocument.defaultView;
  224. while (!(node instanceof HTMLBodyElement)) {
  225. // We can skip elements that have not been scrolled.
  226. // We only care about top now remember to add the scrollLeft
  227. // check if we decide to care about the X axis.
  228. if (node.scrollTop !== 0) {
  229. // the element has been scrolled so we may need to adjust
  230. // where we think the root element is located.
  231. //
  232. // Otherwise it may seem visible but be scrolled out of the viewport
  233. // inside this scrollable node.
  234. return node;
  235. } else {
  236. // this node does not effect where we think
  237. // the node is even if it is scrollable it has not hidden
  238. // the element we are looking for.
  239. node = node.parentNode;
  240. continue;
  241. }
  242. }
  243. // we also care about the window this is the more
  244. // common case where the content is larger then
  245. // the viewport/screen.
  246. if (win.scrollMaxX != win.scrollMinX || win.scrollMaxY != win.scrollMinY) {
  247. return win;
  248. }
  249. return null;
  250. },
  251. /**
  252. * Checks if "top and "bottom" points of the position is visible.
  253. *
  254. * @param {Number} top position.
  255. * @param {Number} height of the element.
  256. * @param {Number} maxHeight of the window.
  257. * @return {Boolean} true when visible.
  258. */
  259. yAxisVisible: function fv_yAxisVisible(top, height, maxHeight) {
  260. return (top > 0 && (top + height) < maxHeight);
  261. },
  262. /**
  263. * Searches up through the dom for scrollable elements
  264. * which are not currently visible (relative to the viewport).
  265. *
  266. * @param {HTMLElement} element to start search at.
  267. * @param {Object} pos .top, .height and .width of element.
  268. */
  269. scrollablesVisible: function fv_scrollablesVisible(element, pos) {
  270. while ((element = this.findScrolled(element))) {
  271. if (element.window && element.self === element)
  272. break;
  273. // remember getBoundingClientRect does not care
  274. // about scrolling only where the element starts
  275. // in the document.
  276. let offset = element.getBoundingClientRect();
  277. // the top of both the scrollable area and
  278. // the form element itself are in the same document.
  279. // We adjust the "top" so if the elements coordinates
  280. // are relative to the viewport in the current document.
  281. let adjustedTop = pos.top - offset.top;
  282. let visible = this.yAxisVisible(
  283. adjustedTop,
  284. pos.height,
  285. offset.height
  286. );
  287. if (!visible)
  288. return false;
  289. element = element.parentNode;
  290. }
  291. return true;
  292. },
  293. /**
  294. * Verifies the element is visible in the viewport.
  295. * Handles scrollable areas, frames and scrollable viewport(s) (windows).
  296. *
  297. * @param {HTMLElement} element to verify.
  298. * @return {Boolean} true when visible.
  299. */
  300. isVisible: function fv_isVisible(element) {
  301. // scrollable frames can be ignored we just care about iframes...
  302. let rect = element.getBoundingClientRect();
  303. let parent = element.ownerDocument.defaultView;
  304. // used to calculate the inner position of frames / scrollables.
  305. // The intent was to use this information to scroll either up or down.
  306. // scrollIntoView(true) will _break_ some web content so we can't do
  307. // this today. If we want that functionality we need to manually scroll
  308. // the individual elements.
  309. let pos = {
  310. top: rect.top,
  311. height: rect.height,
  312. width: rect.width
  313. };
  314. let visible = true;
  315. do {
  316. let frame = parent.frameElement;
  317. visible = visible &&
  318. this.yAxisVisible(pos.top, pos.height, parent.innerHeight) &&
  319. this.scrollablesVisible(element, pos);
  320. // nothing we can do about this now...
  321. // In the future we can use this information to scroll
  322. // only the elements we need to at this point as we should
  323. // have all the details we need to figure out how to scroll.
  324. if (!visible)
  325. return false;
  326. if (frame) {
  327. let frameRect = frame.getBoundingClientRect();
  328. pos.top += frameRect.top + frame.clientTop;
  329. }
  330. } while (
  331. (parent !== parent.parent) &&
  332. (parent = parent.parent)
  333. );
  334. return visible;
  335. }
  336. };
  337. // This object implements nsITextInputProcessorCallback
  338. var textInputProcessorCallback = {
  339. onNotify: function(aTextInputProcessor, aNotification) {
  340. try {
  341. switch (aNotification.type) {
  342. case "request-to-commit":
  343. // TODO: Send a notification through asyncMessage to the keyboard here.
  344. aTextInputProcessor.commitComposition();
  345. break;
  346. case "request-to-cancel":
  347. // TODO: Send a notification through asyncMessage to the keyboard here.
  348. aTextInputProcessor.cancelComposition();
  349. break;
  350. case "notify-detached":
  351. // TODO: Send a notification through asyncMessage to the keyboard here.
  352. break;
  353. // TODO: Manage _focusedElement for text input from here instead.
  354. // (except for <select> which will be need to handled elsewhere)
  355. case "notify-focus":
  356. break;
  357. case "notify-blur":
  358. break;
  359. }
  360. } catch (e) {
  361. return false;
  362. }
  363. return true;
  364. }
  365. };
  366. var FormAssistant = {
  367. init: function fa_init() {
  368. addEventListener("focus", this, true, false);
  369. addEventListener("blur", this, true, false);
  370. addEventListener("resize", this, true, false);
  371. // We should not blur the fucus if the submit event is cancelled,
  372. // therefore we are binding our event listener in the bubbling phase here.
  373. addEventListener("submit", this, false, false);
  374. addEventListener("pagehide", this, true, false);
  375. addEventListener("beforeunload", this, true, false);
  376. addEventListener("input", this, true, false);
  377. addEventListener("keydown", this, true, false);
  378. addEventListener("keyup", this, true, false);
  379. addMessageListener("Forms:Select:Choice", this);
  380. addMessageListener("Forms:Input:Value", this);
  381. addMessageListener("Forms:Select:Blur", this);
  382. addMessageListener("Forms:SetSelectionRange", this);
  383. addMessageListener("Forms:ReplaceSurroundingText", this);
  384. addMessageListener("Forms:Input:SendKey", this);
  385. addMessageListener("Forms:GetContext", this);
  386. addMessageListener("Forms:SetComposition", this);
  387. addMessageListener("Forms:EndComposition", this);
  388. },
  389. ignoredInputTypes: new Set([
  390. 'button', 'file', 'checkbox', 'radio', 'reset', 'submit', 'image',
  391. 'range'
  392. ]),
  393. isHandlingFocus: false,
  394. selectionStart: -1,
  395. selectionEnd: -1,
  396. text: "",
  397. scrollIntoViewTimeout: null,
  398. _focusedElement: null,
  399. _focusCounter: 0, // up one for every time we focus a new element
  400. _focusDeleteObserver: null,
  401. _focusContentObserver: null,
  402. _documentEncoder: null,
  403. _editor: null,
  404. _editing: false,
  405. _selectionPrivate: null,
  406. get focusedElement() {
  407. if (this._focusedElement && Cu.isDeadWrapper(this._focusedElement))
  408. this._focusedElement = null;
  409. return this._focusedElement;
  410. },
  411. set focusedElement(val) {
  412. this._focusCounter++;
  413. this._focusedElement = val;
  414. },
  415. setFocusedElement: function fa_setFocusedElement(element) {
  416. let self = this;
  417. if (element === this.focusedElement)
  418. return;
  419. if (this.focusedElement) {
  420. this.focusedElement.removeEventListener('compositionend', this);
  421. if (this._focusDeleteObserver) {
  422. this._focusDeleteObserver.disconnect();
  423. this._focusDeleteObserver = null;
  424. }
  425. if (this._focusContentObserver) {
  426. this._focusContentObserver.disconnect();
  427. this._focusContentObserver = null;
  428. }
  429. if (this._selectionPrivate) {
  430. this._selectionPrivate.removeSelectionListener(this);
  431. this._selectionPrivate = null;
  432. }
  433. }
  434. this._documentEncoder = null;
  435. if (this._editor) {
  436. // When the nsIFrame of the input element is reconstructed by
  437. // CSS restyling, the editor observers are removed. Catch
  438. // [nsIEditor.removeEditorObserver] failure exception if that
  439. // happens.
  440. try {
  441. this._editor.removeEditorObserver(this);
  442. } catch (e) {}
  443. this._editor = null;
  444. }
  445. if (element) {
  446. element.addEventListener('compositionend', this);
  447. if (isContentEditable(element)) {
  448. this._documentEncoder = getDocumentEncoder(element);
  449. }
  450. this._editor = getPlaintextEditor(element);
  451. if (this._editor) {
  452. // Add a nsIEditorObserver to monitor the text content of the focused
  453. // element.
  454. this._editor.addEditorObserver(this);
  455. let selection = this._editor.selection;
  456. if (selection) {
  457. this._selectionPrivate = selection.QueryInterface(Ci.nsISelectionPrivate);
  458. this._selectionPrivate.addSelectionListener(this);
  459. }
  460. }
  461. // If our focusedElement is removed from DOM we want to handle it properly
  462. let MutationObserver = element.ownerDocument.defaultView.MutationObserver;
  463. this._focusDeleteObserver = new MutationObserver(function(mutations) {
  464. var del = [].some.call(mutations, function(m) {
  465. return [].some.call(m.removedNodes, function(n) {
  466. return n.contains(element);
  467. });
  468. });
  469. if (del && element === self.focusedElement) {
  470. self.unhandleFocus();
  471. }
  472. });
  473. this._focusDeleteObserver.observe(element.ownerDocument.body, {
  474. childList: true,
  475. subtree: true
  476. });
  477. // If contenteditable, also add a mutation observer on its content and
  478. // call selectionChanged when a change occurs
  479. if (isContentEditable(element)) {
  480. this._focusContentObserver = new MutationObserver(function() {
  481. this.updateSelection();
  482. }.bind(this));
  483. this._focusContentObserver.observe(element, {
  484. childList: true,
  485. subtree: true
  486. });
  487. }
  488. }
  489. this.focusedElement = element;
  490. },
  491. notifySelectionChanged: function(aDocument, aSelection, aReason) {
  492. this.updateSelection();
  493. },
  494. get documentEncoder() {
  495. return this._documentEncoder;
  496. },
  497. // Get the nsIPlaintextEditor object of current input field.
  498. get editor() {
  499. return this._editor;
  500. },
  501. // Implements nsIEditorObserver get notification when the text content of
  502. // current input field has changed.
  503. EditAction: function fa_editAction() {
  504. if (this._editing || !this.isHandlingFocus) {
  505. return;
  506. }
  507. this.sendInputState(this.focusedElement);
  508. },
  509. handleEvent: function fa_handleEvent(evt) {
  510. let target = evt.composedTarget;
  511. let range = null;
  512. switch (evt.type) {
  513. case "focus":
  514. if (!target) {
  515. break;
  516. }
  517. // Focusing on Window, Document or iFrame should focus body
  518. if (target instanceof HTMLHtmlElement) {
  519. target = target.document.body;
  520. } else if (target instanceof HTMLDocument) {
  521. target = target.body;
  522. } else if (target instanceof HTMLIFrameElement) {
  523. target = target.contentDocument ? target.contentDocument.body
  524. : null;
  525. }
  526. if (!target) {
  527. break;
  528. }
  529. if (isContentEditable(target)) {
  530. this.handleFocus(this.getTopLevelEditable(target));
  531. this.updateSelection();
  532. break;
  533. }
  534. if (this.isFocusableElement(target)) {
  535. this.handleFocus(target);
  536. this.updateSelection();
  537. }
  538. break;
  539. case "pagehide":
  540. case "beforeunload":
  541. // We are only interested to the pagehide and beforeunload events from
  542. // the root document.
  543. if (target && target != content.document) {
  544. break;
  545. }
  546. // fall through
  547. case "submit":
  548. if (this.focusedElement && !evt.defaultPrevented) {
  549. this.focusedElement.blur();
  550. }
  551. break;
  552. case "blur":
  553. if (this.focusedElement) {
  554. this.unhandleFocus();
  555. }
  556. break;
  557. case "resize":
  558. if (!this.isHandlingFocus)
  559. return;
  560. if (this.scrollIntoViewTimeout) {
  561. content.clearTimeout(this.scrollIntoViewTimeout);
  562. this.scrollIntoViewTimeout = null;
  563. }
  564. // We may receive multiple resize events in quick succession, so wait
  565. // a bit before scrolling the input element into view.
  566. if (this.focusedElement) {
  567. this.scrollIntoViewTimeout = content.setTimeout(function () {
  568. this.scrollIntoViewTimeout = null;
  569. if (this.focusedElement && !FormVisibility.isVisible(this.focusedElement)) {
  570. scrollSelectionOrElementIntoView(this.focusedElement);
  571. }
  572. }.bind(this), RESIZE_SCROLL_DELAY);
  573. }
  574. break;
  575. case "keydown":
  576. if (!this.focusedElement || this._editing) {
  577. break;
  578. }
  579. CompositionManager.endComposition('');
  580. break;
  581. case "keyup":
  582. if (!this.focusedElement || this._editing) {
  583. break;
  584. }
  585. CompositionManager.endComposition('');
  586. break;
  587. case "compositionend":
  588. if (!this.focusedElement) {
  589. break;
  590. }
  591. CompositionManager.onCompositionEnd();
  592. break;
  593. }
  594. },
  595. receiveMessage: function fa_receiveMessage(msg) {
  596. let target = this.focusedElement;
  597. let json = msg.json;
  598. // To not break mozKeyboard contextId is optional
  599. if ('contextId' in json &&
  600. json.contextId !== this._focusCounter &&
  601. json.requestId) {
  602. // Ignore messages that are meant for a previously focused element
  603. sendAsyncMessage("Forms:SequenceError", {
  604. requestId: json.requestId,
  605. error: "Expected contextId " + this._focusCounter +
  606. " but was " + json.contextId
  607. });
  608. return;
  609. }
  610. if (!target) {
  611. return;
  612. }
  613. this._editing = true;
  614. switch (msg.name) {
  615. case "Forms:Input:Value": {
  616. CompositionManager.endComposition('');
  617. target.value = json.value;
  618. let event = target.ownerDocument.createEvent('HTMLEvents');
  619. event.initEvent('input', true, false);
  620. target.dispatchEvent(event);
  621. break;
  622. }
  623. case "Forms:Input:SendKey":
  624. CompositionManager.endComposition('');
  625. let win = target.ownerDocument.defaultView;
  626. let tip = WindowMap.getTextInputProcessor(win);
  627. if (!tip) {
  628. if (json.requestId) {
  629. sendAsyncMessage("Forms:SendKey:Result:Error", {
  630. requestId: json.requestId,
  631. error: "Unable to start input transaction."
  632. });
  633. }
  634. break;
  635. }
  636. // If we receive a keyboardEventDict from json, that means the user
  637. // is calling the method with the new arguments.
  638. // Otherwise, we would have to construct our own keyboardEventDict
  639. // based on legacy values we have received.
  640. let keyboardEventDict = json.keyboardEventDict;
  641. let flags = 0;
  642. if (keyboardEventDict) {
  643. if ('flags' in keyboardEventDict) {
  644. flags = keyboardEventDict.flags;
  645. }
  646. } else {
  647. // The naive way to figure out if the key to dispatch is printable.
  648. let printable = !!json.charCode;
  649. // For printable keys, the value should be the actual character.
  650. // For non-printable keys, it should be a value in the D3E spec.
  651. // Here we make some educated guess for it.
  652. let key = printable ?
  653. String.fromCharCode(json.charCode) :
  654. guessKeyNameFromKeyCode(win.KeyboardEvent, json.keyCode);
  655. // keyCode from content is only respected when the key is not an
  656. // an alphanumeric character. We also ask TextInputProcessor not to
  657. // infer this value for non-printable keys to keep the original
  658. // behavior.
  659. let keyCode = (printable && /^[a-zA-Z0-9]$/.test(key)) ?
  660. key.toUpperCase().charCodeAt(0) :
  661. json.keyCode;
  662. keyboardEventDict = {
  663. key: key,
  664. keyCode: keyCode,
  665. // We don't have any information to tell the virtual key the
  666. // user have interacted with.
  667. code: "",
  668. // We do not have the information to infer location of the virtual key
  669. // either (and we would need TextInputProcessor not to compute it).
  670. location: 0,
  671. // This indicates the key is triggered for repeats.
  672. repeat: json.repeat
  673. };
  674. flags = tip.KEY_KEEP_KEY_LOCATION_STANDARD;
  675. if (!printable) {
  676. flags |= tip.KEY_NON_PRINTABLE_KEY;
  677. }
  678. if (!keyboardEventDict.keyCode) {
  679. flags |= tip.KEY_KEEP_KEYCODE_ZERO;
  680. }
  681. }
  682. let keyboardEvent = new win.KeyboardEvent("", keyboardEventDict);
  683. let keydownDefaultPrevented = false;
  684. try {
  685. switch (json.method) {
  686. case 'sendKey': {
  687. let consumedFlags = tip.keydown(keyboardEvent, flags);
  688. keydownDefaultPrevented =
  689. !!(tip.KEYDOWN_IS_CONSUMED & consumedFlags);
  690. if (!keyboardEventDict.repeat) {
  691. tip.keyup(keyboardEvent, flags);
  692. }
  693. break;
  694. }
  695. case 'keydown': {
  696. let consumedFlags = tip.keydown(keyboardEvent, flags);
  697. keydownDefaultPrevented =
  698. !!(tip.KEYDOWN_IS_CONSUMED & consumedFlags);
  699. break;
  700. }
  701. case 'keyup': {
  702. tip.keyup(keyboardEvent, flags);
  703. break;
  704. }
  705. }
  706. } catch (err) {
  707. dump("forms.js:" + err.toString() + "\n");
  708. if (json.requestId) {
  709. if (err instanceof Ci.nsIException &&
  710. err.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
  711. sendAsyncMessage("Forms:SendKey:Result:Error", {
  712. requestId: json.requestId,
  713. error: "The values specified are illegal."
  714. });
  715. } else {
  716. sendAsyncMessage("Forms:SendKey:Result:Error", {
  717. requestId: json.requestId,
  718. error: "Unable to type into destroyed input."
  719. });
  720. }
  721. }
  722. break;
  723. }
  724. if (json.requestId) {
  725. if (keydownDefaultPrevented) {
  726. sendAsyncMessage("Forms:SendKey:Result:Error", {
  727. requestId: json.requestId,
  728. error: "Key event(s) was cancelled."
  729. });
  730. } else {
  731. sendAsyncMessage("Forms:SendKey:Result:OK", {
  732. requestId: json.requestId,
  733. selectioninfo: this.getSelectionInfo()
  734. });
  735. }
  736. }
  737. break;
  738. case "Forms:Select:Choice":
  739. let options = target.options;
  740. let valueChanged = false;
  741. if ("index" in json) {
  742. if (options.selectedIndex != json.index) {
  743. options.selectedIndex = json.index;
  744. valueChanged = true;
  745. }
  746. } else if ("indexes" in json) {
  747. for (let i = 0; i < options.length; i++) {
  748. let newValue = (json.indexes.indexOf(i) != -1);
  749. if (options.item(i).selected != newValue) {
  750. options.item(i).selected = newValue;
  751. valueChanged = true;
  752. }
  753. }
  754. }
  755. // only fire onchange event if any selected option is changed
  756. if (valueChanged) {
  757. let event = target.ownerDocument.createEvent('HTMLEvents');
  758. event.initEvent('change', true, true);
  759. target.dispatchEvent(event);
  760. }
  761. break;
  762. case "Forms:Select:Blur": {
  763. if (this.focusedElement) {
  764. this.focusedElement.blur();
  765. }
  766. break;
  767. }
  768. case "Forms:SetSelectionRange": {
  769. CompositionManager.endComposition('');
  770. let start = json.selectionStart;
  771. let end = json.selectionEnd;
  772. if (!setSelectionRange(target, start, end)) {
  773. if (json.requestId) {
  774. sendAsyncMessage("Forms:SetSelectionRange:Result:Error", {
  775. requestId: json.requestId,
  776. error: "failed"
  777. });
  778. }
  779. break;
  780. }
  781. if (json.requestId) {
  782. sendAsyncMessage("Forms:SetSelectionRange:Result:OK", {
  783. requestId: json.requestId,
  784. selectioninfo: this.getSelectionInfo()
  785. });
  786. }
  787. break;
  788. }
  789. case "Forms:ReplaceSurroundingText": {
  790. CompositionManager.endComposition('');
  791. if (!replaceSurroundingText(target,
  792. json.text,
  793. json.offset,
  794. json.length)) {
  795. if (json.requestId) {
  796. sendAsyncMessage("Forms:ReplaceSurroundingText:Result:Error", {
  797. requestId: json.requestId,
  798. error: "failed"
  799. });
  800. }
  801. break;
  802. }
  803. if (json.requestId) {
  804. sendAsyncMessage("Forms:ReplaceSurroundingText:Result:OK", {
  805. requestId: json.requestId,
  806. selectioninfo: this.getSelectionInfo()
  807. });
  808. }
  809. break;
  810. }
  811. case "Forms:GetContext": {
  812. let obj = getJSON(target, this._focusCounter);
  813. sendAsyncMessage("Forms:GetContext:Result:OK", obj);
  814. break;
  815. }
  816. case "Forms:SetComposition": {
  817. CompositionManager.setComposition(target, json.text, json.cursor,
  818. json.clauses, json.keyboardEventDict);
  819. sendAsyncMessage("Forms:SetComposition:Result:OK", {
  820. requestId: json.requestId,
  821. selectioninfo: this.getSelectionInfo()
  822. });
  823. break;
  824. }
  825. case "Forms:EndComposition": {
  826. CompositionManager.endComposition(json.text, json.keyboardEventDict);
  827. sendAsyncMessage("Forms:EndComposition:Result:OK", {
  828. requestId: json.requestId,
  829. selectioninfo: this.getSelectionInfo()
  830. });
  831. break;
  832. }
  833. }
  834. this._editing = false;
  835. },
  836. handleFocus: function fa_handleFocus(target) {
  837. if (this.focusedElement === target)
  838. return;
  839. if (target instanceof HTMLOptionElement)
  840. target = target.parentNode;
  841. this.setFocusedElement(target);
  842. this.sendInputState(target);
  843. this.isHandlingFocus = true;
  844. },
  845. unhandleFocus: function fa_unhandleFocus() {
  846. this.setFocusedElement(null);
  847. this.isHandlingFocus = false;
  848. this.selectionStart = -1;
  849. this.selectionEnd = -1;
  850. this.text = "";
  851. sendAsyncMessage("Forms:Blur", {});
  852. },
  853. isFocusableElement: function fa_isFocusableElement(element) {
  854. if (element instanceof HTMLSelectElement ||
  855. element instanceof HTMLTextAreaElement)
  856. return true;
  857. if (element instanceof HTMLOptionElement &&
  858. element.parentNode instanceof HTMLSelectElement)
  859. return true;
  860. return (element instanceof HTMLInputElement &&
  861. !this.ignoredInputTypes.has(element.type) &&
  862. !element.readOnly);
  863. },
  864. getTopLevelEditable: function fa_getTopLevelEditable(element) {
  865. function retrieveTopLevelEditable(element) {
  866. while (element && !isContentEditable(element))
  867. element = element.parentNode;
  868. return element;
  869. }
  870. return retrieveTopLevelEditable(element) || element;
  871. },
  872. sendInputState: function(element) {
  873. sendAsyncMessage("Forms:Focus", getJSON(element, this._focusCounter));
  874. },
  875. getSelectionInfo: function fa_getSelectionInfo() {
  876. let element = this.focusedElement;
  877. let range = getSelectionRange(element);
  878. let text = isContentEditable(element) ? getContentEditableText(element)
  879. : element.value;
  880. let changed = this.selectionStart !== range[0] ||
  881. this.selectionEnd !== range[1] ||
  882. this.text !== text;
  883. this.selectionStart = range[0];
  884. this.selectionEnd = range[1];
  885. this.text = text;
  886. return {
  887. selectionStart: range[0],
  888. selectionEnd: range[1],
  889. text: text,
  890. changed: changed
  891. };
  892. },
  893. _selectionTimeout: null,
  894. // Notify when the selection range changes
  895. updateSelection: function fa_updateSelection() {
  896. // A call to setSelectionRange on input field causes 2 selection changes
  897. // one to [0,0] and one to actual value. Both are sent in same tick.
  898. // Prevent firing two events in that scenario, always only use the last 1.
  899. //
  900. // It is also a workaround for Bug 1053048, which prevents
  901. // getSelectionInfo() accessing selectionStart or selectionEnd in the
  902. // callback function of nsISelectionListener::NotifySelectionChanged().
  903. if (this._selectionTimeout) {
  904. content.clearTimeout(this._selectionTimeout);
  905. }
  906. this._selectionTimeout = content.setTimeout(function() {
  907. if (!this.focusedElement) {
  908. return;
  909. }
  910. let selectionInfo = this.getSelectionInfo();
  911. if (selectionInfo.changed) {
  912. sendAsyncMessage("Forms:SelectionChange", selectionInfo);
  913. }
  914. }.bind(this), 0);
  915. }
  916. };
  917. FormAssistant.init();
  918. function isContentEditable(element) {
  919. if (!element) {
  920. return false;
  921. }
  922. if (element.isContentEditable || element.designMode == "on")
  923. return true;
  924. return element.ownerDocument && element.ownerDocument.designMode == "on";
  925. }
  926. function isPlainTextField(element) {
  927. if (!element) {
  928. return false;
  929. }
  930. return element instanceof HTMLTextAreaElement ||
  931. (element instanceof HTMLInputElement &&
  932. element.mozIsTextField(false));
  933. }
  934. function getJSON(element, focusCounter) {
  935. // <input type=number> has a nested anonymous <input type=text> element that
  936. // takes focus on behalf of the number control when someone tries to focus
  937. // the number control. If |element| is such an anonymous text control then we
  938. // need it's number control here in order to get the correct 'type' etc.:
  939. element = element.ownerNumberControl || element;
  940. let type = element.tagName.toLowerCase();
  941. let inputType = (element.type || "").toLowerCase();
  942. let value = element.value || "";
  943. let max = element.max || "";
  944. let min = element.min || "";
  945. // Treat contenteditable element as a special text area field
  946. if (isContentEditable(element)) {
  947. type = "contenteditable";
  948. inputType = "textarea";
  949. value = getContentEditableText(element);
  950. }
  951. // Until the input type=date/datetime/range have been implemented
  952. // let's return their real type even if the platform returns 'text'
  953. let attributeInputType = element.getAttribute("type") || "";
  954. if (attributeInputType) {
  955. let inputTypeLowerCase = attributeInputType.toLowerCase();
  956. switch (inputTypeLowerCase) {
  957. case "datetime":
  958. case "datetime-local":
  959. case "month":
  960. case "week":
  961. case "range":
  962. inputType = inputTypeLowerCase;
  963. break;
  964. }
  965. }
  966. // Gecko has some support for @inputmode but behind a preference and
  967. // it is disabled by default.
  968. // Gaia is then using @x-inputmode has its proprietary way to set
  969. // inputmode for fields. This shouldn't be used outside of pre-installed
  970. // apps because the attribute is going to disappear as soon as a definitive
  971. // solution will be find.
  972. let inputMode = element.getAttribute('x-inputmode');
  973. if (inputMode) {
  974. inputMode = inputMode.toLowerCase();
  975. } else {
  976. inputMode = '';
  977. }
  978. let range = getSelectionRange(element);
  979. return {
  980. "contextId": focusCounter,
  981. "type": type,
  982. "inputType": inputType,
  983. "inputMode": inputMode,
  984. "choices": getListForElement(element),
  985. "value": value,
  986. "selectionStart": range[0],
  987. "selectionEnd": range[1],
  988. "max": max,
  989. "min": min,
  990. "lang": element.lang || ""
  991. };
  992. }
  993. function getListForElement(element) {
  994. if (!(element instanceof HTMLSelectElement))
  995. return null;
  996. let optionIndex = 0;
  997. let result = {
  998. "multiple": element.multiple,
  999. "choices": []
  1000. };
  1001. // Build up a flat JSON array of the choices.
  1002. // In HTML, it's possible for select element choices to be under a
  1003. // group header (but not recursively). We distinguish between headers
  1004. // and entries using the boolean "list.group".
  1005. let children = element.children;
  1006. for (let i = 0; i < children.length; i++) {
  1007. let child = children[i];
  1008. if (child instanceof HTMLOptGroupElement) {
  1009. result.choices.push({
  1010. "group": true,
  1011. "text": child.label || child.firstChild.data,
  1012. "disabled": child.disabled
  1013. });
  1014. let subchildren = child.children;
  1015. for (let j = 0; j < subchildren.length; j++) {
  1016. let subchild = subchildren[j];
  1017. result.choices.push({
  1018. "group": false,
  1019. "inGroup": true,
  1020. "text": subchild.text,
  1021. "disabled": child.disabled || subchild.disabled,
  1022. "selected": subchild.selected,
  1023. "optionIndex": optionIndex++
  1024. });
  1025. }
  1026. } else if (child instanceof HTMLOptionElement) {
  1027. result.choices.push({
  1028. "group": false,
  1029. "inGroup": false,
  1030. "text": child.text,
  1031. "disabled": child.disabled,
  1032. "selected": child.selected,
  1033. "optionIndex": optionIndex++
  1034. });
  1035. }
  1036. }
  1037. return result;
  1038. };
  1039. // Create a plain text document encode from the focused element.
  1040. function getDocumentEncoder(element) {
  1041. let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
  1042. .createInstance(Ci.nsIDocumentEncoder);
  1043. let flags = Ci.nsIDocumentEncoder.SkipInvisibleContent |
  1044. Ci.nsIDocumentEncoder.OutputRaw |
  1045. Ci.nsIDocumentEncoder.OutputDropInvisibleBreak |
  1046. // Bug 902847. Don't trim trailing spaces of a line.
  1047. Ci.nsIDocumentEncoder.OutputDontRemoveLineEndingSpaces |
  1048. Ci.nsIDocumentEncoder.OutputLFLineBreak |
  1049. Ci.nsIDocumentEncoder.OutputNonTextContentAsPlaceholder;
  1050. encoder.init(element.ownerDocument, "text/plain", flags);
  1051. return encoder;
  1052. }
  1053. // Get the visible content text of a content editable element
  1054. function getContentEditableText(element) {
  1055. if (!element || !isContentEditable(element)) {
  1056. return null;
  1057. }
  1058. let doc = element.ownerDocument;
  1059. let range = doc.createRange();
  1060. range.selectNodeContents(element);
  1061. let encoder = FormAssistant.documentEncoder;
  1062. encoder.setRange(range);
  1063. return encoder.encodeToString();
  1064. }
  1065. function getSelectionRange(element) {
  1066. let start = 0;
  1067. let end = 0;
  1068. if (isPlainTextField(element)) {
  1069. // Get the selection range of <input> and <textarea> elements
  1070. start = element.selectionStart;
  1071. end = element.selectionEnd;
  1072. } else if (isContentEditable(element)){
  1073. // Get the selection range of contenteditable elements
  1074. let win = element.ownerDocument.defaultView;
  1075. let sel = win.getSelection();
  1076. if (sel && sel.rangeCount > 0) {
  1077. start = getContentEditableSelectionStart(element, sel);
  1078. end = start + getContentEditableSelectionLength(element, sel);
  1079. } else {
  1080. dump("Failed to get window.getSelection()\n");
  1081. }
  1082. }
  1083. return [start, end];
  1084. }
  1085. function getContentEditableSelectionStart(element, selection) {
  1086. let doc = element.ownerDocument;
  1087. let range = doc.createRange();
  1088. range.setStart(element, 0);
  1089. range.setEnd(selection.anchorNode, selection.anchorOffset);
  1090. let encoder = FormAssistant.documentEncoder;
  1091. encoder.setRange(range);
  1092. return encoder.encodeToString().length;
  1093. }
  1094. function getContentEditableSelectionLength(element, selection) {
  1095. let encoder = FormAssistant.documentEncoder;
  1096. encoder.setRange(selection.getRangeAt(0));
  1097. return encoder.encodeToString().length;
  1098. }
  1099. function setSelectionRange(element, start, end) {
  1100. let isTextField = isPlainTextField(element);
  1101. // Check the parameters
  1102. if (!isTextField && !isContentEditable(element)) {
  1103. // Skip HTMLOptionElement and HTMLSelectElement elements, as they don't
  1104. // support the operation of setSelectionRange
  1105. return false;
  1106. }
  1107. let text = isTextField ? element.value : getContentEditableText(element);
  1108. let length = text.length;
  1109. if (start < 0) {
  1110. start = 0;
  1111. }
  1112. if (end > length) {
  1113. end = length;
  1114. }
  1115. if (start > end) {
  1116. start = end;
  1117. }
  1118. if (isTextField) {
  1119. // Set the selection range of <input> and <textarea> elements
  1120. element.setSelectionRange(start, end, "forward");
  1121. return true;
  1122. } else {
  1123. // set the selection range of contenteditable elements
  1124. let win = element.ownerDocument.defaultView;
  1125. let sel = win.getSelection();
  1126. // Move the caret to the start position
  1127. sel.collapse(element, 0);
  1128. for (let i = 0; i < start; i++) {
  1129. sel.modify("move", "forward", "character");
  1130. }
  1131. // Avoid entering infinite loop in case we cannot change the selection
  1132. // range. See bug https://bugzilla.mozilla.org/show_bug.cgi?id=978918
  1133. let oldStart = getContentEditableSelectionStart(element, sel);
  1134. let counter = 0;
  1135. while (oldStart < start) {
  1136. sel.modify("move", "forward", "character");
  1137. let newStart = getContentEditableSelectionStart(element, sel);
  1138. if (oldStart == newStart) {
  1139. counter++;
  1140. if (counter > MAX_BLOCKED_COUNT) {
  1141. return false;
  1142. }
  1143. } else {
  1144. counter = 0;
  1145. oldStart = newStart;
  1146. }
  1147. }
  1148. // Extend the selection to the end position
  1149. for (let i = start; i < end; i++) {
  1150. sel.modify("extend", "forward", "character");
  1151. }
  1152. // Avoid entering infinite loop in case we cannot change the selection
  1153. // range. See bug https://bugzilla.mozilla.org/show_bug.cgi?id=978918
  1154. counter = 0;
  1155. let selectionLength = end - start;
  1156. let oldSelectionLength = getContentEditableSelectionLength(element, sel);
  1157. while (oldSelectionLength < selectionLength) {
  1158. sel.modify("extend", "forward", "character");
  1159. let newSelectionLength = getContentEditableSelectionLength(element, sel);
  1160. if (oldSelectionLength == newSelectionLength ) {
  1161. counter++;
  1162. if (counter > MAX_BLOCKED_COUNT) {
  1163. return false;
  1164. }
  1165. } else {
  1166. counter = 0;
  1167. oldSelectionLength = newSelectionLength;
  1168. }
  1169. }
  1170. return true;
  1171. }
  1172. }
  1173. /**
  1174. * Scroll the given element into view.
  1175. *
  1176. * Calls scrollSelectionIntoView for contentEditable elements.
  1177. */
  1178. function scrollSelectionOrElementIntoView(element) {
  1179. let editor = getPlaintextEditor(element);
  1180. if (editor) {
  1181. editor.selectionController.scrollSelectionIntoView(
  1182. Ci.nsISelectionController.SELECTION_NORMAL,
  1183. Ci.nsISelectionController.SELECTION_FOCUS_REGION,
  1184. Ci.nsISelectionController.SCROLL_SYNCHRONOUS);
  1185. } else {
  1186. element.scrollIntoView(false);
  1187. }
  1188. }
  1189. // Get nsIPlaintextEditor object from an input field
  1190. function getPlaintextEditor(element) {
  1191. let editor = null;
  1192. // Get nsIEditor
  1193. if (isPlainTextField(element)) {
  1194. // Get from the <input> and <textarea> elements
  1195. editor = element.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
  1196. } else if (isContentEditable(element)) {
  1197. // Get from content editable element
  1198. let win = element.ownerDocument.defaultView;
  1199. let editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
  1200. .getInterface(Ci.nsIWebNavigation)
  1201. .QueryInterface(Ci.nsIInterfaceRequestor)
  1202. .getInterface(Ci.nsIEditingSession);
  1203. if (editingSession) {
  1204. editor = editingSession.getEditorForWindow(win);
  1205. }
  1206. }
  1207. if (editor) {
  1208. editor.QueryInterface(Ci.nsIPlaintextEditor);
  1209. }
  1210. return editor;
  1211. }
  1212. function replaceSurroundingText(element, text, offset, length) {
  1213. let editor = FormAssistant.editor;
  1214. if (!editor) {
  1215. return false;
  1216. }
  1217. // Check the parameters.
  1218. if (length < 0) {
  1219. length = 0;
  1220. }
  1221. // Change selection range before replacing. For content editable element,
  1222. // searching the node for setting selection range is not needed when the
  1223. // selection is collapsed within a text node.
  1224. let fastPathHit = false;
  1225. if (!isPlainTextField(element)) {
  1226. let sel = element.ownerDocument.defaultView.getSelection();
  1227. let node = sel.anchorNode;
  1228. if (sel.isCollapsed && node && node.nodeType == 3 /* TEXT_NODE */) {
  1229. let start = sel.anchorOffset + offset;
  1230. let end = start + length;
  1231. // Fallback to setSelectionRange() if the replacement span multiple nodes.
  1232. if (start >= 0 && end <= node.textContent.length) {
  1233. fastPathHit = true;
  1234. sel.collapse(node, start);
  1235. sel.extend(node, end);
  1236. }
  1237. }
  1238. }
  1239. if (!fastPathHit) {
  1240. let range = getSelectionRange(element);
  1241. let start = range[0] + offset;
  1242. if (start < 0) {
  1243. start = 0;
  1244. }
  1245. let end = start + length;
  1246. if (start != range[0] || end != range[1]) {
  1247. if (!setSelectionRange(element, start, end)) {
  1248. return false;
  1249. }
  1250. }
  1251. }
  1252. if (length) {
  1253. // Delete the selected text.
  1254. editor.deleteSelection(Ci.nsIEditor.ePrevious, Ci.nsIEditor.eStrip);
  1255. }
  1256. if (text) {
  1257. // We don't use CR but LF
  1258. // see https://bugzilla.mozilla.org/show_bug.cgi?id=902847
  1259. text = text.replace(/\r/g, '\n');
  1260. // Insert the text to be replaced with.
  1261. editor.insertText(text);
  1262. }
  1263. return true;
  1264. }
  1265. var CompositionManager = {
  1266. _isStarted: false,
  1267. _tip: null,
  1268. _KeyboardEventForWin: null,
  1269. _clauseAttrMap: {
  1270. 'raw-input':
  1271. Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE,
  1272. 'selected-raw-text':
  1273. Ci.nsITextInputProcessor.ATTR_SELECTED_RAW_CLAUSE,
  1274. 'converted-text':
  1275. Ci.nsITextInputProcessor.ATTR_CONVERTED_CLAUSE,
  1276. 'selected-converted-text':
  1277. Ci.nsITextInputProcessor.ATTR_SELECTED_CLAUSE
  1278. },
  1279. setComposition: function cm_setComposition(element, text, cursor, clauses, dict) {
  1280. // Check parameters.
  1281. if (!element) {
  1282. return;
  1283. }
  1284. let len = text.length;
  1285. if (cursor > len) {
  1286. cursor = len;
  1287. }
  1288. let clauseLens = [];
  1289. let clauseAttrs = [];
  1290. if (clauses) {
  1291. let remainingLength = len;
  1292. for (let i = 0; i < clauses.length; i++) {
  1293. if (clauses[i]) {
  1294. let clauseLength = clauses[i].length || 0;
  1295. // Make sure the total clauses length is not bigger than that of the
  1296. // composition string.
  1297. if (clauseLength > remainingLength) {
  1298. clauseLength = remainingLength;
  1299. }
  1300. remainingLength -= clauseLength;
  1301. clauseLens.push(clauseLength);
  1302. clauseAttrs.push(this._clauseAttrMap[clauses[i].selectionType] ||
  1303. Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE);
  1304. }
  1305. }
  1306. // If the total clauses length is less than that of the composition
  1307. // string, extend the last clause to the end of the composition string.
  1308. if (remainingLength > 0) {
  1309. clauseLens[clauseLens.length - 1] += remainingLength;
  1310. }
  1311. } else {
  1312. clauseLens.push(len);
  1313. clauseAttrs.push(Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE);
  1314. }
  1315. let win = element.ownerDocument.defaultView;
  1316. let tip = WindowMap.getTextInputProcessor(win);
  1317. if (!tip) {
  1318. return;
  1319. }
  1320. // Update the composing text.
  1321. tip.setPendingCompositionString(text);
  1322. for (var i = 0; i < clauseLens.length; i++) {
  1323. if (!clauseLens[i]) {
  1324. continue;
  1325. }
  1326. tip.appendClauseToPendingComposition(clauseLens[i], clauseAttrs[i]);
  1327. }
  1328. if (cursor >= 0) {
  1329. tip.setCaretInPendingComposition(cursor);
  1330. }
  1331. if (!dict) {
  1332. this._isStarted = tip.flushPendingComposition();
  1333. } else {
  1334. let keyboardEvent = new win.KeyboardEvent("", dict);
  1335. let flags = dict.flags;
  1336. this._isStarted = tip.flushPendingComposition(keyboardEvent, flags);
  1337. }
  1338. if (this._isStarted) {
  1339. this._tip = tip;
  1340. this._KeyboardEventForWin = win.KeyboardEvent;
  1341. }
  1342. },
  1343. endComposition: function cm_endComposition(text, dict) {
  1344. if (!this._isStarted) {
  1345. return;
  1346. }
  1347. let tip = this._tip;
  1348. if (!tip) {
  1349. return;
  1350. }
  1351. text = text || "";
  1352. if (!dict) {
  1353. tip.commitCompositionWith(text);
  1354. } else {
  1355. let keyboardEvent = new this._KeyboardEventForWin("", dict);
  1356. let flags = dict.flags;
  1357. tip.commitCompositionWith(text, keyboardEvent, flags);
  1358. }
  1359. this._isStarted = false;
  1360. this._tip = null;
  1361. this._KeyboardEventForWin = null;
  1362. },
  1363. // Composition ends due to external actions.
  1364. onCompositionEnd: function cm_onCompositionEnd() {
  1365. if (!this._isStarted) {
  1366. return;
  1367. }
  1368. this._isStarted = false;
  1369. this._tip = null;
  1370. this._KeyboardEventForWin = null;
  1371. }
  1372. };