browser/metro/base/content/contenthandlers/FormHelper.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
     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
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 let Ci = Components.interfaces;
     7 let Cc = Components.classes;
     9 dump("### FormHelper.js loaded\n");
    11 let HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
    12 let HTMLInputElement = Ci.nsIDOMHTMLInputElement;
    13 let HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
    14 let HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
    15 let HTMLDocument = Ci.nsIDOMHTMLDocument;
    16 let HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
    17 let HTMLBodyElement = Ci.nsIDOMHTMLBodyElement;
    18 let HTMLLabelElement = Ci.nsIDOMHTMLLabelElement;
    19 let HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
    20 let HTMLOptGroupElement = Ci.nsIDOMHTMLOptGroupElement;
    21 let HTMLOptionElement = Ci.nsIDOMHTMLOptionElement;
    22 let XULMenuListElement = Ci.nsIDOMXULMenuListElement;
    24 /**
    25  * Responsible of navigation between forms fields and of the opening of the assistant
    26  */
    27 function FormAssistant() {
    28   addMessageListener("FormAssist:Closed", this);
    29   addMessageListener("FormAssist:ChoiceSelect", this);
    30   addMessageListener("FormAssist:ChoiceChange", this);
    31   addMessageListener("FormAssist:AutoComplete", this);
    32   addMessageListener("FormAssist:Update", this);
    34   /* Listen text events in order to update the autocomplete suggestions as soon
    35    * a key is entered on device
    36    */
    37   addEventListener("text", this, false);
    38   addEventListener("focus", this, true);
    39   addEventListener("blur", this, true);
    40   addEventListener("pageshow", this, false);
    41   addEventListener("pagehide", this, false);
    42   addEventListener("submit", this, false);
    43 }
    45 FormAssistant.prototype = {
    46   _els: Cc["@mozilla.org/eventlistenerservice;1"].getService(Ci.nsIEventListenerService),
    47   _open: false,
    48   _focusSync: false,
    49   _debugEvents: false,
    50   _selectWrapper: null,
    51   _currentElement: null,
    52   invalidSubmit: false,
    54   get focusSync() {
    55     return this._focusSync;
    56   },
    58   set focusSync(aVal) {
    59     this._focusSync = aVal;
    60   },
    62   get currentElement() {
    63     return this._currentElement;
    64   },
    66   set currentElement(aElement) {
    67     if (!aElement || !this._isVisibleElement(aElement)) {
    68       return null;
    69     }
    71     this._currentElement = aElement;
    72     gFocusManager.setFocus(this._currentElement, Ci.nsIFocusManager.FLAG_NOSCROLL);
    74     // To ensure we get the current caret positionning of the focused
    75     // element we need to delayed a bit the event
    76     this._executeDelayed(function(self) {
    77       // Bug 640870
    78       // Sometimes the element inner frame get destroyed while the element
    79       // receive the focus because the display is turned to 'none' for
    80       // example, in this "fun" case just do nothing if the element is hidden
    81       if (self._isVisibleElement(gFocusManager.focusedElement)) {
    82         self._sendJsonMsgWrapper("FormAssist:Show");
    83       }
    84     });
    85     return this._currentElement;
    86   },
    88   open: function formHelperOpen(aElement, aEvent) {
    89     // If the click is on an option element we want to check if the parent
    90     // is a valid target.
    91     if (aElement instanceof HTMLOptionElement &&
    92         aElement.parentNode instanceof HTMLSelectElement &&
    93         !aElement.disabled) {
    94       aElement = aElement.parentNode;
    95     }
    97     // Don't show the formhelper popup for multi-select boxes, except for touch.
    98     if (aElement instanceof HTMLSelectElement && aEvent) {
    99       if ((aElement.multiple || aElement.size > 1) &&
   100           aEvent.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
   101         return false;
   102       }
   103       // Don't fire mouse events on selects; see bug 685197.
   104       aEvent.preventDefault();
   105       aEvent.stopPropagation();
   106     }
   108     // The form assistant will close if a click happen:
   109     // * outside of the scope of the form helper
   110     // * hover a button of type=[image|submit]
   111     // * hover a disabled element
   112     if (!this._isValidElement(aElement)) {
   113       let passiveButtons = { button: true, checkbox: true, file: true, radio: true, reset: true };
   114       if ((aElement instanceof HTMLInputElement || aElement instanceof HTMLButtonElement) &&
   115           passiveButtons[aElement.type] && !aElement.disabled)
   116         return false;
   117       return this.close();
   118     }
   120     // Look for a top editable element
   121     if (this._isEditable(aElement)) {
   122       aElement = this._getTopLevelEditable(aElement);
   123     }
   125     // We only work with choice lists or elements with autocomplete suggestions
   126     if (!this._isSelectElement(aElement) &&
   127         !this._isAutocomplete(aElement)) {
   128       return this.close();
   129     }
   131     // Don't re-open when navigating to avoid repopulating list when changing selection.
   132     if (this._isAutocomplete(aElement) && this._open && Util.isNavigationKey(aEvent.keyCode)) {
   133       return false;
   134     }
   136     // Enable the assistant
   137     this.currentElement = aElement;
   138     return this._open = true;
   139   },
   141   close: function close() {
   142     if (this._open) {
   143       this._currentElement = null;
   144       sendAsyncMessage("FormAssist:Hide", { });
   145       this._open = false;
   146     }
   148     return this._open;
   149   },
   151   receiveMessage: function receiveMessage(aMessage) {
   152     if (this._debugEvents) Util.dumpLn(aMessage.name);
   154     let currentElement = this.currentElement;
   155     if ((!this._isAutocomplete(currentElement) &&
   156          !getWrapperForElement(currentElement)) ||
   157         !currentElement) {
   158       return;
   159     }
   161     let json = aMessage.json;
   163     switch (aMessage.name) {
   164       case "FormAssist:ChoiceSelect": {
   165         this._selectWrapper = getWrapperForElement(currentElement);
   166         this._selectWrapper.select(json.index, json.selected);
   167         break;
   168       }
   170       case "FormAssist:ChoiceChange": {
   171         // ChoiceChange could happened once we have move to another element or
   172         // to nothing, so we should keep the used wrapper in mind.
   173         this._selectWrapper = getWrapperForElement(currentElement);
   174         this._selectWrapper.fireOnChange();
   176         // New elements can be shown when a select is updated so we need to
   177         // reconstruct the inner elements array and to take care of possible
   178         // focus change, this is why we use "self.currentElement" instead of
   179         // using directly "currentElement".
   180         this._executeDelayed(function(self) {
   181           let currentElement = self.currentElement;
   182           if (!currentElement)
   183             return;
   184           self._currentElement = currentElement;
   185         });
   186         break;
   187       }
   189       case "FormAssist:AutoComplete": {
   190         try {
   191           currentElement = currentElement.QueryInterface(Ci.nsIDOMNSEditableElement);
   192           let imeEditor = currentElement.editor.QueryInterface(Ci.nsIEditorIMESupport);
   193           if (imeEditor.composing)
   194             imeEditor.forceCompositionEnd();
   195         }
   196         catch(e) {}
   198         currentElement.value = json.value;
   200         let event = currentElement.ownerDocument.createEvent("Events");
   201         event.initEvent("DOMAutoComplete", true, true);
   202         currentElement.dispatchEvent(event);
   203         break;
   204       }
   206       case "FormAssist:Closed":
   207         currentElement.blur();
   208         this._open = false;
   209         break;
   211       case "FormAssist:Update":
   212         this._sendJsonMsgWrapper("FormAssist:Show");
   213         break;
   214     }
   215   },
   217   handleEvent: function formHelperHandleEvent(aEvent) {
   218     if (this._debugEvents) Util.dumpLn(aEvent.type, this.currentElement);
   219     // focus changes should be taken into account only if the user has done a
   220     // manual operation like manually clicking
   221     let shouldIgnoreFocus = (aEvent.type == "focus" && !this._open && !this.focusSync);
   222     if ((!this._open && aEvent.type != "focus") || shouldIgnoreFocus) {
   223       return;
   224     }
   226     let currentElement = this.currentElement;
   227     switch (aEvent.type) {
   228       case "submit":
   229         // submit is a final action and the form assistant should be closed
   230         this.close();
   231         break;
   233       case "pagehide":
   234       case "pageshow":
   235         // When reacting to a page show/hide, if the focus is different this
   236         // could mean the web page has dramatically changed because of
   237         // an Ajax change based on fragment identifier
   238         if (gFocusManager.focusedElement != currentElement)
   239           this.close();
   240         break;
   242       case "focus":
   243         let focusedElement =
   244           gFocusManager.getFocusedElementForWindow(content, true, {}) ||
   245           aEvent.target;
   247         // If a body element is editable and the body is the child of an
   248         // iframe we can assume this is an advanced HTML editor, so let's
   249         // redirect the form helper selection to the iframe element
   250         if (focusedElement && this._isEditable(focusedElement)) {
   251           let editableElement = this._getTopLevelEditable(focusedElement);
   252           if (this._isValidElement(editableElement)) {
   253             this._executeDelayed(function(self) {
   254               self.open(editableElement);
   255             });
   256           }
   257           return;
   258         }
   260         // if an element is focused while we're closed but the element can be handle
   261         // by the assistant, try to activate it (only during mouseup)
   262         if (!currentElement) {
   263           if (focusedElement && this._isValidElement(focusedElement)) {
   264             this._executeDelayed(function(self) {
   265               self.open(focusedElement);
   266             });
   267           }
   268           return;
   269         }
   271         if (this._currentElement != focusedElement)
   272           this.currentElement = focusedElement;
   273         break;
   275       case "blur":
   276         content.setTimeout(function(self) {
   277           if (!self._open)
   278             return;
   280           // If the blurring causes focus be in no other element,
   281           // we should close the form assistant.
   282           let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {});
   283           if (!focusedElement)
   284             self.close();
   285         }, 0, this);
   286         break;
   288       case "text":
   289         if (this._isAutocomplete(aEvent.target)) {
   290           this._sendJsonMsgWrapper("FormAssist:AutoComplete");
   291         }
   292         break;
   293     }
   294   },
   296   _executeDelayed: function formHelperExecuteSoon(aCallback) {
   297     let self = this;
   298     let timer = new Util.Timeout(function() {
   299       aCallback(self);
   300     });
   301     timer.once(0);
   302   },
   304   _isEditable: function formHelperIsEditable(aElement) {
   305     if (!aElement)
   306       return false;
   307     let canEdit = false;
   309     if (aElement.isContentEditable || aElement.designMode == "on") {
   310       canEdit = true;
   311     } else if (aElement instanceof HTMLIFrameElement &&
   312                (aElement.contentDocument.body.isContentEditable ||
   313                 aElement.contentDocument.designMode == "on")) {
   314       canEdit = true;
   315     } else {
   316       canEdit = aElement.ownerDocument && aElement.ownerDocument.designMode == "on";
   317     }
   319     return canEdit;
   320   },
   322   _getTopLevelEditable: function formHelperGetTopLevelEditable(aElement) {
   323     if (!(aElement instanceof HTMLIFrameElement)) {
   324       let element = aElement;
   326       // Retrieve the top element that is editable
   327       if (element instanceof HTMLHtmlElement)
   328         element = element.ownerDocument.body;
   329       else if (element instanceof HTMLDocument)
   330         element = element.body;
   332       while (element && !this._isEditable(element))
   333         element = element.parentNode;
   335       // Return the container frame if we are into a nested editable frame
   336       if (element && element instanceof HTMLBodyElement && element.ownerDocument.defaultView != content.document.defaultView)
   337         return element.ownerDocument.defaultView.frameElement;
   338     }
   340     return aElement;
   341   },
   343   _isAutocomplete: function formHelperIsAutocomplete(aElement) {
   344     if (aElement instanceof HTMLInputElement) {
   345       if (aElement.getAttribute("type") == "password")
   346         return false;
   348       let autocomplete = aElement.getAttribute("autocomplete");
   349       let allowedValues = ["off", "false", "disabled"];
   350       if (allowedValues.indexOf(autocomplete) == -1)
   351         return true;
   352     }
   354     return false;
   355   },
   357   /*
   358    * This function is similar to getListSuggestions from
   359    * components/satchel/src/nsInputListAutoComplete.js but sadly this one is
   360    * used by the autocomplete.xml binding which is not in used in fennec
   361    */
   362   _getListSuggestions: function formHelperGetListSuggestions(aElement) {
   363     if (!(aElement instanceof HTMLInputElement) || !aElement.list)
   364       return [];
   366     let suggestions = [];
   367     let filter = !aElement.hasAttribute("mozNoFilter");
   368     let lowerFieldValue = aElement.value.toLowerCase();
   370     let options = aElement.list.options;
   371     let length = options.length;
   372     for (let i = 0; i < length; i++) {
   373       let item = options.item(i);
   375       let label = item.value;
   376       if (item.label)
   377         label = item.label;
   378       else if (item.text)
   379         label = item.text;
   381       if (filter && label.toLowerCase().indexOf(lowerFieldValue) == -1)
   382         continue;
   383        suggestions.push({ label: label, value: item.value });
   384     }
   386     return suggestions;
   387   },
   389   _isValidElement: function formHelperIsValidElement(aElement) {
   390     if (!aElement.getAttribute)
   391       return false;
   393     let formExceptions = { button: true, checkbox: true, file: true, image: true, radio: true, reset: true, submit: true };
   394     if (aElement instanceof HTMLInputElement && formExceptions[aElement.type])
   395       return false;
   397     if (aElement instanceof HTMLButtonElement ||
   398         (aElement.getAttribute("role") == "button" && aElement.hasAttribute("tabindex")))
   399       return false;
   401     return this._isNavigableElement(aElement) && this._isVisibleElement(aElement);
   402   },
   404   _isNavigableElement: function formHelperIsNavigableElement(aElement) {
   405     if (aElement.disabled || aElement.getAttribute("tabindex") == "-1")
   406       return false;
   408     if (aElement.getAttribute("role") == "button" && aElement.hasAttribute("tabindex"))
   409       return true;
   411     if (this._isSelectElement(aElement) || aElement instanceof HTMLTextAreaElement)
   412       return true;
   414     if (aElement instanceof HTMLInputElement || aElement instanceof HTMLButtonElement)
   415       return !(aElement.type == "hidden");
   417     return this._isEditable(aElement);
   418   },
   420   _isVisibleElement: function formHelperIsVisibleElement(aElement) {
   421     if (!aElement || !aElement.ownerDocument) {
   422       return false;
   423     }
   424     let style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, null);
   425     if (!style)
   426       return false;
   428     let isVisible = (style.getPropertyValue("visibility") != "hidden");
   429     let isOpaque = (style.getPropertyValue("opacity") != 0);
   431     let rect = aElement.getBoundingClientRect();
   433     // Since the only way to show a drop-down menu for a select when the form
   434     // assistant is enabled is to return true here, a select is allowed to have
   435     // an opacity to 0 in order to let web developpers add a custom design on
   436     // top of it. This is less important to use the form assistant for the
   437     // other types of fields because even if the form assistant won't fired,
   438     // the focus will be in and a VKB will popup if needed
   439     return isVisible && (isOpaque || this._isSelectElement(aElement)) && (rect.height != 0 || rect.width != 0);
   440   },
   442   _isSelectElement: function formHelperIsSelectElement(aElement) {
   443     return (aElement instanceof HTMLSelectElement || aElement instanceof XULMenuListElement);
   444   },
   446   /** Caret is used to input text for this element. */
   447   _getCaretRect: function _formHelperGetCaretRect() {
   448     let element = this.currentElement;
   449     let focusedElement = gFocusManager.getFocusedElementForWindow(content, true, {});
   450     if (element && (element.mozIsTextField && element.mozIsTextField(false) ||
   451         element instanceof HTMLTextAreaElement) && focusedElement == element && this._isVisibleElement(element)) {
   452       let utils = Util.getWindowUtils(element.ownerDocument.defaultView);
   453       let rect = utils.sendQueryContentEvent(utils.QUERY_CARET_RECT, element.selectionEnd, 0, 0, 0,
   454                                              utils.QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK);
   455       if (rect) {
   456         let scroll = ContentScroll.getScrollOffset(element.ownerDocument.defaultView);
   457         return new Rect(scroll.x + rect.left, scroll.y + rect.top, rect.width, rect.height);
   458       }
   459     }
   461     return new Rect(0, 0, 0, 0);
   462   },
   464   /** Gets a rect bounding important parts of the element that must be seen when assisting. */
   465   _getRect: function _formHelperGetRect(aOptions={}) {
   466     const kDistanceMax = 100;
   467     let element = this.currentElement;
   468     let elRect = getBoundingContentRect(element);
   470     if (aOptions.alignToLabel) {
   471       let labels = this._getLabels();
   472       for (let i=0; i<labels.length; i++) {
   473         let labelRect = labels[i].rect;
   474         if (labelRect.left < elRect.left) {
   475           let isClose = Math.abs(labelRect.left - elRect.left) - labelRect.width < kDistanceMax &&
   476                         Math.abs(labelRect.top - elRect.top) - labelRect.height < kDistanceMax;
   477           if (isClose) {
   478             let width = labelRect.width + elRect.width + (elRect.left - labelRect.left - labelRect.width);
   479             return new Rect(labelRect.left, labelRect.top, width, elRect.height).expandToIntegers();
   480           }
   481         }
   482       }
   483     }
   484     return elRect;
   485   },
   487   _getLabels: function formHelperGetLabels() {
   488     let associatedLabels = [];
   489     if (!this.currentElement)
   490       return associatedLabels;
   491     let element = this.currentElement;
   492     let labels = element.ownerDocument.getElementsByTagName("label");
   493     for (let i=0; i<labels.length; i++) {
   494       let label = labels[i];
   495       if ((label.control == element || label.getAttribute("for") == element.id) && this._isVisibleElement(label)) {
   496         associatedLabels.push({
   497           rect: getBoundingContentRect(label),
   498           title: label.textContent
   499         });
   500       }
   501     }
   503     return associatedLabels;
   504   },
   506   _sendJsonMsgWrapper: function (aMsg) {
   507     let json = this._getJSON();
   508     if (json) {
   509       sendAsyncMessage(aMsg, json);
   510     }
   511   },
   513   _getJSON: function() {
   514     let element = this.currentElement;
   515     if (!element) {
   516       return null;
   517     }
   518     let choices = getListForElement(element);
   519     let editable = (element instanceof HTMLInputElement && element.mozIsTextField(false)) || this._isEditable(element);
   521     let labels = this._getLabels();
   522     return {
   523       current: {
   524         id: element.id,
   525         name: element.name,
   526         title: labels.length ? labels[0].title : "",
   527         value: element.value,
   528         maxLength: element.maxLength,
   529         type: (element.getAttribute("type") || "").toLowerCase(),
   530         choices: choices,
   531         isAutocomplete: this._isAutocomplete(element),
   532         list: this._getListSuggestions(element),
   533         rect: this._getRect(),
   534         caretRect: this._getCaretRect(),
   535         editable: editable
   536       },
   537     };
   538   },
   540   /**
   541    * For each radio button group, remove all but the checked button
   542    * if there is one, or the first button otherwise.
   543    */
   544   _filterRadioButtons: function(aNodes) {
   545     // First pass: Find the checked or first element in each group.
   546     let chosenRadios = {};
   547     for (let i=0; i < aNodes.length; i++) {
   548       let node = aNodes[i];
   549       if (node.type == "radio" && (!chosenRadios.hasOwnProperty(node.name) || node.checked))
   550         chosenRadios[node.name] = node;
   551     }
   553     // Second pass: Exclude all other radio buttons from the list.
   554     let result = [];
   555     for (let i=0; i < aNodes.length; i++) {
   556       let node = aNodes[i];
   557       if (node.type == "radio" && chosenRadios[node.name] != node)
   558         continue;
   559       result.push(node);
   560     }
   561     return result;
   562   }
   563 };
   564 this.FormAssistant = FormAssistant;
   567 /******************************************************************************
   568  * The next classes wraps some forms elements such as different type of list to
   569  * abstract the difference between html and xul element while manipulating them
   570  *  - SelectWrapper   : <html:select>
   571  *  - MenulistWrapper : <xul:menulist>
   572  *****************************************************************************/
   574 function getWrapperForElement(aElement) {
   575   let wrapper = null;
   576   if (aElement instanceof HTMLSelectElement) {
   577     wrapper = new SelectWrapper(aElement);
   578   }
   579   else if (aElement instanceof XULMenuListElement) {
   580     wrapper = new MenulistWrapper(aElement);
   581   }
   583   return wrapper;
   584 }
   586 function getListForElement(aElement) {
   587   let wrapper = getWrapperForElement(aElement);
   588   if (!wrapper)
   589     return null;
   591   let optionIndex = 0;
   592   let result = {
   593     multiple: wrapper.getMultiple(),
   594     choices: []
   595   };
   597   // Build up a flat JSON array of the choices. In HTML, it's possible for select element choices
   598   // to be under a group header (but not recursively). We distinguish between headers and entries
   599   // using the boolean "list.group".
   600   // XXX If possible, this would be a great candidate for tracing.
   601   let children = wrapper.getChildren();
   602   for (let i = 0; i < children.length; i++) {
   603     let child = children[i];
   604     if (wrapper.isGroup(child)) {
   605       // This is the group element. Add an entry in the choices that says that the following
   606       // elements are a member of this group.
   607       result.choices.push({ group: true,
   608                             text: child.label || child.firstChild.data,
   609                             disabled: child.disabled
   610                           });
   611       let subchildren = child.children;
   612       for (let j = 0; j < subchildren.length; j++) {
   613         let subchild = subchildren[j];
   614         result.choices.push({
   615           group: false,
   616           inGroup: true,
   617           text: wrapper.getText(subchild),
   618           disabled: child.disabled || subchild.disabled,
   619           selected: subchild.selected,
   620           optionIndex: optionIndex++
   621         });
   622       }
   623     }
   624     else if (wrapper.isOption(child)) {
   625       // This is a regular choice under no group.
   626       result.choices.push({
   627         group: false,
   628         inGroup: false,
   629         text: wrapper.getText(child),
   630         disabled: child.disabled,
   631         selected: child.selected,
   632         optionIndex: optionIndex++
   633       });
   634     }
   635   }
   637   return result;
   638 }
   641 function SelectWrapper(aControl) {
   642   this._control = aControl;
   643 }
   645 SelectWrapper.prototype = {
   646   getSelectedIndex: function() {
   647     return this._control.selectedIndex;
   648   },
   650   getMultiple: function() {
   651     return this._control.multiple;
   652   },
   654   getOptions: function() {
   655     return this._control.options;
   656   },
   658   getChildren: function() {
   659     return this._control.children;
   660   },
   662   getText: function(aChild) {
   663     return aChild.text;
   664   },
   666   isOption: function(aChild) {
   667     return aChild instanceof HTMLOptionElement;
   668   },
   670   isGroup: function(aChild) {
   671     return aChild instanceof HTMLOptGroupElement;
   672   },
   674   select: function(aIndex, aSelected) {
   675     let options = this._control.options;
   676     if (this.getMultiple())
   677       options[aIndex].selected = aSelected;
   678     else
   679       options.selectedIndex = aIndex;
   680   },
   682   fireOnChange: function() {
   683     let control = this._control;
   684     let evt = this._control.ownerDocument.createEvent("Events");
   685     evt.initEvent("change", true, true, this._control.ownerDocument.defaultView, 0,
   686                   false, false,
   687                   false, false, null);
   688     content.setTimeout(function() {
   689       control.dispatchEvent(evt);
   690     }, 0);
   691   }
   692 };
   693 this.SelectWrapper = SelectWrapper;
   696 // bug 559792
   697 // Use wrappedJSObject when control is in content for extra protection
   698 function MenulistWrapper(aControl) {
   699   this._control = aControl;
   700 }
   702 MenulistWrapper.prototype = {
   703   getSelectedIndex: function() {
   704     let control = this._control.wrappedJSObject || this._control;
   705     let result = control.selectedIndex;
   706     return ((typeof result == "number" && !isNaN(result)) ? result : -1);
   707   },
   709   getMultiple: function() {
   710     return false;
   711   },
   713   getOptions: function() {
   714     let control = this._control.wrappedJSObject || this._control;
   715     return control.menupopup.children;
   716   },
   718   getChildren: function() {
   719     let control = this._control.wrappedJSObject || this._control;
   720     return control.menupopup.children;
   721   },
   723   getText: function(aChild) {
   724     return aChild.label;
   725   },
   727   isOption: function(aChild) {
   728     return aChild instanceof Ci.nsIDOMXULSelectControlItemElement;
   729   },
   731   isGroup: function(aChild) {
   732     return false;
   733   },
   735   select: function(aIndex, aSelected) {
   736     let control = this._control.wrappedJSObject || this._control;
   737     control.selectedIndex = aIndex;
   738   },
   740   fireOnChange: function() {
   741     let control = this._control;
   742     let evt = document.createEvent("XULCommandEvent");
   743     evt.initCommandEvent("command", true, true, window, 0,
   744                          false, false,
   745                          false, false, null);
   746     content.setTimeout(function() {
   747       control.dispatchEvent(evt);
   748     }, 0);
   749   }
   750 };
   751 this.MenulistWrapper = MenulistWrapper;

mercurial