michael@0: // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: let Ci = Components.interfaces; michael@0: let Cc = Components.classes; michael@0: michael@0: dump("### FormHelper.js loaded\n"); michael@0: michael@0: let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement; michael@0: let HTMLInputElement = Ci.nsIDOMHTMLInputElement; michael@0: let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement; michael@0: let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement; michael@0: let HTMLDocument = Ci.nsIDOMHTMLDocument; michael@0: let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement; michael@0: let HTMLBodyElement = Ci.nsIDOMHTMLBodyElement; michael@0: let HTMLLabelElement = Ci.nsIDOMHTMLLabelElement; michael@0: let HTMLButtonElement = Ci.nsIDOMHTMLButtonElement; michael@0: let HTMLOptGroupElement = Ci.nsIDOMHTMLOptGroupElement; michael@0: let HTMLOptionElement = Ci.nsIDOMHTMLOptionElement; michael@0: let XULMenuListElement = Ci.nsIDOMXULMenuListElement; michael@0: michael@0: /** michael@0: * Responsible of navigation between forms fields and of the opening of the assistant michael@0: */ michael@0: function FormAssistant() { michael@0: addMessageListener("FormAssist:Closed", this); michael@0: addMessageListener("FormAssist:ChoiceSelect", this); michael@0: addMessageListener("FormAssist:ChoiceChange", this); michael@0: addMessageListener("FormAssist:AutoComplete", this); michael@0: addMessageListener("FormAssist:Update", this); michael@0: michael@0: /* Listen text events in order to update the autocomplete suggestions as soon michael@0: * a key is entered on device michael@0: */ michael@0: addEventListener("text", this, false); michael@0: addEventListener("focus", this, true); michael@0: addEventListener("blur", this, true); michael@0: addEventListener("pageshow", this, false); michael@0: addEventListener("pagehide", this, false); michael@0: addEventListener("submit", this, false); michael@0: } michael@0: michael@0: FormAssistant.prototype = { michael@0: _els: Cc["@mozilla.org/eventlistenerservice;1"].getService(Ci.nsIEventListenerService), michael@0: _open: false, michael@0: _focusSync: false, michael@0: _debugEvents: false, michael@0: _selectWrapper: null, michael@0: _currentElement: null, michael@0: invalidSubmit: false, michael@0: michael@0: get focusSync() { michael@0: return this._focusSync; michael@0: }, michael@0: michael@0: set focusSync(aVal) { michael@0: this._focusSync = aVal; michael@0: }, michael@0: michael@0: get currentElement() { michael@0: return this._currentElement; michael@0: }, michael@0: michael@0: set currentElement(aElement) { michael@0: if (!aElement || !this._isVisibleElement(aElement)) { michael@0: return null; michael@0: } michael@0: michael@0: this._currentElement = aElement; michael@0: gFocusManager.setFocus(this._currentElement, Ci.nsIFocusManager.FLAG_NOSCROLL); michael@0: michael@0: // To ensure we get the current caret positionning of the focused michael@0: // element we need to delayed a bit the event michael@0: this._executeDelayed(function(self) { michael@0: // Bug 640870 michael@0: // Sometimes the element inner frame get destroyed while the element michael@0: // receive the focus because the display is turned to 'none' for michael@0: // example, in this "fun" case just do nothing if the element is hidden michael@0: if (self._isVisibleElement(gFocusManager.focusedElement)) { michael@0: self._sendJsonMsgWrapper("FormAssist:Show"); michael@0: } michael@0: }); michael@0: return this._currentElement; michael@0: }, michael@0: michael@0: open: function formHelperOpen(aElement, aEvent) { michael@0: // If the click is on an option element we want to check if the parent michael@0: // is a valid target. michael@0: if (aElement instanceof HTMLOptionElement && michael@0: aElement.parentNode instanceof HTMLSelectElement && michael@0: !aElement.disabled) { michael@0: aElement = aElement.parentNode; michael@0: } michael@0: michael@0: // Don't show the formhelper popup for multi-select boxes, except for touch. michael@0: if (aElement instanceof HTMLSelectElement && aEvent) { michael@0: if ((aElement.multiple || aElement.size > 1) && michael@0: aEvent.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) { michael@0: return false; michael@0: } michael@0: // Don't fire mouse events on selects; see bug 685197. michael@0: aEvent.preventDefault(); michael@0: aEvent.stopPropagation(); michael@0: } michael@0: michael@0: // The form assistant will close if a click happen: michael@0: // * outside of the scope of the form helper michael@0: // * hover a button of type=[image|submit] michael@0: // * hover a disabled element michael@0: if (!this._isValidElement(aElement)) { michael@0: let passiveButtons = { button: true, checkbox: true, file: true, radio: true, reset: true }; michael@0: if ((aElement instanceof HTMLInputElement || aElement instanceof HTMLButtonElement) && michael@0: passiveButtons[aElement.type] && !aElement.disabled) michael@0: return false; michael@0: return this.close(); michael@0: } michael@0: michael@0: // Look for a top editable element michael@0: if (this._isEditable(aElement)) { michael@0: aElement = this._getTopLevelEditable(aElement); michael@0: } michael@0: michael@0: // We only work with choice lists or elements with autocomplete suggestions michael@0: if (!this._isSelectElement(aElement) && michael@0: !this._isAutocomplete(aElement)) { michael@0: return this.close(); michael@0: } michael@0: michael@0: // Don't re-open when navigating to avoid repopulating list when changing selection. michael@0: if (this._isAutocomplete(aElement) && this._open && Util.isNavigationKey(aEvent.keyCode)) { michael@0: return false; michael@0: } michael@0: michael@0: // Enable the assistant michael@0: this.currentElement = aElement; michael@0: return this._open = true; michael@0: }, michael@0: michael@0: close: function close() { michael@0: if (this._open) { michael@0: this._currentElement = null; michael@0: sendAsyncMessage("FormAssist:Hide", { }); michael@0: this._open = false; michael@0: } michael@0: michael@0: return this._open; michael@0: }, michael@0: michael@0: receiveMessage: function receiveMessage(aMessage) { michael@0: if (this._debugEvents) Util.dumpLn(aMessage.name); michael@0: michael@0: let currentElement = this.currentElement; michael@0: if ((!this._isAutocomplete(currentElement) && michael@0: !getWrapperForElement(currentElement)) || michael@0: !currentElement) { michael@0: return; michael@0: } michael@0: michael@0: let json = aMessage.json; michael@0: michael@0: switch (aMessage.name) { michael@0: case "FormAssist:ChoiceSelect": { michael@0: this._selectWrapper = getWrapperForElement(currentElement); michael@0: this._selectWrapper.select(json.index, json.selected); michael@0: break; michael@0: } michael@0: michael@0: case "FormAssist:ChoiceChange": { michael@0: // ChoiceChange could happened once we have move to another element or michael@0: // to nothing, so we should keep the used wrapper in mind. michael@0: this._selectWrapper = getWrapperForElement(currentElement); michael@0: this._selectWrapper.fireOnChange(); michael@0: michael@0: // New elements can be shown when a select is updated so we need to michael@0: // reconstruct the inner elements array and to take care of possible michael@0: // focus change, this is why we use "self.currentElement" instead of michael@0: // using directly "currentElement". michael@0: this._executeDelayed(function(self) { michael@0: let currentElement = self.currentElement; michael@0: if (!currentElement) michael@0: return; michael@0: self._currentElement = currentElement; michael@0: }); michael@0: break; michael@0: } michael@0: michael@0: case "FormAssist:AutoComplete": { michael@0: try { michael@0: currentElement = currentElement.QueryInterface(Ci.nsIDOMNSEditableElement); michael@0: let imeEditor = currentElement.editor.QueryInterface(Ci.nsIEditorIMESupport); michael@0: if (imeEditor.composing) michael@0: imeEditor.forceCompositionEnd(); michael@0: } michael@0: catch(e) {} michael@0: michael@0: currentElement.value = json.value; michael@0: michael@0: let event = currentElement.ownerDocument.createEvent("Events"); michael@0: event.initEvent("DOMAutoComplete", true, true); michael@0: currentElement.dispatchEvent(event); michael@0: break; michael@0: } michael@0: michael@0: case "FormAssist:Closed": michael@0: currentElement.blur(); michael@0: this._open = false; michael@0: break; michael@0: michael@0: case "FormAssist:Update": michael@0: this._sendJsonMsgWrapper("FormAssist:Show"); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: handleEvent: function formHelperHandleEvent(aEvent) { michael@0: if (this._debugEvents) Util.dumpLn(aEvent.type, this.currentElement); michael@0: // focus changes should be taken into account only if the user has done a michael@0: // manual operation like manually clicking michael@0: let shouldIgnoreFocus = (aEvent.type == "focus" && !this._open && !this.focusSync); michael@0: if ((!this._open && aEvent.type != "focus") || shouldIgnoreFocus) { michael@0: return; michael@0: } michael@0: michael@0: let currentElement = this.currentElement; michael@0: switch (aEvent.type) { michael@0: case "submit": michael@0: // submit is a final action and the form assistant should be closed michael@0: this.close(); michael@0: break; michael@0: michael@0: case "pagehide": michael@0: case "pageshow": michael@0: // When reacting to a page show/hide, if the focus is different this michael@0: // could mean the web page has dramatically changed because of michael@0: // an Ajax change based on fragment identifier michael@0: if (gFocusManager.focusedElement != currentElement) michael@0: this.close(); michael@0: break; michael@0: michael@0: case "focus": michael@0: let focusedElement = michael@0: gFocusManager.getFocusedElementForWindow(content, true, {}) || michael@0: aEvent.target; michael@0: michael@0: // If a body element is editable and the body is the child of an michael@0: // iframe we can assume this is an advanced HTML editor, so let's michael@0: // redirect the form helper selection to the iframe element michael@0: if (focusedElement && this._isEditable(focusedElement)) { michael@0: let editableElement = this._getTopLevelEditable(focusedElement); michael@0: if (this._isValidElement(editableElement)) { michael@0: this._executeDelayed(function(self) { michael@0: self.open(editableElement); michael@0: }); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // if an element is focused while we're closed but the element can be handle michael@0: // by the assistant, try to activate it (only during mouseup) michael@0: if (!currentElement) { michael@0: if (focusedElement && this._isValidElement(focusedElement)) { michael@0: this._executeDelayed(function(self) { michael@0: self.open(focusedElement); michael@0: }); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (this._currentElement != focusedElement) michael@0: this.currentElement = focusedElement; michael@0: break; michael@0: michael@0: case "blur": michael@0: content.setTimeout(function(self) { michael@0: if (!self._open) michael@0: return; michael@0: michael@0: // If the blurring causes focus be in no other element, michael@0: // we should close the form assistant. michael@0: let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {}); michael@0: if (!focusedElement) michael@0: self.close(); michael@0: }, 0, this); michael@0: break; michael@0: michael@0: case "text": michael@0: if (this._isAutocomplete(aEvent.target)) { michael@0: this._sendJsonMsgWrapper("FormAssist:AutoComplete"); michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _executeDelayed: function formHelperExecuteSoon(aCallback) { michael@0: let self = this; michael@0: let timer = new Util.Timeout(function() { michael@0: aCallback(self); michael@0: }); michael@0: timer.once(0); michael@0: }, michael@0: michael@0: _isEditable: function formHelperIsEditable(aElement) { michael@0: if (!aElement) michael@0: return false; michael@0: let canEdit = false; michael@0: michael@0: if (aElement.isContentEditable || aElement.designMode == "on") { michael@0: canEdit = true; michael@0: } else if (aElement instanceof HTMLIFrameElement && michael@0: (aElement.contentDocument.body.isContentEditable || michael@0: aElement.contentDocument.designMode == "on")) { michael@0: canEdit = true; michael@0: } else { michael@0: canEdit = aElement.ownerDocument && aElement.ownerDocument.designMode == "on"; michael@0: } michael@0: michael@0: return canEdit; michael@0: }, michael@0: michael@0: _getTopLevelEditable: function formHelperGetTopLevelEditable(aElement) { michael@0: if (!(aElement instanceof HTMLIFrameElement)) { michael@0: let element = aElement; michael@0: michael@0: // Retrieve the top element that is editable michael@0: if (element instanceof HTMLHtmlElement) michael@0: element = element.ownerDocument.body; michael@0: else if (element instanceof HTMLDocument) michael@0: element = element.body; michael@0: michael@0: while (element && !this._isEditable(element)) michael@0: element = element.parentNode; michael@0: michael@0: // Return the container frame if we are into a nested editable frame michael@0: if (element && element instanceof HTMLBodyElement && element.ownerDocument.defaultView != content.document.defaultView) michael@0: return element.ownerDocument.defaultView.frameElement; michael@0: } michael@0: michael@0: return aElement; michael@0: }, michael@0: michael@0: _isAutocomplete: function formHelperIsAutocomplete(aElement) { michael@0: if (aElement instanceof HTMLInputElement) { michael@0: if (aElement.getAttribute("type") == "password") michael@0: return false; michael@0: michael@0: let autocomplete = aElement.getAttribute("autocomplete"); michael@0: let allowedValues = ["off", "false", "disabled"]; michael@0: if (allowedValues.indexOf(autocomplete) == -1) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: /* michael@0: * This function is similar to getListSuggestions from michael@0: * components/satchel/src/nsInputListAutoComplete.js but sadly this one is michael@0: * used by the autocomplete.xml binding which is not in used in fennec michael@0: */ michael@0: _getListSuggestions: function formHelperGetListSuggestions(aElement) { michael@0: if (!(aElement instanceof HTMLInputElement) || !aElement.list) michael@0: return []; michael@0: michael@0: let suggestions = []; michael@0: let filter = !aElement.hasAttribute("mozNoFilter"); michael@0: let lowerFieldValue = aElement.value.toLowerCase(); michael@0: michael@0: let options = aElement.list.options; michael@0: let length = options.length; michael@0: for (let i = 0; i < length; i++) { michael@0: let item = options.item(i); michael@0: michael@0: let label = item.value; michael@0: if (item.label) michael@0: label = item.label; michael@0: else if (item.text) michael@0: label = item.text; michael@0: michael@0: if (filter && label.toLowerCase().indexOf(lowerFieldValue) == -1) michael@0: continue; michael@0: suggestions.push({ label: label, value: item.value }); michael@0: } michael@0: michael@0: return suggestions; michael@0: }, michael@0: michael@0: _isValidElement: function formHelperIsValidElement(aElement) { michael@0: if (!aElement.getAttribute) michael@0: return false; michael@0: michael@0: let formExceptions = { button: true, checkbox: true, file: true, image: true, radio: true, reset: true, submit: true }; michael@0: if (aElement instanceof HTMLInputElement && formExceptions[aElement.type]) michael@0: return false; michael@0: michael@0: if (aElement instanceof HTMLButtonElement || michael@0: (aElement.getAttribute("role") == "button" && aElement.hasAttribute("tabindex"))) michael@0: return false; michael@0: michael@0: return this._isNavigableElement(aElement) && this._isVisibleElement(aElement); michael@0: }, michael@0: michael@0: _isNavigableElement: function formHelperIsNavigableElement(aElement) { michael@0: if (aElement.disabled || aElement.getAttribute("tabindex") == "-1") michael@0: return false; michael@0: michael@0: if (aElement.getAttribute("role") == "button" && aElement.hasAttribute("tabindex")) michael@0: return true; michael@0: michael@0: if (this._isSelectElement(aElement) || aElement instanceof HTMLTextAreaElement) michael@0: return true; michael@0: michael@0: if (aElement instanceof HTMLInputElement || aElement instanceof HTMLButtonElement) michael@0: return !(aElement.type == "hidden"); michael@0: michael@0: return this._isEditable(aElement); michael@0: }, michael@0: michael@0: _isVisibleElement: function formHelperIsVisibleElement(aElement) { michael@0: if (!aElement || !aElement.ownerDocument) { michael@0: return false; michael@0: } michael@0: let style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, null); michael@0: if (!style) michael@0: return false; michael@0: michael@0: let isVisible = (style.getPropertyValue("visibility") != "hidden"); michael@0: let isOpaque = (style.getPropertyValue("opacity") != 0); michael@0: michael@0: let rect = aElement.getBoundingClientRect(); michael@0: michael@0: // Since the only way to show a drop-down menu for a select when the form michael@0: // assistant is enabled is to return true here, a select is allowed to have michael@0: // an opacity to 0 in order to let web developpers add a custom design on michael@0: // top of it. This is less important to use the form assistant for the michael@0: // other types of fields because even if the form assistant won't fired, michael@0: // the focus will be in and a VKB will popup if needed michael@0: return isVisible && (isOpaque || this._isSelectElement(aElement)) && (rect.height != 0 || rect.width != 0); michael@0: }, michael@0: michael@0: _isSelectElement: function formHelperIsSelectElement(aElement) { michael@0: return (aElement instanceof HTMLSelectElement || aElement instanceof XULMenuListElement); michael@0: }, michael@0: michael@0: /** Caret is used to input text for this element. */ michael@0: _getCaretRect: function _formHelperGetCaretRect() { michael@0: let element = this.currentElement; michael@0: let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {}); michael@0: if (element && (element.mozIsTextField && element.mozIsTextField(false) || michael@0: element instanceof HTMLTextAreaElement) && focusedElement == element && this._isVisibleElement(element)) { michael@0: let utils = Util.getWindowUtils(element.ownerDocument.defaultView); michael@0: let rect = utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, element.selectionEnd, 0, 0, 0, michael@0: utils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK); michael@0: if (rect) { michael@0: let scroll = ContentScroll.getScrollOffset(element.ownerDocument.defaultView); michael@0: return new Rect(scroll.x + rect.left, scroll.y + rect.top, rect.width, rect.height); michael@0: } michael@0: } michael@0: michael@0: return new Rect(0, 0, 0, 0); michael@0: }, michael@0: michael@0: /** Gets a rect bounding important parts of the element that must be seen when assisting. */ michael@0: _getRect: function _formHelperGetRect(aOptions={}) { michael@0: const kDistanceMax = 100; michael@0: let element = this.currentElement; michael@0: let elRect = getBoundingContentRect(element); michael@0: michael@0: if (aOptions.alignToLabel) { michael@0: let labels = this._getLabels(); michael@0: for (let i=0; i michael@0: * - MenulistWrapper : michael@0: *****************************************************************************/ michael@0: michael@0: function getWrapperForElement(aElement) { michael@0: let wrapper = null; michael@0: if (aElement instanceof HTMLSelectElement) { michael@0: wrapper = new SelectWrapper(aElement); michael@0: } michael@0: else if (aElement instanceof XULMenuListElement) { michael@0: wrapper = new MenulistWrapper(aElement); michael@0: } michael@0: michael@0: return wrapper; michael@0: } michael@0: michael@0: function getListForElement(aElement) { michael@0: let wrapper = getWrapperForElement(aElement); michael@0: if (!wrapper) michael@0: return null; michael@0: michael@0: let optionIndex = 0; michael@0: let result = { michael@0: multiple: wrapper.getMultiple(), michael@0: choices: [] michael@0: }; michael@0: michael@0: // Build up a flat JSON array of the choices. In HTML, it's possible for select element choices michael@0: // to be under a group header (but not recursively). We distinguish between headers and entries michael@0: // using the boolean "list.group". michael@0: // XXX If possible, this would be a great candidate for tracing. michael@0: let children = wrapper.getChildren(); michael@0: for (let i = 0; i < children.length; i++) { michael@0: let child = children[i]; michael@0: if (wrapper.isGroup(child)) { michael@0: // This is the group element. Add an entry in the choices that says that the following michael@0: // elements are a member of this group. michael@0: result.choices.push({ group: true, michael@0: text: child.label || child.firstChild.data, michael@0: disabled: child.disabled michael@0: }); michael@0: let subchildren = child.children; michael@0: for (let j = 0; j < subchildren.length; j++) { michael@0: let subchild = subchildren[j]; michael@0: result.choices.push({ michael@0: group: false, michael@0: inGroup: true, michael@0: text: wrapper.getText(subchild), michael@0: disabled: child.disabled || subchild.disabled, michael@0: selected: subchild.selected, michael@0: optionIndex: optionIndex++ michael@0: }); michael@0: } michael@0: } michael@0: else if (wrapper.isOption(child)) { michael@0: // This is a regular choice under no group. michael@0: result.choices.push({ michael@0: group: false, michael@0: inGroup: false, michael@0: text: wrapper.getText(child), michael@0: disabled: child.disabled, michael@0: selected: child.selected, michael@0: optionIndex: optionIndex++ michael@0: }); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: function SelectWrapper(aControl) { michael@0: this._control = aControl; michael@0: } michael@0: michael@0: SelectWrapper.prototype = { michael@0: getSelectedIndex: function() { michael@0: return this._control.selectedIndex; michael@0: }, michael@0: michael@0: getMultiple: function() { michael@0: return this._control.multiple; michael@0: }, michael@0: michael@0: getOptions: function() { michael@0: return this._control.options; michael@0: }, michael@0: michael@0: getChildren: function() { michael@0: return this._control.children; michael@0: }, michael@0: michael@0: getText: function(aChild) { michael@0: return aChild.text; michael@0: }, michael@0: michael@0: isOption: function(aChild) { michael@0: return aChild instanceof HTMLOptionElement; michael@0: }, michael@0: michael@0: isGroup: function(aChild) { michael@0: return aChild instanceof HTMLOptGroupElement; michael@0: }, michael@0: michael@0: select: function(aIndex, aSelected) { michael@0: let options = this._control.options; michael@0: if (this.getMultiple()) michael@0: options[aIndex].selected = aSelected; michael@0: else michael@0: options.selectedIndex = aIndex; michael@0: }, michael@0: michael@0: fireOnChange: function() { michael@0: let control = this._control; michael@0: let evt = this._control.ownerDocument.createEvent("Events"); michael@0: evt.initEvent("change", true, true, this._control.ownerDocument.defaultView, 0, michael@0: false, false, michael@0: false, false, null); michael@0: content.setTimeout(function() { michael@0: control.dispatchEvent(evt); michael@0: }, 0); michael@0: } michael@0: }; michael@0: this.SelectWrapper = SelectWrapper; michael@0: michael@0: michael@0: // bug 559792 michael@0: // Use wrappedJSObject when control is in content for extra protection michael@0: function MenulistWrapper(aControl) { michael@0: this._control = aControl; michael@0: } michael@0: michael@0: MenulistWrapper.prototype = { michael@0: getSelectedIndex: function() { michael@0: let control = this._control.wrappedJSObject || this._control; michael@0: let result = control.selectedIndex; michael@0: return ((typeof result == "number" && !isNaN(result)) ? result : -1); michael@0: }, michael@0: michael@0: getMultiple: function() { michael@0: return false; michael@0: }, michael@0: michael@0: getOptions: function() { michael@0: let control = this._control.wrappedJSObject || this._control; michael@0: return control.menupopup.children; michael@0: }, michael@0: michael@0: getChildren: function() { michael@0: let control = this._control.wrappedJSObject || this._control; michael@0: return control.menupopup.children; michael@0: }, michael@0: michael@0: getText: function(aChild) { michael@0: return aChild.label; michael@0: }, michael@0: michael@0: isOption: function(aChild) { michael@0: return aChild instanceof Ci.nsIDOMXULSelectControlItemElement; michael@0: }, michael@0: michael@0: isGroup: function(aChild) { michael@0: return false; michael@0: }, michael@0: michael@0: select: function(aIndex, aSelected) { michael@0: let control = this._control.wrappedJSObject || this._control; michael@0: control.selectedIndex = aIndex; michael@0: }, michael@0: michael@0: fireOnChange: function() { michael@0: let control = this._control; michael@0: let evt = document.createEvent("XULCommandEvent"); michael@0: evt.initCommandEvent("command", true, true, window, 0, michael@0: false, false, michael@0: false, false, null); michael@0: content.setTimeout(function() { michael@0: control.dispatchEvent(evt); michael@0: }, 0); michael@0: } michael@0: }; michael@0: this.MenulistWrapper = MenulistWrapper;