null null false null null null null null parseInt(this.getAttribute("shrinkdelay")) || 0 14 false false null = 1) this.mController.handleTab(); break; case KeyEvent.DOM_VK_UP: case KeyEvent.DOM_VK_DOWN: case KeyEvent.DOM_VK_PAGE_UP: case KeyEvent.DOM_VK_PAGE_DOWN: cancel = this.mController.handleKeyNavigation(aEvent.keyCode); break; } } // Handle keys we know aren't part of a shortcut, even with Alt or // Ctrl. switch (aEvent.keyCode) { case KeyEvent.DOM_VK_ESCAPE: cancel = this.mController.handleEscape(); break; case KeyEvent.DOM_VK_RETURN: if (/Mac/.test(navigator.platform)) { // Prevent the default action, since it will beep on Mac if (aEvent.metaKey) aEvent.preventDefault(); } if (this.mController.selection) { this._selectionDetails = { index: this.mController.selection.currentIndex, kind: "key" }; } cancel = this.handleEnter(aEvent); break; case KeyEvent.DOM_VK_DELETE: if (/Mac/.test(navigator.platform) && !aEvent.shiftKey) { break; } cancel = this.handleDelete(); break; case KeyEvent.DOM_VK_BACK_SPACE: if (/Mac/.test(navigator.platform) && aEvent.shiftKey) { cancel = this.handleDelete(); } break; case KeyEvent.DOM_VK_DOWN: case KeyEvent.DOM_VK_UP: if (aEvent.altKey) this.toggleHistoryPopup(); break; case KeyEvent.DOM_VK_F4: if (!/Mac/.test(navigator.platform)) { this.toggleHistoryPopup(); } break; } if (cancel) { aEvent.stopPropagation(); aEvent.preventDefault(); } return true; ]]> false aCommand == "cmd_paste", doCommand: function(aCommand) { this._autocomplete._valueIsPasted = true; this._autocomplete.editor.paste(this._kGlobalClipboard); this._autocomplete._valueIsPasted = false; }, isCommandEnabled: function(aCommand) { return this._autocomplete.editor.isSelectionEditable && this._autocomplete.editor.canPaste(this._kGlobalClipboard); }, onEvent: function() {} }) ]]> = 1) { // mousemove sets selected index. Don't blindly use that selected // index in this blur handler since if the popup is open you can // easily "select" another match just by moving the mouse over it. let filledVal = this.value.replace(/.+ >> /, "").toLowerCase(); let selectedVal = null; if (this.popup.selectedIndex >= 0) { selectedVal = this.mController.getFinalCompleteValueAt( this.popup.selectedIndex); } if (selectedVal && filledVal != selectedVal.toLowerCase()) { for (let i = 0; i < this.mController.matchCount; i++) { let matchVal = this.mController.getFinalCompleteValueAt(i); if (matchVal.toLowerCase() == filledVal) { this.popup.selectedIndex = i; break; } } } this.mController.handleEnter(false); } if (!this.ignoreBlurWhileSearching) this.detachController(); } ]]> false false 0) this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val); // Fire select event on xul:tree so that accessibility API // support layer can fire appropriate accessibility events. var event = document.createEvent('Events'); event.initEvent("select", true, true); this.tree.dispatchEvent(event); return val; ]]> 100 ? width : 100); // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840 var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction; this.style.direction = popupDirection; this.openPopup(aElement, "after_start", 0, 0, false, false); } ]]> document.getAnonymousElementByAttribute(this, "anonid", "tree"); document.getAnonymousElementByAttribute(this, "anonid", "treecols"); null false false 6 -1 aMaxRow && aIndex != aMaxRow) newIdx = aMaxRow; else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0) newIdx = 0; if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow) aIndex = -1; else aIndex = newIdx; return aIndex; ]]> { this.mIsPopupHidingTick = false; }, 0); // Reset the maxRows property to the cached "normal" value, and reset // _normalMaxRows so that we can detect whether it was set by the input // when the popupshowing handler runs. // Null-check this.mInput; see bug 1017914 if (this.mInput) this.mInput.maxRows = this._normalMaxRows; this._normalMaxRows = -1; // If the list was being navigated and then closed, make sure // we fire accessible focus event back to textbox // Null-check this.mInput; see bug 1017914 if (isListActive && this.mInput) { this.mInput.mIgnoreFocus = true; this.mInput._focus(); this.mInput.mIgnoreFocus = false; } ]]> 0 false 100 ? width : 100); // invalidate() depends on the width attribute this._invalidate(); this.openPopup(aElement, "after_start", 0, 0, false, false); } ]]> this.adjustHeight(), 0); } else { this._adjustHeightOnPopupShown = true; } this._currentIndex = 0; if (this._appendResultTimeout) { clearTimeout(this._appendResultTimeout); } this._appendCurrentResult(reason); ]]> this.maxRows) { // Set a fixed max-height to avoid flicker when growing the panel. let lastVisibleRowRect = rows[this.maxRows - 1].getBoundingClientRect(); let visibleHeight = lastVisibleRowRect.bottom - firstRowRect.top; this.richlistbox.style.maxHeight = visibleHeight + this._rlbPadding + "px"; } // The class `forceHandleUnderflow` is for the item might need to // handle OverUnderflow or Overflow when the height of an item will // be changed dynamically. for (let i = 0; i < numRows; i++) { if (rows[i].classList.contains("forceHandleUnderflow")) { rows[i].handleOverUnderflow(); } } let lastRowRect = rows[numRows - 1].getBoundingClientRect(); // Calculate the height to have the first row to last row shown height = lastRowRect.bottom - firstRowRect.top + this._rlbPadding; } let animate = this._rlbAnimated && this.getAttribute("dontanimate") != "true"; let currentHeight = this.richlistbox.getBoundingClientRect().height; if (height > currentHeight) { // Grow immediately. if (animate) { this.richlistbox.removeAttribute("height"); this.richlistbox.style.height = height + "px"; } else { this.richlistbox.style.removeProperty("height"); this.richlistbox.height = height; } } else { // Delay shrinking to avoid flicker. this._shrinkTimeout = setTimeout(() => { this._collapseUnusedItems(); if (animate) { this.richlistbox.removeAttribute("height"); this.richlistbox.style.height = height + "px"; } else { this.richlistbox.style.removeProperty("height"); this.richlistbox.height = height; } }, this.mInput.shrinkDelay); } ]]> = matchCount) break; var item; // trim the leading/trailing whitespace var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, ""); let url = controller.getValueAt(this._currentIndex); if (this._currentIndex < existingItemsCount) { // re-use the existing item item = this.richlistbox.childNodes[this._currentIndex]; item.setAttribute("dir", this.style.direction); // Completely reuse the existing richlistitem for invalidation // due to new results, but only when: the item is the same, *OR* // we are about to replace the currently mouse-selected item, to // avoid surprising the user. let iface = Components.interfaces.nsIAutoCompletePopup; if (item.getAttribute("text") == trimmedSearchString && invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT && (item.getAttribute("url") == url || this.richlistbox.mouseSelectedIndex === this._currentIndex)) { // Additionally, if the item is a searchengine action, then it // should only be reused if the engine name is the same as the // popup's override engine name, if any. let action = item._parseActionUrl(url); if (!action || action.type != "searchengine" || !this.overrideSearchEngineName || action.params.engineName == this.overrideSearchEngineName) { item.collapsed = false; // Call adjustSiteIconStart only after setting collapsed= // false. The calculations it does may be wrong otherwise. item.adjustSiteIconStart(this._siteIconStart); // The popup may have changed size between now and the last // time the item was shown, so always handle over/underflow. item.handleOverUnderflow(); this._currentIndex++; continue; } } } else { // need to create a new item item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem"); item.setAttribute("dir", this.style.direction); } // set these attributes before we set the class // so that we can use them from the constructor let iconURI = controller.getImageAt(this._currentIndex); item.setAttribute("image", iconURI); item.setAttribute("url", url); item.setAttribute("title", controller.getCommentAt(this._currentIndex)); item.setAttribute("originaltype", controller.getStyleAt(this._currentIndex)); item.setAttribute("text", trimmedSearchString); if (this._currentIndex < existingItemsCount) { // re-use the existing item item._adjustAcItem(); item.collapsed = false; } else { // set the class at the end so we can use the attributes // in the xbl constructor item.className = "autocomplete-richlistitem"; this.richlistbox.appendChild(item); } // The binding may have not been applied yet. setTimeout(() => { let changed = item.adjustSiteIconStart(this._siteIconStart); if (changed) { item.handleOverUnderflow(); } }, 0); this._currentIndex++; } if (typeof this.onResultsAdded == "function") this.onResultsAdded(); if (this._currentIndex < matchCount) { // yield after each batch of items so that typing the url bar is // responsive this._appendResultTimeout = setTimeout(() => this._appendCurrentResult(), 0); } ]]> document.getAnonymousElementByAttribute(this, "anonid", "richlistbox"); str).join(" ") // allow consumers that have extended popups to override // the label values for the richlistitems let panel = this.parentNode.parentNode; if (panel.createResultLabel) { return panel.createResultLabel(this, label); } return label; ]]> null false = 0) { regions.push([matchIndex, matchIndex + searchLen]); } } // Sort the regions by start position then end position regions = regions.sort((a, b) => { let start = a[0] - b[0]; return (start == 0) ? a[1] - b[1] : start; }); // Generate the boundary indices from each region let start = 0; let end = 0; let boundaries = []; let len = regions.length; for (let i = 0; i < len; i++) { // We have a new boundary if the start of the next is past the end let region = regions[i]; if (region[0] > end) { // First index is the beginning of match boundaries.push(start); // Second index is the beginning of non-match boundaries.push(end); // Track the new region now that we've stored the previous one start = region[0]; } // Push back the end index for the current or new region end = Math.max(end, region[1]); } // Add the last region boundaries.push(start); boundaries.push(end); // Put on the end boundary if necessary if (end < aText.length) boundaries.push(aText.length); // Skip the first item because it's always 0 return boundaries.slice(1); ]]> = 0 && index < aReplacements.length) { pairs.push([...aReplacements[index]]); } } else { pairs.push([part]); } } return pairs; ]]> null = 0) { pairs = [ [searchSuggestion.substring(0, idx), ""], [searchQuery, "match"], [searchSuggestion.substring(idx + searchQuery.length), ""], ]; } else { pairs = [ [searchSuggestion, ""], ]; } } else { pairs = [ [searchQuery, ""], ]; } let interpStr = pairs.map((pair, i) => `%${i + 1}$S`).join(""); title = this._generateEmphasisPairs(interpStr, pairs); // If this is a default search match, we remove the image so we // can style it ourselves with a generic search icon. // We don't do this when matching an aliased search engine, // because the icon helps with recognising which engine will be // used (when using the default engine, we don't need that // recognition). if (!action.params.alias && !initialTypes.has("favicon")) { this.removeAttribute("image"); } } else if (action.type == "visiturl") { emphasiseUrl = false; displayUrl = this._unescapeUrl(action.params.url); title = displayUrl; titleLooksLikeUrl = true; let visitStr = this._stringBundle.GetStringFromName("visit"); this._setUpDescription(this._actionText, visitStr, true); } else if (action.type == "extension") { let content = action.params.content; displayUrl = content; this._setUpDescription(this._actionText, content, true); } } if (!displayUrl) { let input = popup.input; let url = typeof(input.trimValue) == "function" ? input.trimValue(originalUrl) : originalUrl; displayUrl = this._unescapeUrl(url); } // For performance reasons we may want to limit the displayUrl size. if (popup.textRunsMaxLen) { displayUrl = displayUrl.substr(0, popup.textRunsMaxLen); } this.setAttribute("displayurl", displayUrl); // Show the domain as the title if we don't have a title. if (!title) { title = displayUrl; titleLooksLikeUrl = true; try { let uri = Services.io.newURI(originalUrl, null, null); // Not all valid URLs have a domain. if (uri.host) title = uri.host; } catch (e) {} } this._tags.setAttribute("empty", "true"); if (type == "tag" || type == "bookmark-tag") { // The title is separated from the tags by an endash let tags; [, title, tags] = title.match(/^(.+) \u2013 (.+)$/); // Each tag is split by a comma in an undefined order, so sort it let sortedTags = tags.split(/\s*,\s*/).sort((a, b) => { return a.localeCompare(a); }); let anyTagsMatch = this._setUpTags(sortedTags); if (anyTagsMatch) { this._tags.removeAttribute("empty"); } if (type == "bookmark-tag") { type = "bookmark"; } } else if (type == "keyword") { // Note that this is a moz-action with action.type == keyword. emphasiseUrl = false; let keywordArg = this.getAttribute("text").replace(/^[^\s]+\s*/, ""); if (!keywordArg) { // Treat keyword searches without arguments as visiturl actions. type = "visiturl"; this.setAttribute("actiontype", "visiturl"); let visitStr = this._stringBundle.GetStringFromName("visit"); this._setUpDescription(this._actionText, visitStr, true); } else { let pairs = [[title, ""], [keywordArg, "match"]]; let interpStr = this._stringBundle.GetStringFromName("bookmarkKeywordSearch"); title = this._generateEmphasisPairs(interpStr, pairs); // The action box will be visible since this is a moz-action, but // we want it to appear as if it were not visible, so set its text // to the empty string. this._setUpDescription(this._actionText, "", false); } } this.setAttribute("type", type); if (titleLooksLikeUrl) { this._titleText.setAttribute("lookslikeurl", "true"); } else { this._titleText.removeAttribute("lookslikeurl"); } if (Array.isArray(title)) { // For performance reasons we may want to limit the title size. if (popup.textRunsMaxLen) { title = title.map(t => t.substr(0, popup.textRunsMaxLen)); } this._setUpEmphasisedSections(this._titleText, title); } else { // For performance reasons we may want to limit the title size. if (popup.textRunsMaxLen) { title = title.substr(0, popup.textRunsMaxLen); } this._setUpDescription(this._titleText, title, false); } this._setUpDescription(this._urlText, displayUrl, !emphasiseUrl); if (this._inOverflow) { this._handleOverflow(); } ]]> 0; ]]> itemWidth) { // Title + tags + URL/action overflows the item width. // The percentage of the item width allocated to the title and tags. let titleTagsPct = 0.66; let titleTagsAvailable = itemWidth - separatorURLActionWidth; let titleTagsMaxWidth = Math.max( titleTagsAvailable, itemWidth * titleTagsPct ); if (titleTagsWidth > titleTagsMaxWidth) { // Title + tags overflows the max title + tags width. // The percentage of the title + tags width allocated to the // title. let titlePct = 0.33; let titleAvailable = titleTagsMaxWidth - tagsRect.width; let titleMaxWidth = Math.max( titleAvailable, titleTagsMaxWidth * titlePct ); let tagsAvailable = titleTagsMaxWidth - titleRect.width; let tagsMaxWidth = Math.max( tagsAvailable, titleTagsMaxWidth * (1 - titlePct) ); this._titleText.style.maxWidth = titleMaxWidth + "px"; this._tagsText.style.maxWidth = tagsMaxWidth + "px"; } let urlActionMaxWidth = Math.max( itemWidth - titleTagsWidth, itemWidth * (1 - titleTagsPct) ); urlActionMaxWidth -= separatorRect.width; this._urlText.style.maxWidth = urlActionMaxWidth + "px"; this._actionText.style.maxWidth = urlActionMaxWidth + "px"; } ]]> Date.now() -1 30) { let item = event.target; while (item && item.localName != "richlistitem") { item = item.parentNode; } if (!item) return; let index = this.getIndexOfItem(item); if (index != this.selectedIndex) { this.mouseSelectedIndex = this.selectedIndex = index; } this.mLastMoveTime = Date.now(); } ]]> Date.now() 30) { var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); if (rc != this.parentNode.currentIndex) this.parentNode.view.selection.select(rc); this.mLastMoveTime = Date.now(); } ]]>