Bug 1349747 - Search Suggestions Implementation through trie tree, datalists. draft
authormanotejmeka <manotejmeka@gmail.com>
Thu, 13 Apr 2017 02:03:50 -0400
changeset 561776 687011a6cca6e261db1a26199198300b97dbe644
parent 561775 b70d1598d69dbf3161433328a71f0cf462b13e1f
child 561777 37ee196ecaf5a964c31738ae066325a3b5892d54
child 568940 c0666887f0172d67e7043c8ed0d7d766baa97e6a
push id53861
push userbmo:manotejmeka@gmail.com
push dateThu, 13 Apr 2017 06:43:30 +0000
bugs1349747
milestone55.0a1
Bug 1349747 - Search Suggestions Implementation through trie tree, datalists. MozReview-Commit-ID: 5wyh7RvXCM9
browser/components/preferences/in-content/findInPage.js
browser/components/preferences/in-content/jar.mn
browser/components/preferences/in-content/preferences.xul
browser/components/preferences/in-content/tests/browser_search_within_preferences.js
browser/components/preferences/in-content/tries.js
browser/themes/shared/incontentprefs/preferences.inc.css
toolkit/themes/shared/in-content/common.inc.css
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -1,49 +1,138 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* import-globals-from preferences.js */
+/* import-globals-from tries.js */
 
 var gSearchResultsPane = {
   findSelection: null,
   searchResultsCategory: null,
   searchInput: null,
-  listSearchTooltips: [],
+  listSearchBubbles: [],
+  initialSearch: true,
+  trie: null,
+  timer: null,
 
   init() {
     let controller = this.getSelectionController();
     this.findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
     this.searchResultsCategory = document.getElementById("category-search-results");
 
     this.searchInput = document.getElementById("searchInput");
     this.searchInput.hidden = !Services.prefs.getBoolPref("browser.preferences.search");
+
+    let dataList = document.getElementById("searchSuggestions");
+    dataList.hidden = !this.searchInput.hidden;
     if (!this.searchInput.hidden) {
+      this.searchInput.addEventListener("focus", this);
+      this.searchInput.addEventListener("input", this);
+      this.searchInput.addEventListener("keypress", this);
       this.searchInput.addEventListener("command", this);
-      this.searchInput.addEventListener("focus", this);
+    } else {
+      this.searchInput.removeEventListener("focus", this);
+      this.searchInput.removeEventListener("input", this);
+      this.searchInput.removeEventListener("keypress", this);
+      this.searchInput.removeEventListener("command", this);
+    }
+    this.trie = new Trie();
+  },
+
+  /**
+   * Adding and removing class names to create a clicable close button in searchInput element
+   *
+   * @param Boolean classAction
+   *        Determining is X button is already added or not
+   * @param String className
+   *        Providing class name to bind appropriate CSS styling
+   */
+  classManipulation(classAction, className) {
+    let nodeObject = this.searchInput
+	// Adding the class name and listener
+    if (classAction && !nodeObject.classList.contains(className)) {
+      nodeObject.classList.add(className);
+      nodeObject.addEventListener("mousedown", this);
+    } else if (!classAction && nodeObject.classList.contains(className)) {
+	  // Removing the class name and listener
+	  nodeObject.removeEventListener("mousedown", this);
+      nodeObject.classList.remove(className);
     }
   },
 
+  // chooseDataListOption: "",
+
   handleEvent(event) {
     if (event.type === "command") {
       this.searchFunction(event);
     } else if (event.type === "focus") {
       // Will attempt to initialize all uninitialized categories
       //  Initializing all the JS for all the tabs
       if (!this.categoriesInitialized) {
         this.categoriesInitialized = true;
         // Each element of gCategoryInits is a name
         for (let [/* name */, category] of gCategoryInits) {
           if (!category.inited) {
             category.init();
           }
         }
+        // Initial search to load tries tree
+        this.searchFunction("testing search");
+		this.searchInput.value = ""; // Clearning old searches
+		this.searchFunction("");
       }
-    } else if (event.type === "mouseenter" && event.target.classList.contains("search-tooltip")) {
+    } else if (event.type === "input") {
+      let query = event.target.value.trim().toLowerCase();
+      // Adding a class to show the close button
+      this.classManipulation(query, "closeButton");
+      let overlayClose = document.getElementById("close-button-overlay");
+      overlayClose.hidden = false;
+      overlayClose.addEventListener("click", this);
+
+      // Stopping new search trigger when previous input change is same as
+      // current input change due to choosing of Datalist option.
+      if (this.chooseDataListOption != query) {
+        let datalistOptions = document.getElementById("searchSuggestions").children;
+
+        let word = "";
+        for (let optionObj of datalistOptions) {
+          word = optionObj.value;
+          if (word === query) {
+            this.chooseDataListOption = word;
+            this.searchInput.focus();
+            this.searchFunction(event.target.value.trim().toLowerCase());
+            break;
+          }
+        }
+      }
+      this.chooseDataListOption = query;
+      this.searchSuggestions(this.trie.autoComplete(String(query)));
+    } else if (event.type === "keypress") {
+      if (this.timer) {
+        clearTimeout(this.timer);
+      }
+      // Enter button clicked
+      if (event.keyCode === 13) {
+        this.searchFunction(event.target.value.trim().toLowerCase());
+      } else {
+        // Resetting the auto timmer search
+        this.timer = setTimeout(function() {
+          gSearchResultsPane.searchFunction(event.target.value.trim().toLowerCase());
+        }, 600);
+      }
+    } else if (event.type === "click") {
+      // When close button is clicked reseting to initial state
+      event.preventDefault();
+      this.searchInput.value = "";
+      this.searchFunction(this.searchInput.value);
+    } else if (event.type === "command") {
+      this.searchInput(event);
+    } else if (event.type === "mouseenter" &&
+      event.target.classList.contains("search-tooltip")) {
       // Inversing the position of the tooltip so it does not get in the way of content
       // Inverts its potion when mouse hovers/enters the tooptip area
       // Note: When searching for Google, Yahoo the tooltip is off ask for help.
       let parent = event.target;
       let child = parent.firstChild;
 
       if (child.classList.contains("search-tooltip-inner-reverse")) {
         child.classList.remove("search-tooltip-inner-reverse");
@@ -124,16 +213,20 @@ var gSearchResultsPane = {
    *    nodeSizes = "This is an example"
    *    This is used when executing the regular expression
    * @param String searchPhrase
    *    word or words to search for
    * @returns boolean
    *      Returns true when atleast one instance of search phrase is found, otherwise false
    */
   highlightMatches(textNodes, nodeSizes, textSearch, searchPhrase) {
+    if (this.initialSearch) {
+      this.addToTries(textSearch);
+    }
+
     let indices = [];
     let i = -1;
     while ((i = textSearch.indexOf(searchPhrase, i + 1)) >= 0) {
       indices.push(i);
     }
 
     // Looping through each spot the searchPhrase is found in the concatenated string
     for (let startValue of indices) {
@@ -191,18 +284,18 @@ var gSearchResultsPane = {
   },
 
   /**
    * Shows or hides content according to search input
    *
    * @param String event
    *    to search for filted query in
    */
-  searchFunction(event) {
-    let query = event.target.value.trim().toLowerCase();
+  searchFunction(query) {
+    // let query = event.target.value.trim().toLowerCase();
     this.findSelection.removeAllRanges();
 
     // Remove all search tooltips that were created
     let searchTooltips = Array.from(document.querySelectorAll(".search-tooltip"));
     for (let searchTooltip of searchTooltips) {
       searchTooltip.parentElement.classList.remove("search-tooltip-parent");
       searchTooltip.removeEventListener("mouseenter", this);
       searchTooltip.remove();
@@ -255,19 +348,30 @@ var gSearchResultsPane = {
       } else {
         // Creating tooltips for all the instances found
         for (let obj of this.listSearchTooltips) {
           this.createSearchTooltip(obj, query);
         }
       }
     } else {
       this.searchResultsCategory.hidden = true;
+      // Removing the no query found message
       document.getElementById("sorry-message").textContent = "";
       // Going back to General when cleared
       gotoPref("paneGeneral");
+      // this.chooseDataListOption = ""; // Reseting word choosen
+      // Removing the close button image and its overlay in searchInput element
+      let overlayClose = document.getElementById("close-button-overlay");
+      overlayClose.hidden = true;
+      overlayClose.removeEventListener("click", this);
+      this.classManipulation(false, "closeButton");
+    }
+
+    if (this.initialSearch) {
+      this.initialSearch = false;
     }
   },
 
   /**
    * Finding leaf nodes and checking their content for words to search,
    * It is a recursive function
    *
    * @param Node nodeObject
@@ -298,29 +402,39 @@ var gSearchResultsPane = {
         runningSize += node.textContent.length;
         allNodeText += node.textContent;
         nodeSizes.push(runningSize);
       }
 
       // Access key are presented
       let complexTextNodesResult = this.highlightMatches(accessKeyTextNodes, nodeSizes, allNodeText.toLowerCase(), searchPhrase);
 
-      // Searching some elements, such as xul:button, have a 'label' attribute that contains the user-visible text.
-      if (nodeObject.getAttribute("label")) {
-        labelResult = this.stringMatchesFilters(nodeObject.getAttribute("label"), searchPhrase);
-		// Creating tooltips for buttons
+      // Searching some elements, such as xul:button, have a "label" attribute that contains the user-visible text.
+      let labelContent = nodeObject.getAttribute("label");
+      if (labelContent) {
+        if (this.initialSearch) {
+          this.addToTries(labelContent);
+        }
+
+        labelResult = this.stringMatchesFilters(labelContent, searchPhrase);
+        // Creating tooltips for buttons
         if (labelResult && nodeObject.tagName == "button") {
           this.listSearchTooltips.push(nodeObject);
         }
       }
 
-      //  Searching some elements, such as xul:label, store their user-visible text in a "value" attribute.
-      if (nodeObject.getAttribute("value")) {
-        valueResult = this.stringMatchesFilters(nodeObject.getAttribute("value"), searchPhrase);
-		// Creating tooltips for buttons
+      // Searching some elements, such as xul:label, store their user-visible text in a "value" attribute.
+      let valueContent = nodeObject.getAttribute("value");
+      if (valueContent) {
+        if (this.initialSearch) {
+          this.addToTries(valueContent);
+        }
+
+        valueResult = this.stringMatchesFilters(valueContent, searchPhrase);
+        // Creating tooltips for buttons
         if (valueResult && nodeObject.tagName == "button") {
           this.listSearchTooltips.push(nodeObject);
         }
       }
 
       if (nodeObject.tagName == "button" && (labelResult || valueResult)){
         nodeObject.setAttribute("highlightable", "true");
       }
@@ -374,10 +488,47 @@ var gSearchResultsPane = {
     searchContent.setAttribute("class", "search-tooltip-inner");
     searchContent.textContent = searchPhrase;
 
     searchTooltip.appendChild(searchContent);
 
     currentNode.parentElement.insertBefore(searchTooltip, currentNode);
 
     searchTooltip.addEventListener("mouseenter", this);
+  },
+
+  /**
+   * Dynamically repopulating the datalist options for #searchInput dropdown options
+   *
+   * @param Array of Strings listSuggestions
+   *        List of autocompleted & related words
+   */
+	searchSuggestions(listSuggestions) {
+    let dataList = document.getElementById("searchSuggestions");
+    // Removing old options
+    dataList.innerHTML = "";
+    // Creating new options
+    for (let word of listSuggestions) {
+      let option = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
+      option.setAttribute("value", word);
+      dataList.appendChild(option);
+    }
+  },
+
+  /**
+   * Adding each word to the tries graph
+   *
+   * @param String phrase
+   *        List of words to add to tries graph
+   */
+	addToTries(phrase) {
+    // .replace is removing all special characters
+    // then removing the stop words
+    // then splitting it into list of words
+    let listOfWords = phrase.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, "").removeStopWords().split(" ");
+    for (let i = 0; i < listOfWords.length; i++) {
+      // Might need a better way to check if it is not just numbers
+      if (isNaN(listOfWords[i])) {
+        this.trie.insert(listOfWords[i].toLowerCase());
+      }
+    }
   }
 }
--- a/browser/components/preferences/in-content/jar.mn
+++ b/browser/components/preferences/in-content/jar.mn
@@ -9,8 +9,9 @@ browser.jar:
 
    content/browser/preferences/in-content/main.js
    content/browser/preferences/in-content/privacy.js
    content/browser/preferences/in-content/containers.js
    content/browser/preferences/in-content/advanced.js
    content/browser/preferences/in-content/applications.js
    content/browser/preferences/in-content/sync.js
    content/browser/preferences/in-content/findInPage.js
+   content/browser/preferences/in-content/tries.js
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -70,16 +70,17 @@
 
   <html:link rel="shortcut icon"
               href="chrome://browser/skin/preferences/in-content/favicon.ico"/>
 
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/preferences/in-content/preferences.js"/>
+  <script src="chrome://browser/content/preferences/in-content/tries.js"/>
   <script src="chrome://browser/content/preferences/in-content/findInPage.js"/>
   <script src="chrome://browser/content/preferences/in-content/subdialogs.js"/>
 
   <stringbundle id="bundleBrand"
                 src="chrome://branding/locale/brand.properties"/>
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
@@ -166,19 +167,23 @@
            Remove this keyset once bug 1094240 ("disablefastfind" attribute
            broken in e10s mode) is fixed. -->
       <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
     </keyset>
 
     <html:a class="help-button" target="_blank" aria-label="&helpButton2.label;">&helpButton2.label;</html:a>
 
     <vbox class="main-content" flex="1">
-      <hbox pack="end">
-        <textbox type="search" id="searchInput" placeholder="&searchInput.label;" hidden="true"/>
+
+      <hbox class="search-input-hbox" pack="end">
+        <html:input type="search" class="searchInput" id="searchInput" placeholder="&searchInput.label;" list="searchSuggestions" mozNoFilter="true" hidden="true" />
+        <div id="close-button-overlay"></div>
+        <html:datalist id="searchSuggestions" hidden="true" />
       </hbox>
+
       <prefpane id="mainPrefPane">
 #include searchResults.xul
 #include main.xul
 #include privacy.xul
 #include containers.xul
 #include advanced.xul
 #include applications.xul
 #include sync.xul
--- a/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
+++ b/browser/components/preferences/in-content/tests/browser_search_within_preferences.js
@@ -1,12 +1,20 @@
 /*
 * This file contains tests for the Preferences search bar.
 */
 
+function triggerSearch(nodeObject, searchPhrase) {
+  nodeObject.focus();
+  nodeObject.value = searchPhrase;
+  let enterButton = document.createEvent("KeyboardEvent");
+  enterButton.initKeyEvent("keypress", true, true, window, false, false, false, false, 13, 0);
+  nodeObject.dispatchEvent(enterButton);
+}
+
 /**
  * Tests to see if search bar is being hidden when pref is turned off
  */
 add_task(function*() {
   yield SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", false]]});
   yield openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   let searchInput = gBrowser.contentDocument.querySelectorAll("#searchInput");
   is(searchInput.length, 1, "There should only be one element name searchInput querySelectorAll");
@@ -35,33 +43,31 @@ add_task(function*() {
  * After it runs a search, it tests if the "Search Results" panel is the only selected category.
  * The search is then cleared, it then tests if the "General" panel is the only selected category.
  */
 add_task(function*() {
   yield openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
-  searchInput.doCommand()
-  searchInput.value = "password";
-  searchInput.doCommand()
+  triggerSearch(searchInput, "");
+  triggerSearch(searchInput, "password");
 
   let categoriesList = gBrowser.contentDocument.getElementById("categories");
 
   for (let i = 0; i < categoriesList.childElementCount; i++) {
     let child = categoriesList.children[i]
     if (child.id == "category-search-results") {
       is(child.selected, true, "Search results panel should be selected");
     } else if (child.id) {
       is(child.selected, false, "No other panel should be selected");
     }
   }
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand()
+  triggerSearch(searchInput, "");
 
   // Checks if back to generalPane
   for (let i = 0; i < categoriesList.childElementCount; i++) {
     let child = categoriesList.children[i]
     if (child.id == "category-general") {
       is(child.selected, true, "General panel should be selected");
     } else if (child.id) {
       is(child.selected, false, "No other panel should be selected");
@@ -74,34 +80,32 @@ add_task(function*() {
 /**
  * Test for "password" case. When we search "password", it should show the "passwordGroup"
  */
 add_task(function*() {
   yield openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
-  searchInput.doCommand()
-  searchInput.value = "password";
-  searchInput.doCommand()
+  triggerSearch(searchInput, "");
+  triggerSearch(searchInput, "password");
 
   let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
 
   for (let i = 0; i < mainPrefTag.childElementCount; i++) {
     let child = mainPrefTag.children[i]
     if (child.id == "passwordsGroup" || child.id == "weavePrefsDeck" || child.id == "header-searchResults") {
       is_element_visible(child, "Should be in search results");
     } else if (child.id) {
       is_element_hidden(child, "Should not be in search results");
     }
   }
 
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand()
+	triggerSearch(searchInput, "");
 
   // Checks if back to generalPane
   for (let i = 0; i < mainPrefTag.childElementCount; i++) {
     let child = mainPrefTag.children[i]
     if (child.id == "startupGroup"
     || child.id == "defaultEngineGroup"
     || child.id == "oneClickSearchProvidersGroup"
     || child.id == "paneGeneral"
@@ -126,25 +130,23 @@ add_task(function*() {
   yield openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
 
   let noResultsEl = gBrowser.contentDocument.querySelector(".no-results-message");
 
   is_element_hidden(noResultsEl, "Should not be in search results yet");
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
-  searchInput.doCommand()
-  searchInput.value = "coach";
-  searchInput.doCommand()
+	triggerSearch(searchInput, "");
+  triggerSearch(searchInput, "coach");
 
   is_element_visible(noResultsEl, "Should be in search results");
 
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand()
+  triggerSearch(searchInput, "");
 
   is_element_hidden(noResultsEl, "Should not be in search results");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for if we go back to general tab after search case
@@ -152,21 +154,19 @@ add_task(function*() {
 add_task(function*() {
   yield openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
   let generalPane = gBrowser.contentDocument.getElementById("header-general");
 
   is_element_hidden(generalPane, "Should not be in general");
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
-  searchInput.doCommand()
-  searchInput.value = "password";
-  searchInput.doCommand()
+  triggerSearch(searchInput, "");
+  triggerSearch(searchInput, "password");
 
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand()
+  triggerSearch(searchInput, "");
 
   // Checks if back to normal
   is_element_visible(generalPane, "Should be in generalPane");
 
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tries.js
@@ -0,0 +1,202 @@
+var Trie = function() {
+  this.wordCount = 0;
+  this.children = [];
+  this.word = null;
+  this.relatedWords = [];
+}
+
+Trie.prototype.insert = function(word) {
+	let node = this;
+	if (word === "") {
+		return;
+	}
+	for (let character of word.split("")) {
+		if (node.children[character] === undefined || node.children[character] === null) {
+			node.children[character] = new Trie();
+		}
+		node = node.children[character];
+	}
+	node.wordCount++;
+	node.word = word;
+}
+
+Trie.prototype.completeWord = function(partialWord) {
+	let node = this;
+	let listOfWords = [];
+	let child = null;
+
+	if (node.wordCount > 0) {
+		listOfWords.push(partialWord);
+	}
+	for (let childNode in node.children) {
+		child = node.children[childNode];
+		listOfWords = listOfWords.concat(child.completeWord(partialWord + childNode));
+	}
+	return listOfWords;
+}
+
+Trie.prototype.listOfWords = function() {
+	let node = this;
+	if (node === undefined || node === null) {
+		return [];
+	}
+	let wordsList = [];
+	let child = null;
+	if (node.word != null || node.word != undefined) {
+		wordsList.push(node.word);
+		wordsList = wordsList.concat(node.relatedWords);
+	}
+	for (let char in node.children) {
+		child = node.children[char];
+		wordsList = wordsList.concat(child.listOfWords());
+	}
+	return wordsList;
+}
+
+Trie.prototype.autoComplete = function(partialWord) {
+	let node = this;
+	let typos = node.levenshteinDistance(partialWord, 1);
+	let arrayTypos = firstElement(typos);
+
+	for (let char of partialWord) {
+		if (node.children[char] === undefined || node.children[char] === null) {
+			return new Set([].concat(arrayTypos));
+		}
+		node = node.children[char];
+	}
+	// return node.completeWord(partialWord);
+	let complete = node.listOfWords();
+	let fullList = new Set(complete.concat(arrayTypos));
+	fullList.delete(partialWord);
+	return fullList;
+}
+
+Trie.prototype.addRelatedWords = function(findWord, addWord, swapCall = true) {
+	let node = this;
+	if (swapCall) {
+		this.addRelatedWords(addWord, findWord, false);
+	}
+
+	for (let char of findWord) {
+		if (node.children[char] != undefined || node.children[char] != null) {
+			node = node.children[char];
+		} else {
+			return;
+		}
+	}
+
+	if (node != undefined || node != null) {
+		node.relatedWords.push(addWord);
+	}
+}
+
+Trie.prototype.levenshteinHelper = function(letter, word, previousRow, results, maxCost) {
+	let node = this;
+	let columns = word.length + 1;
+	let currentRow = [previousRow[0] + 1];
+	let insertCost = null, deleteCost = null, replaceCost = null;
+	let child = null;
+
+	for (let i = 1; i < columns; i++) {
+		insertCost = currentRow[i - 1] + 1;
+		deleteCost = previousRow[i] + 1;
+
+		if (word[i - 1] != letter) {
+			replaceCost = previousRow[i - 1] + 1;
+		} else {
+			replaceCost = previousRow[i - 1];
+		}
+
+		currentRow.push(Math.min(insertCost, deleteCost, replaceCost));
+	}
+
+	if (currentRow[currentRow.length - 1] <= maxCost && node.wordCount > 0) {
+		results.push([node.word, currentRow[currentRow.length - 1]]);
+	}
+
+	if (Math.min.apply(null, currentRow) <= maxCost) {
+		for (let character in node.children) {
+			child = node.children[character];
+			child.levenshteinHelper(character, word, currentRow, results, maxCost);
+		}
+	}
+	return results;
+}
+
+// wrote it with the help of http://stevehanov.ca/blog/index.php?id=114
+Trie.prototype.levenshteinDistance = function(word, maxCost) {
+	let node = this;
+	let currentRow = Array.from(Array(word.length + 1).keys());
+	let results = [];
+	let child = null;
+
+	for (let character in node.children) {
+		child = node.children[character]
+		child.levenshteinHelper(character, word, currentRow, results, maxCost);
+	}
+	return results;
+}
+
+function firstElement(arry) {
+	let temp = [];
+	for (let i = 0; i < arry.length; i++) {
+		temp.push(arry[i][0]);
+	}
+	return temp;
+}
+
+String.isStopWord = function(word) {
+	let regex = new RegExp("\\b" + word + "\\b", "i");
+	if (stopWords.search(regex) < 0) {
+		return false;
+	}
+	return true;
+}
+
+String.prototype.removeStopWords = function() {
+	let words = new Array();
+
+	this.replace(/\b[\w]+\b/g, function($0) {
+		if (!String.isStopWord($0)) {
+			words[words.length] = $0.trim();
+		}
+	});
+	return words.join(" ");
+}
+
+// Source : https://github.com/mirzaasif/JS-StopWord/blob/master/StopWord.js
+var stopWords = "a,able,about,above,abst,accordance,according,accordingly,across,act,actually,added,adj,\
+affected,affecting,affects,after,afterwards,again,against,ah,all,almost,alone,along,already,also,although,\
+always,am,among,amongst,an,and,announce,another,any,anybody,anyhow,anymore,anyone,anything,anyway,anyways,\
+anywhere,apparently,approximately,are,aren,arent,arise,around,as,aside,ask,asking,at,auth,available,away,awfully,\
+b,back,be,became,because,become,becomes,becoming,been,before,beforehand,begin,beginning,beginnings,begins,behind,\
+being,believe,below,beside,besides,between,beyond,biol,both,brief,briefly,but,by,c,ca,came,can,cannot,can't,cause,causes,\
+certain,certainly,co,com,come,comes,contain,containing,contains,could,couldnt,d,date,did,didn't,different,do,does,doesn't,\
+doing,done,don't,down,downwards,due,during,e,each,ed,edu,effect,eg,eight,eighty,either,else,elsewhere,end,ending,enough,\
+especially,et,et-al,etc,even,ever,every,everybody,everyone,everything,everywhere,ex,except,f,far,few,ff,fifth,first,five,fix,\
+followed,following,follows,for,former,formerly,forth,found,four,from,further,furthermore,g,gave,get,gets,getting,give,given,gives,\
+giving,go,goes,gone,got,gotten,h,had,happens,hardly,has,hasn't,have,haven't,having,he,hed,hence,her,here,hereafter,hereby,herein,\
+heres,hereupon,hers,herself,hes,hi,hid,him,himself,his,hither,home,how,howbeit,however,hundred,i,id,ie,if,i'll,im,immediate,\
+immediately,importance,important,in,inc,indeed,index,information,instead,into,invention,inward,is,isn't,it,itd,it'll,its,itself,\
+i've,j,just,k,keep,keeps,kept,kg,km,know,known,knows,l,largely,last,lately,later,latter,latterly,least,less,lest,let,lets,like,\
+liked,likely,line,little,'ll,look,looking,looks,ltd,m,made,mainly,make,makes,many,may,maybe,me,mean,means,meantime,meanwhile,\
+merely,mg,might,million,miss,ml,more,moreover,most,mostly,mr,mrs,much,mug,must,my,myself,n,na,name,namely,nay,nd,near,nearly,\
+necessarily,necessary,need,needs,neither,never,nevertheless,new,next,nine,ninety,no,nobody,non,none,nonetheless,noone,nor,\
+normally,nos,not,noted,nothing,now,nowhere,o,obtain,obtained,obviously,of,off,often,oh,ok,okay,old,omitted,on,once,one,ones,\
+only,onto,or,ord,other,others,otherwise,ought,our,ours,ourselves,out,outside,over,overall,owing,own,p,page,pages,part,\
+particular,particularly,past,per,perhaps,placed,please,plus,poorly,possible,possibly,potentially,pp,predominantly,present,\
+previously,primarily,probably,promptly,proud,provides,put,q,que,quickly,quite,qv,r,ran,rather,rd,re,readily,really,recent,\
+recently,ref,refs,regarding,regardless,regards,related,relatively,research,respectively,resulted,resulting,results,right,run,s,\
+said,same,saw,say,saying,says,sec,section,see,seeing,seem,seemed,seeming,seems,seen,self,selves,sent,seven,several,shall,she,shed,\
+she'll,shes,should,shouldn't,show,showed,shown,showns,shows,significant,significantly,similar,similarly,since,six,slightly,so,\
+some,somebody,somehow,someone,somethan,something,sometime,sometimes,somewhat,somewhere,soon,sorry,specifically,specified,specify,\
+specifying,still,stop,strongly,sub,substantially,successfully,such,sufficiently,suggest,sup,sure,t,take,taken,taking,tell,tends,\
+th,than,thank,thanks,thanx,that,that'll,thats,that've,the,their,theirs,them,themselves,then,thence,there,thereafter,thereby,\
+thered,therefore,therein,there'll,thereof,therere,theres,thereto,thereupon,there've,these,they,theyd,they'll,theyre,they've,\
+think,this,those,thou,though,thoughh,thousand,throug,through,throughout,thru,thus,til,tip,to,together,too,took,toward,towards,\
+tried,tries,truly,try,trying,ts,twice,two,u,un,under,unfortunately,unless,unlike,unlikely,until,unto,up,upon,ups,us,use,used,\
+useful,usefully,usefulness,uses,using,usually,v,value,various,'ve,very,via,viz,vol,vols,vs,w,want,wants,was,wasn't,way,we,wed,\
+welcome,we'll,went,were,weren't,we've,what,whatever,what'll,whats,when,whence,whenever,where,whereafter,whereas,whereby,wherein,\
+wheres,whereupon,wherever,whether,which,while,whim,whither,who,whod,whoever,whole,who'll,whom,whomever,whos,whose,why,widely,\
+willing,wish,with,within,without,won't,words,world,would,wouldn't,www,x,y,yes,yet,you,youd,you'll,your,youre,yours,yourself,\
+yourselves,you've,z,zero";
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -617,8 +617,41 @@ description > html|a {
   bottom: -7px;
   top: auto;
 }
 
 /* Styling for parents node of search tooltip */
 .search-tooltip-parent {
   position: relative;
 }
+
+.searchInput {
+  /* backround-image: search-icon image, seach-close-button image
+  *  http://stackoverflow.com/questions/6258521/clear-icon-inside-input-text
+  */
+  background-image: url(chrome://browser/skin/search-indicator-magnifying-glass.svg),url(chrome://global/skin/icons/searchfield-cancel.svg);
+  border: 1px solid #999;
+  padding: 4px 20px 4px 20px;
+  border-radius: 3px;
+  background-repeat: no-repeat, no-repeat;
+  background-position: left 4px center,right -14px center;
+  background-size: 15px 15px, 12px 12px;
+  font-size: 12px;
+}
+
+.searchInput.closeButton {
+  background-position: left 4px center, right 5px center; 
+}
+
+.search-input-hbox {
+  position: relative;
+}
+
+#close-button-overlay {
+  z-index: 1000;
+  position: absolute;
+  width: 14px;
+  height: 14px;
+  right: 5px;
+  bottom: 5px;
+  background-color: transparent;
+  cursor: pointer;
+}
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -88,16 +88,17 @@ xul|caption > xul|label {
 }
 
 *|*.main-content {
   padding-top: 40px;
   padding-inline-end: 44px; /* compensate the 4px margin of child elements */
   padding-bottom: 48px;
   padding-inline-start: 48px;
   overflow: auto;
+  position: relative; /* Search Bar alignment with header in about:preferences */
 }
 
 xul|prefpane > xul|*.content-box {
   overflow: visible;
 }
 
 /* groupboxes */