Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 28 Jan 2014 16:18:18 -0500
changeset 165585 128c86a925d7f9ee71c61f90facef32e8ed63c1f
parent 165584 245b92f1de4f189b3e55a80451af0fca8c7ba40b (current diff)
parent 165546 76b9e0793b76b677dfc1f69497f688b5ed3f0f23 (diff)
child 165628 7e79536aca0ae7b6117115e3889400d8ff450ba1
push id4623
push userryanvm@gmail.com
push dateTue, 28 Jan 2014 21:48:39 +0000
treeherderfx-team@7e79536aca0a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
Merge fx-team to m-c.
browser/devtools/debugger/test/browser_dbg_search-function.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1153,16 +1153,17 @@ pref("devtools.tilt.outro_transition", t
 // - enableCodeFolding: Whether to enable code folding or not.
 pref("devtools.scratchpad.recentFilesMax", 10);
 pref("devtools.scratchpad.showTrailingSpace", false);
 pref("devtools.scratchpad.enableCodeFolding", true);
 
 // Enable the Style Editor.
 pref("devtools.styleeditor.enabled", true);
 pref("devtools.styleeditor.source-maps-enabled", false);
+pref("devtools.styleeditor.autocompletion-enabled", true);
 
 // Enable the Shader Editor.
 pref("devtools.shadereditor.enabled", false);
 
 // Enable tools for Chrome development.
 pref("devtools.chrome.enabled", false);
 
 // Default theme ("dark" or "light")
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -278,17 +278,17 @@ toolbarpaletteitem > #personal-bookmarks
   overflow-x: hidden;
   overflow-y: auto;
 }
 #panelMenu_bookmarksMenu > .bookmark-item {
   max-width: none;
 }
 
 #urlbar-container {
-  min-width: 30ch;
+  min-width: 28ch;
 }
 
 #search-container {
   min-width: 25ch;
 }
 
 #main-window:-moz-lwtheme {
   background-repeat: no-repeat;
@@ -374,17 +374,17 @@ panel[noactions] > richlistbox > richlis
   visibility: collapse;
 }
 
 #urlbar[pageproxystate="invalid"] > #identity-box {
   pointer-events: none;
 }
 
 #identity-icon-labels {
-  max-width: 18em;
+  max-width: 12vw;
 }
 
 #identity-icon-country-label {
   direction: ltr;
 }
 
 #identity-box.verifiedIdentity > #identity-icon-labels > #identity-icon-label {
   -moz-margin-end: 0.25em !important;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -703,27 +703,27 @@
 
         <toolbarbutton id="bookmarks-menu-button"
                        class="toolbarbutton-1 chromeclass-toolbar-additional"
                        persist="class"
                        removable="true"
                        type="menu-button"
                        label="&bookmarksMenuButton.label;"
                        tooltiptext="&bookmarksMenuButton.tooltip;"
+                       anchor="dropmarker"
                        ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
                        ondragover="PlacesMenuDNDHandler.onDragOver(event);"
                        ondragleave="PlacesMenuDNDHandler.onDragLeave(event);"
                        ondrop="PlacesMenuDNDHandler.onDrop(event);"
                        cui-areatype="toolbar"
                        oncommand="BookmarkingUI.onCommand(event);">
           <menupopup id="BMB_bookmarksPopup"
                      placespopup="true"
                      context="placesContext"
                      openInTabs="children"
-                     anonanchorclass="toolbarbutton-menubutton-dropmarker"
                      oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
                      onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
                      onpopupshowing="BookmarkingUI.onPopupShowing(event);
                                      if (!this.parentNode._placesView)
                                        new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
                      tooltip="bhTooltip" popupsinherittooltip="true">
             <menuitem id="BMB_bookmarksShowAll"
                       label="&showAllBookmarks2.label;"
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -1199,16 +1199,21 @@ let CustomizableUIInternal = {
         lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup;
         aNode = aNode.parentNode;
       }
       return lastPopup;
     }
 
     let target = aEvent.originalTarget;
     let panel = this._getPanelForNode(aEvent.currentTarget);
+    // This can happen in e.g. customize mode. If there's no panel,
+    // there's clearly nothing for us to close; pretend we're interactive.
+    if (!panel) {
+      return true;
+    }
     // We keep track of:
     // whether we're in an input container (text field)
     let inInput = false;
     // whether we're in a popup/context menu
     let inMenu = false;
     // whether we're in a toolbarbutton/toolbaritem
     let inItem = false;
     // whether the current menuitem has a valid closemenu attribute
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -485,121 +485,103 @@
 
     </handlers>
   </binding>
 
   <!-- Most of this is copied from the arrowpanel binding in popup.xml -->
   <binding id="places-popup-arrow"
            extends="chrome://browser/content/places/menu.xml#places-popup-base">
     <content flip="both" side="top" position="bottomcenter topleft">
-      <xul:box anonid="container" class="panel-arrowcontainer" flex="1"
+      <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
                xbl:inherits="side,panelopen">
         <xul:box anonid="arrowbox" class="panel-arrowbox">
           <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
         </xul:box>
         <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
           <xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
             <xul:image class="menupopup-drop-indicator" mousethrough="always"/>
           </xul:vbox>
           <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
                               smoothscroll="false">
             <children/>
           </xul:arrowscrollbox>
         </xul:box>
-      </xul:box>
+      </xul:vbox>
     </content>
 
     <implementation>
       <method name="adjustArrowPosition">
         <body><![CDATA[
           var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
 
           var anchor = this.anchorNode;
           if (!anchor) {
             arrow.hidden = true;
             return;
           }
 
           var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
           var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
 
+          var position = this.alignmentPosition;
+          var offset = this.alignmentOffset;
           // if this panel has a "sliding" arrow, we may have previously set margins...
-          arrowbox.style.removeProperty("margin");
-
-          var position = this.alignmentPosition;
+          arrowbox.style.removeProperty("transform");
           if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
-            container.orient = "";
+            container.orient = "horizontal";
             arrowbox.orient = "vertical";
             if (position.indexOf("_after") > 0) {
               arrowbox.pack = "end";
-              arrowbox.style.marginBottom = this.alignmentOffset + "px";
             } else {
               arrowbox.pack = "start";
-              arrowbox.style.marginTop = this.alignmentOffset + "px";
             }
+            arrowbox.style.transform = "translate(0, " + -offset + "px)";
 
             // The assigned side stays the same regardless of direction.
             var isRTL = (window.getComputedStyle(this).direction == "rtl");
 
             if (position.indexOf("start_") == 0) {
               container.dir = "reverse";
               this.setAttribute("side", isRTL ? "left" : "right");
             }
             else {
               container.dir = "";
               this.setAttribute("side", isRTL ? "right" : "left");
             }
           }
           else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
-            container.orient = "vertical";
+            container.orient = "";
             arrowbox.orient = "";
             if (position.indexOf("_end") > 0) {
               arrowbox.pack = "end";
-              arrowbox.style.marginRight = this.alignmentOffset + "px";
             } else {
               arrowbox.pack = "start";
-              arrowbox.style.marginLeft = this.alignmentOffset + "px";
             }
+            arrowbox.style.transform = "translate(" + -offset + "px, 0)";
 
             if (position.indexOf("before_") == 0) {
               container.dir = "reverse";
               this.setAttribute("side", "bottom");
             }
             else {
               container.dir = "";
               this.setAttribute("side", "top");
             }
           }
+
           arrow.hidden = false;
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="popupshowing" phase="target"><![CDATA[
         this.adjustArrowPosition();
       ]]></handler>
       <handler event="popupshown" phase="target"><![CDATA[
         this.setAttribute("panelopen", "true");
-
-        // Allow anchoring to a specified element inside the anchor.
-        var anchorClass = this.getAttribute("anonanchorclass");
-        if (anchorClass && this.anchorNode) {
-          let anchor =
-            document.getAnonymousElementByAttribute(this.anchorNode, "class",
-                                                    anchorClass);
-          if (anchor) {
-            let offsetX = anchor.boxObject.width / 2;
-            if (this.alignmentPosition.endsWith("_end"))
-              offsetX *= -1;
-            this.popupBoxObject.moveToAnchor(anchor, this.alignmentPosition,
-                                             offsetX, 0,
-                                             false);
-            this.adjustArrowPosition();
-          }
-        }
       ]]></handler>
       <handler event="popuphidden" phase="target"><![CDATA[
         this.removeAttribute("panelopen");
       ]]></handler>
     </handlers>
   </binding>
 </bindings>
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -1066,31 +1066,37 @@ FilterView.prototype = {
    *        The operator to use for filtering.
    */
   _doSearch: function(aOperator = "", aText = "") {
     this._searchbox.focus();
     this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
 
     if (aText) {
       this._searchbox.value = aOperator + aText;
+      return;
     }
-    else if (DebuggerView.editor.somethingSelected()) {
+    if (DebuggerView.editor.somethingSelected()) {
       this._searchbox.value = aOperator + DebuggerView.editor.getSelection();
+      return;
     }
-    else {
+    if (SEARCH_AUTOFILL.indexOf(aOperator) != -1) {
       let cursor = DebuggerView.editor.getCursor();
       let content = DebuggerView.editor.getText();
       let location = DebuggerView.Sources.selectedValue;
       let source = DebuggerController.Parser.get(content, location);
       let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch });
 
       if (identifier && identifier.name) {
         this._searchbox.value = aOperator + identifier.name;
+        this._searchbox.select();
+        this._searchbox.selectionStart += aOperator.length;
+        return;
       }
     }
+    this._searchbox.value = aOperator;
   },
 
   /**
    * Called when the source location filter key sequence was pressed.
    */
   _doFileSearch: function() {
     this._doSearch();
     this._searchboxHelpPanel.openPopup(this._searchbox);
@@ -1380,34 +1386,34 @@ FilteredFunctionsView.prototype = Herita
    * @param array aSources
    *        An array of [url, text] tuples for each source.
    */
   _doSearch: function(aToken, aSources, aStore = []) {
     // Continue parsing even if the searched token is an empty string, to
     // cache the syntax tree nodes generated by the reflection API.
 
     // Make sure the currently displayed source is parsed first. Once the
-    // maximum allowed number of resutls are found, parsing will be halted.
+    // maximum allowed number of results are found, parsing will be halted.
     let currentUrl = DebuggerView.Sources.selectedValue;
     let currentSource = aSources.filter(([sourceUrl]) => sourceUrl == currentUrl)[0];
     aSources.splice(aSources.indexOf(currentSource), 1);
     aSources.unshift(currentSource);
 
     // If not searching for a specific function, only parse the displayed source,
     // which is now the first item in the sources array.
     if (!aToken) {
       aSources.splice(1);
     }
 
     for (let [location, contents] of aSources) {
       let parsedSource = DebuggerController.Parser.get(contents, location);
       let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken);
 
       for (let scriptResult of sourceResults) {
-        for (let parseResult of scriptResult.parseResults) {
+        for (let parseResult of scriptResult) {
           aStore.push({
             sourceUrl: scriptResult.sourceUrl,
             scriptOffset: scriptResult.scriptOffset,
             functionName: parseResult.functionName,
             functionLocation: parseResult.functionLocation,
             inferredName: parseResult.inferredName,
             inferredChain: parseResult.inferredChain,
             inferredLocation: parseResult.inferredLocation
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -21,16 +21,17 @@ const GLOBAL_SEARCH_EXPAND_MAX_RESULTS =
 const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
 const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
 const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_FUNCTION_FLAG = "@";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_VARIABLE_FLAG = "*";
+const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
 const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms
 const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
 const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
 
 /**
  * Object defining the debugger view components.
  */
 let DebuggerView = {
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -160,16 +160,17 @@ support-files =
 [browser_dbg_progress-listener-bug.js]
 [browser_dbg_reload-preferred-script-01.js]
 [browser_dbg_reload-preferred-script-02.js]
 [browser_dbg_reload-preferred-script-03.js]
 [browser_dbg_reload-same-script.js]
 [browser_dbg_scripts-switching-01.js]
 [browser_dbg_scripts-switching-02.js]
 [browser_dbg_scripts-switching-03.js]
+[browser_dbg_search-autofill-identifier.js]
 [browser_dbg_search-basic-01.js]
 [browser_dbg_search-basic-02.js]
 [browser_dbg_search-basic-03.js]
 [browser_dbg_search-basic-04.js]
 [browser_dbg_search-global-01.js]
 [browser_dbg_search-global-02.js]
 [browser_dbg_search-global-03.js]
 [browser_dbg_search-global-04.js]
@@ -242,15 +243,14 @@ support-files =
 [browser_dbg_variables-view-popup-07.js]
 [browser_dbg_variables-view-popup-08.js]
 [browser_dbg_variables-view-popup-09.js]
 [browser_dbg_variables-view-reexpand-01.js]
 [browser_dbg_variables-view-reexpand-02.js]
 [browser_dbg_variables-view-webidl.js]
 [browser_dbg_watch-expressions-01.js]
 [browser_dbg_watch-expressions-02.js]
-[browser_dbg_search-function.js]
 [browser_dbg_chrome-create.js]
 skip-if = os == "linux" # Bug 847558
 [browser_dbg_on-pause-raise.js]
 skip-if = os == "linux" # Bug 888811 & bug 891176
 [browser_dbg_break-on-dom-event.js]
 skip-if = os == "mac" # Bug 895426
rename from browser/devtools/debugger/test/browser_dbg_search-function.js
rename to browser/devtools/debugger/test/browser_dbg_search-autofill-identifier.js
--- a/browser/devtools/debugger/test/browser_dbg_search-function.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-autofill-identifier.js
@@ -1,28 +1,135 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Tests that Debugger Search uses the identifier under cursor
- * if nothing is selected or manually passed
+ * Tests that Debugger Search uses the identifier under cursor if nothing is
+ * selected or manually passed and searching using certain operators.
  */
-
 "use strict";
 
 function test() {
-
   const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
 
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     let Source = 'code_function-search-01.js';
     let Debugger = aPanel.panelWin;
     let Editor = Debugger.DebuggerView.editor;
     let Filtering = Debugger.DebuggerView.Filtering;
 
+    function doSearch(aOperator) {
+      Editor.dropSelection();
+      Filtering._doSearch(aOperator);
+    }
+
     waitForSourceShown(aPanel, Source).then(() => {
+      info("Testing with cursor at the beginning of the file...");
+
+      doSearch();
+      is(Filtering._searchbox.value, "",
+        "The searchbox value should not be auto-filled when searching for files.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("!");
+      is(Filtering._searchbox.value, "!",
+        "The searchbox value should not be auto-filled when searching across all files.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("@");
+      is(Filtering._searchbox.value, "@",
+        "The searchbox value should not be auto-filled when searching for functions.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("#");
+      is(Filtering._searchbox.value, "#",
+        "The searchbox value should not be auto-filled when searching inside a file.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch(":");
+      is(Filtering._searchbox.value, ":",
+        "The searchbox value should not be auto-filled when searching for a line.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("*");
+      is(Filtering._searchbox.value, "*",
+        "The searchbox value should not be auto-filled when searching for variables.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
       Editor.setCursor({ line: 7, ch: 0});
-      Filtering._doSearch("@");
-      is(Filtering._searchbox.value, "@test", "Searchbox value should be set to the identifier under cursor if no aText or selection provided");
+      info("Testing with cursor at line 8 and char 1...");
+
+      doSearch();
+      is(Filtering._searchbox.value, "",
+        "The searchbox value should not be auto-filled when searching for files.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("!");
+      is(Filtering._searchbox.value, "!test",
+        "The searchbox value was incorrect when searching across all files.");
+      is(Filtering._searchbox.selectionStart, 1,
+        "The searchbox operator should not be selected");
+      is(Filtering._searchbox.selectionEnd, 5,
+        "The searchbox contents should be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("@");
+      is(Filtering._searchbox.value, "@test",
+        "The searchbox value was incorrect when searching for functions.");
+      is(Filtering._searchbox.selectionStart, 1,
+        "The searchbox operator should not be selected");
+      is(Filtering._searchbox.selectionEnd, 5,
+        "The searchbox contents should be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("#");
+      is(Filtering._searchbox.value, "#test",
+        "The searchbox value should be auto-filled when searching inside a file.");
+      is(Filtering._searchbox.selectionStart, 1,
+        "The searchbox operator should not be selected");
+      is(Filtering._searchbox.selectionEnd, 5,
+        "The searchbox contents should be selected");
+      is(Editor.getSelection(), "test",
+        "The selection in the editor should be 'test'.");
+
+      doSearch(":");
+      is(Filtering._searchbox.value, ":",
+        "The searchbox value should not be auto-filled when searching for a line.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
+      doSearch("*");
+      is(Filtering._searchbox.value, "*",
+        "The searchbox value should not be auto-filled when searching for variables.");
+      is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+        "The searchbox contents should not be selected");
+      is(Editor.getSelection(), "",
+        "The selection in the editor should be empty.");
+
       closeDebuggerAndFinish(aPanel);
     });
   });
 };
--- a/browser/devtools/framework/options-panel.css
+++ b/browser/devtools/framework/options-panel.css
@@ -2,33 +2,32 @@
  * 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/. */
 
 #options-panel-container {
   overflow: auto;
 }
 
 #options-panel {
-  overflow-y: auto;
   display: block;
 }
 
 .options-vertical-pane {
   display: inline;
   float: left;
 }
 
 .options-vertical-pane > label {
   display: block;
 }
 
 .options-vertical-pane {
   margin: 5px;
   width: calc(50% - 30px);
-  min-width: 400px;
+  min-width: 350px;
   -moz-padding-start: 5px;
 }
 
 .options-vertical-pane > label {
   padding: 2px 0;
   font-size: 1.4rem;
 }
 
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -61,16 +61,19 @@
                     tooltiptext="&options.timestampMessages.tooltip;"
                     data-pref="devtools.webconsole.timestampMessages"/>
         </vbox>
         <label value="&options.styleeditor.label;"/>
         <vbox id="styleeditor-options" class="options-groupbox">
           <checkbox label="&options.stylesheetSourceMaps.label;"
                     tooltiptext="&options.stylesheetSourceMaps.tooltip;"
                     data-pref="devtools.styleeditor.source-maps-enabled"/>
+          <checkbox label="&options.stylesheetAutocompletion.label;"
+                    tooltiptext="&options.stylesheetAutocompletion.tooltip;"
+                    data-pref="devtools.styleeditor.autocompletion-enabled"/>
         </vbox>
         <label value="&options.profiler.label;"/>
         <vbox id="profiler-options" class="options-groupbox">
           <checkbox label="&options.showPlatformData.label;"
                     tooltiptext="&options.showPlatformData.tooltip;"
                     data-pref="devtools.profiler.ui.show-platform-data"/>
         </vbox>
         <label value="&options.context.advancedSettings;"/>
--- a/browser/devtools/inspector/selector-search.js
+++ b/browser/devtools/inspector/selector-search.js
@@ -245,36 +245,33 @@ SelectorSearch.prototype = {
 
         if (!query.slice(-1).match(/[\.#\s>+]/)) {
           // Hide the popup if we have some matching nodes and the query is not
           // ending with [.# >] which means that the selector is not at the
           // beginning of a new class, tag or id.
           if (this.searchPopup.isOpen) {
             this.searchPopup.hidePopup();
           }
-        }
-        else {
-          this.showSuggestions();
+          this.searchBox.classList.remove("devtools-no-search-result");
+
+          return this._selectResult(0);
         }
-        this.searchBox.classList.remove("devtools-no-search-result");
-
-        return this._selectResult(0);
+        return this._selectResult(0).then(() => {
+          this.searchBox.classList.remove("devtools-no-search-result");
+        }).then( () => this.showSuggestions());
       }
-      else {
-        if (query.match(/[\s>+]$/)) {
-          this._lastValidSearch = query + "*";
-        }
-        else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
-          let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
-          this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
-        }
-        this.searchBox.classList.add("devtools-no-search-result");
-        this.showSuggestions();
+      if (query.match(/[\s>+]$/)) {
+        this._lastValidSearch = query + "*";
       }
-      return undefined;
+      else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
+        let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
+        this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
+      }
+      this.searchBox.classList.add("devtools-no-search-result");
+      return this.showSuggestions();
     });
   },
 
   /**
    * Handles keypresses inside the input box.
    */
   _onSearchKeypress: function SelectorSearch__onSearchKeypress(aEvent) {
     let query = this.searchBox.value;
@@ -409,23 +406,16 @@ SelectorSearch.prototype = {
     }
   },
 
   
   /**
    * Populates the suggestions list and show the suggestion popup.
    */
   _showPopup: function SelectorSearch__showPopup(aList, aFirstPart) {
-    // Sort alphabetically in increaseing order.
-    aList = aList.sort();
-    // Sort based on count= in decreasing order.
-    aList = aList.sort(function([a1,a2], [b1,b2]) {
-      return a2 < b2;
-    });
-
     let total = 0;
     let query = this.searchBox.value;
     let toLowerCase = false;
     let items = [];
     // In case of tagNames, change the case to small.
     if (query.match(/.*[\.#][^\.#]{0,}$/) == null) {
       toLowerCase = true;
     }
@@ -466,116 +456,49 @@ SelectorSearch.prototype = {
     }
   },
 
   /**
    * Suggests classes,ids and tags based on the user input as user types in the
    * searchbox.
    */
   showSuggestions: function SelectorSearch_showSuggestions() {
-    if (!this.walker.isLocal()) {
-      return;
-    }
     let query = this.searchBox.value;
-    if (this._lastValidSearch != "" &&
-        this._lastToLastValidSearch != this._lastValidSearch) {
-      this._searchSuggestions = {
-        ids: new Map(),
-        classes: new Map(),
-        tags: new Map(),
-      };
-
-      let nodes = [];
-      try {
-        nodes = this.doc.querySelectorAll(this._lastValidSearch);
-      } catch (ex) {}
-      for (let node of nodes) {
-        this._searchSuggestions.ids.set(node.id, 1);
-        this._searchSuggestions.tags
-            .set(node.tagName,
-                 (this._searchSuggestions.tags.get(node.tagName) || 0) + 1);
-        for (let className of node.classList) {
-          this._searchSuggestions.classes
-            .set(className,
-                 (this._searchSuggestions.classes.get(className) || 0) + 1);
-        }
-      }
-      this._lastToLastValidSearch = this._lastValidSearch;
-    }
-    else if (this._lastToLastValidSearch != this._lastValidSearch) {
-      this._searchSuggestions = {
-        ids: new Map(),
-        classes: new Map(),
-        tags: new Map(),
-      };
-
-      if (query.length == 0) {
-        return;
-      }
-
-      let nodes = null;
-      if (this.state == this.States.CLASS) {
-        nodes = this.doc.querySelectorAll("[class]");
-        for (let node of nodes) {
-          for (let className of node.classList) {
-            this._searchSuggestions.classes
-              .set(className,
-                   (this._searchSuggestions.classes.get(className) || 0) + 1);
-          }
-        }
-      }
-      else if (this.state == this.States.ID) {
-        nodes = this.doc.querySelectorAll("[id]");
-        for (let node of nodes) {
-          this._searchSuggestions.ids.set(node.id, 1);
-        }
-      }
-      else if (this.state == this.States.TAG) {
-        nodes = this.doc.getElementsByTagName("*");
-        for (let node of nodes) {
-          this._searchSuggestions.tags
-              .set(node.tagName,
-                   (this._searchSuggestions.tags.get(node.tagName) || 0) + 1);
-        }
-      }
-      else {
-        return;
-      }
-      this._lastToLastValidSearch = this._lastValidSearch;
-    }
-
-    // Filter the suggestions based on search box value.
-    let result = [];
     let firstPart = "";
     if (this.state == this.States.TAG) {
       // gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
       // 'di' returns 'di' and likewise.
-      firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["",query])[1];
-      for (let [tag, count] of this._searchSuggestions.tags) {
-        if (tag.toLowerCase().startsWith(firstPart.toLowerCase())) {
-          result.push([tag, count]);
-        }
-      }
+      firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
+      query = query.slice(0, query.length - firstPart.length);
     }
     else if (this.state == this.States.CLASS) {
       // gets the class that is being completed. For ex. '.foo.b' returns 'b'
       firstPart = query.match(/\.([^\.]*)$/)[1];
-      for (let [className, count] of this._searchSuggestions.classes) {
-        if (className.startsWith(firstPart)) {
-          result.push(["." + className, count]);
-        }
-      }
-      firstPart = "." + firstPart;
+      query = query.slice(0, query.length - firstPart.length - 1);
     }
     else if (this.state == this.States.ID) {
       // gets the id that is being completed. For ex. '.foo#b' returns 'b'
       firstPart = query.match(/#([^#]*)$/)[1];
-      for (let [id, count] of this._searchSuggestions.ids) {
-        if (id.startsWith(firstPart)) {
-          result.push(["#" + id, 1]);
-        }
+      query = query.slice(0, query.length - firstPart.length - 1);
+    }
+    // TODO: implement some caching so that over the wire request is not made
+    // everytime.
+    if (/[\s+>~]$/.test(query)) {
+      query += "*";
+    }
+    this._currentSuggesting = query;
+    return this.walker.getSuggestionsForQuery(query, firstPart, this.state).then(result => {
+      if (this._currentSuggesting != result.query) {
+        // This means that this response is for a previous request and the user
+        // as since typed something extra leading to a new request.
+        return;
       }
-      firstPart = "#" + firstPart;
-    }
-
-    this._showPopup(result, firstPart);
+      this._lastToLastValidSearch = this._lastValidSearch;
+      if (this.state == this.States.CLASS) {
+        firstPart = "." + firstPart;
+      }
+      else if (this.state == this.States.ID) {
+        firstPart = "#" + firstPart;
+      }
+      this._showPopup(result.suggestions, firstPart);
+    });
   },
 };
--- a/browser/devtools/inspector/test/browser_inspector_bug_674871.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_674871.js
@@ -63,17 +63,18 @@ function test()
     });
   }
 
   function isTheIframeHighlighted()
   {
     let outlineRect = getHighlighterOutlineRect();
     let iframeRect = iframeNode.getBoundingClientRect();
     for (let dim of ["width", "height", "top", "left"]) {
-      is(Math.floor(outlineRect[dim]), Math.floor(iframeRect[dim]), "Outline dimension is correct");
+      is(Math.floor(outlineRect[dim]), Math.floor(iframeRect[dim]),
+         "Outline dimension is correct " + outlineRect[dim]);
     }
 
     iframeNode.style.marginBottom = doc.defaultView.innerHeight + "px";
     doc.defaultView.scrollBy(0, 40);
 
     moveMouseOver(iframeNode, 40, 40, isTheIframeContentHighlighted);
   }
 
@@ -96,13 +97,13 @@ function test()
   {
     doc = inspector = iframeNode = iframeBodyNode = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 
   function moveMouseOver(aElement, x, y, cb)
   {
+    inspector.toolbox.once("picker-node-hovered", cb);
     EventUtils.synthesizeMouse(aElement, x, y, {type: "mousemove"},
                                aElement.ownerDocument.defaultView);
-    inspector.toolbox.once("picker-node-hovered", cb);
   }
 }
--- a/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_combinator_suggestions.js
@@ -84,18 +84,17 @@ function test()
          JSON.stringify(suggestions));
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
   }
 
   function checkState(event) {
     inspector.searchSuggestions._lastQuery.then(() => {
       let [key, suggestions] = keyStates[state];
       let actualSuggestions = popup.getItems();
-      is(popup._panel.state == "open" || popup._panel.state == "showing"
-         ? actualSuggestions.length: 0, suggestions.length,
+      is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
          "There are expected number of suggestions at " + state + "th step.");
       actualSuggestions = actualSuggestions.reverse();
       for (let i = 0; i < suggestions.length; i++) {
         is(suggestions[i][0], actualSuggestions[i].label,
            "The suggestion at " + i + "th index for " + state +
            "th step is correct.")
         is(suggestions[i][1] || 1, actualSuggestions[i].count,
            "The count for suggestion at " + i + "th index for " + state +
--- a/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_831693_input_suggestion.js
@@ -85,18 +85,17 @@ function test()
          JSON.stringify(suggestions));
     EventUtils.synthesizeKey(key, {}, inspector.panelWin);
   }
 
   function checkState(event) {
     inspector.searchSuggestions._lastQuery.then(() => {
       let [key, suggestions] = keyStates[state];
       let actualSuggestions = popup.getItems();
-      is(popup._panel.state == "open" || popup._panel.state == "showing"
-         ? actualSuggestions.length: 0, suggestions.length,
+      is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
          "There are expected number of suggestions at " + state + "th step.");
       actualSuggestions = actualSuggestions.reverse();
       for (let i = 0; i < suggestions.length; i++) {
         is(suggestions[i][0], actualSuggestions[i].label,
            "The suggestion at " + i + "th index for " + state +
            "th step is correct.")
         is(suggestions[i][1] || 1, actualSuggestions[i].count,
            "The count for suggestion at " + i + "th index for " + state +
--- a/browser/devtools/inspector/test/browser_inspector_iframeTest.js
+++ b/browser/devtools/inspector/test/browser_inspector_iframeTest.js
@@ -26,38 +26,37 @@ function createDocument() {
     iframe2 = iframe1.contentDocument.createElement('iframe');
 
     iframe2.addEventListener('load', function () {
       iframe2.removeEventListener("load", arguments.callee, false);
 
       div2 = iframe2.contentDocument.createElement('div');
       div2.textContent = 'nested div';
       iframe2.contentDocument.body.appendChild(div2);
-
       // Open the inspector, start the picker mode, and start the tests
       openInspector(aInspector => {
         inspector = aInspector;
         inspector.toolbox.startPicker().then(runTests);
       });
     }, false);
 
     iframe2.src = 'data:text/html,nested iframe';
     iframe1.contentDocument.body.appendChild(iframe2);
   }, false);
 
   iframe1.src = 'data:text/html,little iframe';
   doc.body.appendChild(iframe1);
 }
 
 function moveMouseOver(aElement, cb) {
-  EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"},
-    aElement.ownerDocument.defaultView);
   inspector.toolbox.once("picker-node-hovered", () => {
     executeSoon(cb);
   });
+  EventUtils.synthesizeMouseAtCenter(aElement, {type: "mousemove"},
+    aElement.ownerDocument.defaultView);
 }
 
 function runTests() {
   testDiv1Highlighter();
 }
 
 function testDiv1Highlighter() {
   moveMouseOver(div1, () => {
--- a/browser/devtools/markupview/test/browser_bug896181_css_mixed_completion_new_attribute.js
+++ b/browser/devtools/markupview/test/browser_bug896181_css_mixed_completion_new_attribute.js
@@ -125,19 +125,17 @@ function test() {
       if (selEnd != -1) {
         is(editor.input.value, completion,
            "Correct value is autocompleted for state " + state);
         is(editor.input.selectionStart, selStart,
            "Selection is starting at the right location for state " + state);
         is(editor.input.selectionEnd, selEnd,
            "Selection is ending at the right location for state " + state);
         if (popupOpen) {
-          ok(editor.popup._panel.state == "open" ||
-             editor.popup._panel.state == "showing",
-             "Popup is open for state " + state);
+          ok(editor.popup.isOpen, "Popup is open for state " + state);
         }
         else {
           ok(editor.popup._panel.state != "open" &&
              editor.popup._panel.state != "showing",
              "Popup is closed for state " + state);
         }
       }
       else {
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -66,25 +66,27 @@ let NetMonitorView = {
    * Initializes the network monitor view.
    */
   initialize: function() {
     this._initializePanes();
 
     this.Toolbar.initialize();
     this.RequestsMenu.initialize();
     this.NetworkDetails.initialize();
+    this.CustomRequest.initialize();
   },
 
   /**
    * Destroys the network monitor view.
    */
   destroy: function() {
     this.Toolbar.destroy();
     this.RequestsMenu.destroy();
     this.NetworkDetails.destroy();
+    this.CustomRequest.destroy();
 
     this._destroyPanes();
   },
 
   /**
    * Initializes the UI for all the displayed panes.
    */
   _initializePanes: function() {
@@ -266,27 +268,63 @@ RequestsMenuView.prototype = Heritage.ex
 
     this.allowFocusOnRightClick = true;
     this.widget.maintainSelectionVisible = false;
     this.widget.autoscrollWithAppendedItems = true;
 
     this.widget.addEventListener("select", this._onSelect, false);
     this._splitter.addEventListener("mousemove", this._onResize, false);
     window.addEventListener("resize", this._onResize, false);
+
+    this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
+    this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
+    this.clearEvent = this.clear.bind(this);
+    this._onContextShowing = this._onContextShowing.bind(this);
+    this._onContextNewTabCommand = this.openRequestInTab.bind(this);
+    this._onContextCopyUrlCommand = this.copyUrl.bind(this);
+    this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
+
+    this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
+    this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
+    this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
+
+    $("#toolbar-labels").addEventListener("click", this.requestsMenuSortEvent, false);
+    $("#requests-menu-footer").addEventListener("click", this.requestsMenuFilterEvent, false);
+    $("#requests-menu-clear-button").addEventListener("click", this.clearEvent, false);
+    $("#network-request-popup").addEventListener("popupshowing", this._onContextShowing, false);
+    $("#request-menu-context-newtab").addEventListener("command", this._onContextNewTabCommand, false);
+    $("#request-menu-context-copy-url").addEventListener("command", this._onContextCopyUrlCommand, false);
+    $("#request-menu-context-resend").addEventListener("command", this._onContextResendCommand, false);
+
+    $("#custom-request-send-button").addEventListener("click", this.sendCustomRequestEvent, false);
+    $("#custom-request-close-button").addEventListener("click", this.closeCustomRequestEvent, false);
+    $("#headers-summary-resend").addEventListener("click", this.cloneSelectedRequestEvent, false);
   },
 
   /**
    * Destruction function, called when the network monitor is closed.
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
     this.widget.removeEventListener("select", this._onSelect, false);
     this._splitter.removeEventListener("mousemove", this._onResize, false);
     window.removeEventListener("resize", this._onResize, false);
+
+    $("#toolbar-labels").removeEventListener("click", this.requestsMenuSortEvent, false);
+    $("#requests-menu-footer").removeEventListener("click", this.requestsMenuFilterEvent, false);
+    $("#requests-menu-clear-button").removeEventListener("click", this.clearEvent, false);
+    $("#network-request-popup").removeEventListener("popupshowing", this._onContextShowing, false);
+    $("#request-menu-context-newtab").removeEventListener("command", this._onContextNewTabCommand, false);
+    $("#request-menu-context-copy-url").removeEventListener("command", this._onContextCopyUrlCommand, false);
+    $("#request-menu-context-resend").removeEventListener("command", this._onContextResendCommand, false);
+
+    $("#custom-request-send-button").removeEventListener("click", this.sendCustomRequestEvent, false);
+    $("#custom-request-close-button").removeEventListener("click", this.closeCustomRequestEvent, false);
+    $("#headers-summary-resend").removeEventListener("click", this.cloneSelectedRequestEvent, false);
   },
 
   /**
    * Resets this container (removes all the networking information).
    */
   reset: function() {
     this.empty();
     this._firstRequestStartedMillis = -1;
@@ -1431,16 +1469,36 @@ SidebarView.prototype = {
  * Functions handling the custom request view.
  */
 function CustomRequestView() {
   dumpn("CustomRequestView was instantiated");
 }
 
 CustomRequestView.prototype = {
   /**
+   * Initialization function, called when the network monitor is started.
+   */
+  initialize: function() {
+    dumpn("Initializing the CustomRequestView");
+
+    this.updateCustomRequestEvent = getKeyWithEvent(this.onUpdate.bind(this));
+
+    $("#custom-pane").addEventListener("input", this.updateCustomRequestEvent, false);
+  },
+
+  /**
+   * Destruction function, called when the network monitor is closed.
+   */
+  destroy: function() {
+    dumpn("Destroying the CustomRequestView");
+
+    $("#custom-pane").removeEventListener("input", this.updateCustomRequestEvent, false);
+  },
+
+  /**
    * Populates this view with the specified data.
    *
    * @param object aData
    *        The data source (this should be the attachment of a request item).
    * @return object
    *        Returns a promise that resolves upon population the view.
    */
   populate: function(aData) {
@@ -2233,15 +2291,32 @@ function writeQueryText(aParams) {
  * @return string
  *         Query string that can be appended to a url.
  */
 function writeQueryString(aParams) {
   return [(name + "=" + value) for ({name, value} of aParams)].join("&");
 }
 
 /**
+ * Helper method to get a wrapped function which can be bound to as an event listener directly and is executed only when data-key is present in event.target.
+ *
+ * @param function callback
+ *          Function to execute execute when data-key is present in event.target.
+ * @return function
+ *          Wrapped function with the target data-key as the first argument.
+ */
+function getKeyWithEvent(callback) {
+  return function(event) {
+    var key = event.target.getAttribute("data-key");
+    if (key) {
+      callback.call(null, key);
+    }
+  };
+}
+
+/**
  * Preliminary setup for the NetMonitorView object.
  */
 NetMonitorView.Toolbar = new ToolbarView();
 NetMonitorView.RequestsMenu = new RequestsMenuView();
 NetMonitorView.Sidebar = new SidebarView();
 NetMonitorView.CustomRequest = new CustomRequestView();
 NetMonitorView.NetworkDetails = new NetworkDetailsView();
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -16,103 +16,99 @@
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript;version=1.8"
           src="chrome://browser/content/devtools/theme-switching.js"/>
   <script type="text/javascript" src="netmonitor-controller.js"/>
   <script type="text/javascript" src="netmonitor-view.js"/>
 
   <popupset id="networkPopupSet">
-    <menupopup id="network-request-popup"
-               onpopupshowing="NetMonitorView.RequestsMenu._onContextShowing(event);">
+    <menupopup id="network-request-popup">
       <menuitem id="request-menu-context-newtab"
                 label="&netmonitorUI.context.newTab;"
-                accesskey="&netmonitorUI.context.newTab.accesskey;"
-                oncommand="NetMonitorView.RequestsMenu.openRequestInTab();"/>
+                accesskey="&netmonitorUI.context.newTab.accesskey;"/>
       <menuitem id="request-menu-context-copy-url"
                 label="&netmonitorUI.context.copyUrl;"
-                accesskey="&netmonitorUI.context.copyUrl.accesskey;"
-                oncommand="NetMonitorView.RequestsMenu.copyUrl();"/>
+                accesskey="&netmonitorUI.context.copyUrl.accesskey;"/>
       <menuitem id="request-menu-context-resend"
                 label="&netmonitorUI.summary.editAndResend;"
-                accesskey="&netmonitorUI.summary.editAndResend.accesskey;"
-                oncommand="NetMonitorView.RequestsMenu.cloneSelectedRequest();"/>
+                accesskey="&netmonitorUI.summary.editAndResend.accesskey;"/>
     </menupopup>
   </popupset>
 
   <box id="body"
        class="devtools-responsive-container theme-body"
        flex="1">
     <vbox id="network-table" flex="1">
       <toolbar id="requests-menu-toolbar"
                class="devtools-toolbar"
                align="center">
         <hbox id="toolbar-labels" flex="1">
           <hbox id="requests-menu-status-and-method-header-box"
                 class="requests-menu-header requests-menu-status-and-method"
                 align="center">
             <button id="requests-menu-status-button"
                     class="requests-menu-header-button requests-menu-status"
-                    onclick="NetMonitorView.RequestsMenu.sortBy('status')"
+                    data-key="status"
                     label="&netmonitorUI.toolbar.status2;">
             </button>
             <button id="requests-menu-method-button"
                     class="requests-menu-header-button requests-menu-method"
-                    onclick="NetMonitorView.RequestsMenu.sortBy('method')"
+                    data-key="method"
                     label="&netmonitorUI.toolbar.method;"
                     flex="1">
             </button>
           </hbox>
           <hbox id="requests-menu-file-header-box"
                 class="requests-menu-header requests-menu-file"
                 align="center">
             <button id="requests-menu-file-button"
                     class="requests-menu-header-button requests-menu-file"
-                    onclick="NetMonitorView.RequestsMenu.sortBy('file')"
+                    data-key="file"
                     label="&netmonitorUI.toolbar.file;"
                     flex="1">
             </button>
           </hbox>
           <hbox id="requests-menu-domain-header-box"
                 class="requests-menu-header requests-menu-domain"
                 align="center">
             <button id="requests-menu-domain-button"
                     class="requests-menu-header-button requests-menu-domain"
-                    onclick="NetMonitorView.RequestsMenu.sortBy('domain')"
+                    data-key="domain"
                     label="&netmonitorUI.toolbar.domain;"
                     flex="1">
             </button>
           </hbox>
           <hbox id="requests-menu-type-header-box"
                 class="requests-menu-header requests-menu-type"
                 align="center">
             <button id="requests-menu-type-button"
                     class="requests-menu-header-button requests-menu-type"
-                    onclick="NetMonitorView.RequestsMenu.sortBy('type')"
+                    data-key="type"
                     label="&netmonitorUI.toolbar.type;"
                     flex="1">
             </button>
           </hbox>
           <hbox id="requests-menu-size-header-box"
                 class="requests-menu-header requests-menu-size"
                 align="center">
             <button id="requests-menu-size-button"
                     class="requests-menu-header-button requests-menu-size"
-                    onclick="NetMonitorView.RequestsMenu.sortBy('size')"
+                    data-key="size"
                     label="&netmonitorUI.toolbar.size;"
                     flex="1">
             </button>
           </hbox>
           <hbox id="requests-menu-waterfall-header-box"
                 class="requests-menu-header requests-menu-waterfall"
                 align="center"
                 flex="1">
             <button id="requests-menu-waterfall-button"
                     class="requests-menu-header-button requests-menu-waterfall"
-                    onclick="NetMonitorView.RequestsMenu.sortBy('waterfall')"
+                    data-key="waterfall"
                     pack="start"
                     flex="1">
               <label id="requests-menu-waterfall-label"
                      class="plain requests-menu-waterfall"
                      value="&netmonitorUI.toolbar.waterfall;"/>
             </button>
           </hbox>
         </hbox>
@@ -155,134 +151,133 @@
       </vbox>
       <hbox id="requests-menu-footer">
         <spacer id="requests-menu-spacer-start"
                 class="requests-menu-footer-spacer"
                 flex="100"/>
         <button id="requests-menu-filter-all-button"
                 class="requests-menu-footer-button"
                 checked="true"
-                onclick="NetMonitorView.RequestsMenu.filterOn('all')"
+                data-key="all"
                 label="&netmonitorUI.footer.filterAll;">
         </button>
         <button id="requests-menu-filter-html-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('html')"
+                data-key="html"
                 label="&netmonitorUI.footer.filterHTML;">
         </button>
         <button id="requests-menu-filter-css-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('css')"
+                data-key="css"
                 label="&netmonitorUI.footer.filterCSS;">
         </button>
         <button id="requests-menu-filter-js-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('js')"
+                data-key="js"
                 label="&netmonitorUI.footer.filterJS;">
         </button>
         <button id="requests-menu-filter-xhr-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('xhr')"
+                data-key="xhr"
                 label="&netmonitorUI.footer.filterXHR;">
         </button>
         <button id="requests-menu-filter-fonts-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('fonts')"
+                data-key="fonts"
                 label="&netmonitorUI.footer.filterFonts;">
         </button>
         <button id="requests-menu-filter-images-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('images')"
+                data-key="images"
                 label="&netmonitorUI.footer.filterImages;">
         </button>
         <button id="requests-menu-filter-media-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('media')"
+                data-key="media"
                 label="&netmonitorUI.footer.filterMedia;">
         </button>
         <button id="requests-menu-filter-flash-button"
                 class="requests-menu-footer-button"
-                onclick="NetMonitorView.RequestsMenu.filterOn('flash')"
+                data-key="flash"
                 label="&netmonitorUI.footer.filterFlash;">
         </button>
         <spacer id="requests-menu-spacer-end"
                 class="requests-menu-footer-spacer"
                 flex="100"/>
         <label id="request-menu-network-summary"
                class="plain requests-menu-footer-label"
                flex="1"
                crop="end"/>
         <button id="requests-menu-clear-button"
                class="requests-menu-footer-button"
-               onclick="NetMonitorView.RequestsMenu.clear()"
                label="&netmonitorUI.footer.clear;">
         </button>
       </hbox>
     </vbox>
 
     <splitter id="splitter" class="devtools-side-splitter"/>
 
     <deck id="details-pane"
           hidden="true">
       <vbox id="custom-pane"
             class="tabpanel-content">
         <hbox align="baseline">
           <label value="&netmonitorUI.custom.newRequest;"
                  class="plain tabpanel-summary-label
                         custom-header"/>
           <hbox flex="1" pack="end">
-            <button class="devtools-toolbarbutton"
-                    label="&netmonitorUI.custom.send;"
-                    onclick="NetMonitorView.RequestsMenu.sendCustomRequest();"/>
-            <button class="devtools-toolbarbutton"
-                    label="&netmonitorUI.custom.cancel;"
-                    onclick="NetMonitorView.RequestsMenu.closeCustomRequest();"/>
+            <button id="custom-request-send-button"
+                    class="devtools-toolbarbutton"
+                    label="&netmonitorUI.custom.send;"/>
+            <button id="custom-request-close-button"
+                    class="devtools-toolbarbutton"
+                    label="&netmonitorUI.custom.cancel;"/>
           </hbox>
         </hbox>
         <hbox id="custom-method-and-url"
               class="tabpanel-summary-container"
               align="center">
           <textbox id="custom-method-value"
-                   oninput="NetMonitorView.CustomRequest.onUpdate('method');"/>
+                   data-key="method"/>
           <textbox id="custom-url-value"
                    flex="1"
-                   oninput="NetMonitorView.CustomRequest.onUpdate('url');"/>
+                   data-key="url"/>
         </hbox>
         <vbox id="custom-query"
               class="tabpanel-summary-container custom-section">
           <label class="plain tabpanel-summary-label"
                  value="&netmonitorUI.custom.query;"/>
           <textbox id="custom-query-value"
                    class="tabpanel-summary-input"
                    multiline="true"
                    rows="4"
                    wrap="off"
-                   oninput="NetMonitorView.CustomRequest.onUpdate('query');"/>
+                   data-key="query"/>
         </vbox>
         <vbox id="custom-headers"
               class="tabpanel-summary-container custom-section">
           <label class="plain tabpanel-summary-label"
                  value="&netmonitorUI.custom.headers;"/>
           <textbox id="custom-headers-value"
                    class="tabpanel-summary-input"
                    multiline="true"
                    rows="8"
                    wrap="off"
-                   oninput="NetMonitorView.CustomRequest.onUpdate('headers');"/>
+                   data-key="headers"/>
         </vbox>
         <vbox id="custom-postdata"
               class="tabpanel-summary-container custom-section">
           <label class="plain tabpanel-summary-label"
                  value="&netmonitorUI.custom.postData;"/>
           <textbox id="custom-postdata-value"
                    class="tabpanel-summary-input"
                    multiline="true"
                    rows="6"
                    wrap="off"
-                   oninput="NetMonitorView.CustomRequest.onUpdate('body');"/>
+                   data-key="body"/>
         </vbox>
       </vbox>
       <tabbox id="event-details-pane"
               class="devtools-sidebar-tabs"
               handleCtrlTab="false">
         <tabs>
           <tab label="&netmonitorUI.tab.headers;"/>
           <tab label="&netmonitorUI.tab.cookies;"/>
@@ -321,19 +316,18 @@
                        value="&netmonitorUI.summary.status;"/>
                 <box id="headers-summary-status-circle"
                      class="requests-menu-status"/>
                 <label id="headers-summary-status-value"
                        class="plain tabpanel-summary-value devtools-monospace"
                        crop="end"
                        flex="1"/>
                 <button id="headers-summary-resend"
-                       label="&netmonitorUI.summary.editAndResend;"
-                       class="devtools-toolbarbutton"
-                       onclick="NetMonitorView.RequestsMenu.cloneSelectedRequest();"/>
+                        class="devtools-toolbarbutton"
+                        label="&netmonitorUI.summary.editAndResend;"/>
               </hbox>
               <hbox id="headers-summary-version"
                     class="tabpanel-summary-container"
                     align="center">
                 <label class="plain tabpanel-summary-label"
                        value="&netmonitorUI.summary.version;"/>
                 <label id="headers-summary-version-value"
                        class="plain tabpanel-summary-value devtools-monospace"
--- a/browser/devtools/shared/Parser.jsm
+++ b/browser/devtools/shared/Parser.jsm
@@ -80,17 +80,17 @@ Parser.prototype = {
           syntaxTrees.push(new SyntaxTree(nodes, aUrl, length, offset));
         } catch (e) {
           this.errors.push(e);
           DevToolsUtils.reportException(aUrl, e);
         }
       }
     }
 
-    let pool = new SyntaxTreesPool(syntaxTrees);
+    let pool = new SyntaxTreesPool(syntaxTrees, aUrl);
 
     // Cache the syntax trees pool by the specified url. This is entirely
     // optional, but it's strongly encouraged to cache ASTs because
     // generating them can be costly with big/complex sources.
     if (aUrl) {
       this._cache.set(aUrl, pool);
     }
 
@@ -118,28 +118,31 @@ Parser.prototype = {
   errors: null
 };
 
 /**
  * A pool handling a collection of AST nodes generated by the reflection API.
  *
  * @param object aSyntaxTrees
  *        A collection of AST nodes generated for a source.
+ * @param string aUrl [optional]
+ *        The source url.
  */
-function SyntaxTreesPool(aSyntaxTrees) {
+function SyntaxTreesPool(aSyntaxTrees, aUrl = "<unknown>") {
   this._trees = aSyntaxTrees;
+  this._url = aUrl;
   this._cache = new Map();
 }
 
 SyntaxTreesPool.prototype = {
   /**
    * @see SyntaxTree.prototype.getIdentifierAt
    */
   getIdentifierAt: function({ line, column, scriptIndex }) {
-    return this._first(this._call("getIdentifierAt", scriptIndex, line, column));
+    return this._call("getIdentifierAt", scriptIndex, line, column)[0];
   },
 
   /**
    * @see SyntaxTree.prototype.getNamedFunctionDefinitions
    */
   getNamedFunctionDefinitions: function(aSubstring) {
     return this._call("getNamedFunctionDefinitions", -1, aSubstring);
   },
@@ -173,28 +176,16 @@ SyntaxTreesPool.prototype = {
       }
     }
 
     info.index = -1;
     return info;
   },
 
   /**
-   * Gets the first script results from a source results set.
-   * If no results are found, null is returned.
-   *
-   * @return array
-   *         A collection of parse results for the first script in a source.
-   */
-  _first: function(aSourceResults) {
-    let scriptResult = aSourceResults.filter(e => !!e.parseResults)[0];
-    return scriptResult ? scriptResult.parseResults : null;
-  },
-
-  /**
    * Handles a request for a specific or all known syntax trees.
    *
    * @param string aFunction
    *        The function name to call on the SyntaxTree instances.
    * @param number aSyntaxTreeIndex
    *        The syntax tree for which to handle the request. If the tree at
    *        the specified index isn't found, the accumulated results for all
    *        syntax trees are returned.
@@ -211,27 +202,28 @@ SyntaxTreesPool.prototype = {
       return this._cache.get(requestId);
     }
 
     let requestedTree = this._trees[aSyntaxTreeIndex];
     let targettedTrees = requestedTree ? [requestedTree] : this._trees;
 
     for (let syntaxTree of targettedTrees) {
       try {
-        results.push({
-          sourceUrl: syntaxTree.url,
-          scriptLength: syntaxTree.length,
-          scriptOffset: syntaxTree.offset,
-          parseResults: syntaxTree[aFunction].apply(syntaxTree, aParams)
-        });
+        let parseResults = syntaxTree[aFunction].apply(syntaxTree, aParams);
+        if (parseResults) {
+          parseResults.sourceUrl = syntaxTree.url;
+          parseResults.scriptLength = syntaxTree.length;
+          parseResults.scriptOffset = syntaxTree.offset;
+          results.push(parseResults);
+        }
       } catch (e) {
         // Can't guarantee that the tree traversal logic is forever perfect :)
         // Language features may be added, in which case the recursive methods
         // need to be updated. If an exception is thrown here, file a bug.
-        DevToolsUtils.reportException("syntax tree", e);
+        DevToolsUtils.reportException("Syntax tree visitor for " + aUrl, e);
       }
     }
     this._cache.set(requestId, results);
     return results;
   },
 
   _trees: null,
   _cache: null
--- a/browser/devtools/shared/autocomplete-popup.js
+++ b/browser/devtools/shared/autocomplete-popup.js
@@ -154,17 +154,17 @@ AutocompletePopup.prototype = {
   {
     this._panel.hidePopup();
   },
 
   /**
    * Check if the autocomplete popup is open.
    */
   get isOpen() {
-    return this._panel.state == "open";
+    return this._panel.state == "open" || this._panel.state == "showing";
   },
 
   /**
    * Destroy the object instance. Please note that the panel DOM elements remain
    * in the DOM, because they might still be in use by other instances of the
    * same code. It is the responsability of the client code to perform DOM
    * cleanup.
    */
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/autocomplete.js
@@ -0,0 +1,179 @@
+/* vim:set ts=2 sw=2 sts=2 et tw=80:
+ * 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/. */
+
+const cssAutoCompleter = require("devtools/sourceeditor/css-autocompleter");
+const { AutocompletePopup } = require("devtools/shared/autocomplete-popup");
+
+const privates = new WeakMap();
+
+/**
+ * Prepares an editor instance for autocompletion, setting up the popup and the
+ * CSS completer instance.
+ */
+function setupAutoCompletion(ctx, walker) {
+  let { cm, ed, Editor } = ctx;
+
+  let win = ed.container.contentWindow.wrappedJSObject;
+
+  let completer = null;
+  if (ed.config.mode == Editor.modes.css)
+    completer = new cssAutoCompleter({walker: walker});
+
+  let popup = new AutocompletePopup(win.parent.document, {
+    position: "after_start",
+    fixedWidth: true,
+    theme: "auto",
+    autoSelect: true
+  });
+
+  let keyMap = {
+    "Tab": cm => {
+      if (popup && popup.isOpen) {
+        cycleSuggestions(ed);
+        return;
+      }
+
+      return win.CodeMirror.Pass;
+    },
+    "Shift-Tab": cm => {
+      if (popup && popup.isOpen) {
+        cycleSuggestions(ed, true);
+        return;
+      }
+
+      return win.CodeMirror.Pass;
+    },
+  };
+  keyMap[Editor.accel("Space")] = cm => autoComplete(ctx);
+  cm.addKeyMap(keyMap);
+
+  cm.on("keydown", (cm, e) => onEditorKeypress(ed, e));
+  ed.on("change", () => autoComplete(ctx));
+  ed.on("destroy", () => {
+    cm.off("keydown", (cm, e) => onEditorKeypress(ed, e));
+    ed.off("change", () => autoComplete(ctx));
+    popup.destroy();
+    popup = null;
+    completer = null;
+  });
+
+  privates.set(ed, {
+    popup: popup,
+    completer: completer,
+    insertingSuggestion: false,
+    suggestionInsertedOnce: false
+  });
+}
+
+/**
+ * Provides suggestions to autocomplete the current token/word being typed.
+ */
+function autoComplete({ ed, cm }) {
+  let private = privates.get(ed);
+  let { completer, popup } = private;
+  if (!completer || private.insertingSuggestion || private.doNotAutocomplete) {
+    private.insertingSuggestion = false;
+    return;
+  }
+  let cur = ed.getCursor();
+  completer.complete(cm.getRange({line: 0, ch: 0}, cur), cur)
+    .then(suggestions => {
+    if (!suggestions || !suggestions.length || !suggestions[0].preLabel) {
+      private.suggestionInsertedOnce = false;
+      popup.hidePopup();
+      ed.emit("after-suggest");
+      return;
+    }
+    // The cursor is at the end of the currently entered part of the token, like
+    // "backgr|" but we need to open the popup at the beginning of the character
+    // "b". Thus we need to calculate the width of the entered part of the token
+    // ("backgr" here). 4 comes from the popup's left padding.
+    let left = suggestions[0].preLabel.length * cm.defaultCharWidth() + 4;
+    popup.hidePopup();
+    popup.setItems(suggestions);
+    popup.openPopup(cm.display.cursor, -1 * left, 0);
+    private.suggestionInsertedOnce = false;
+    // This event is used in tests.
+    ed.emit("after-suggest");
+  });
+}
+
+/**
+ * Cycles through provided suggestions by the popup in a top to bottom manner
+ * when `reverse` is not true. Opposite otherwise.
+ */
+function cycleSuggestions(ed, reverse) {
+  let private = privates.get(ed);
+  let { popup, completer } = private;
+  let cur = ed.getCursor();
+  private.insertingSuggestion = true;
+  if (!private.suggestionInsertedOnce) {
+    private.suggestionInsertedOnce = true;
+    let firstItem;
+    if (reverse) {
+      firstItem = popup.getItemAtIndex(popup.itemCount - 1);
+      popup.selectPreviousItem();
+    } else {
+      firstItem = popup.getItemAtIndex(0);
+      if (firstItem.label == firstItem.preLabel && popup.itemCount > 1) {
+        firstItem = popup.getItemAtIndex(1);
+        popup.selectNextItem();
+      }
+    }
+    if (popup.itemCount == 1)
+      popup.hidePopup();
+    ed.replaceText(firstItem.label.slice(firstItem.preLabel.length), cur, cur);
+  } else {
+    let fromCur = {
+      line: cur.line,
+      ch  : cur.ch - popup.selectedItem.label.length
+    };
+    if (reverse)
+      popup.selectPreviousItem();
+    else
+      popup.selectNextItem();
+    ed.replaceText(popup.selectedItem.label, fromCur, cur);
+  }
+  // This event is used in tests.
+  ed.emit("suggestion-entered");
+}
+
+/**
+ * onkeydown handler for the editor instance to prevent autocompleting on some
+ * keypresses.
+ */
+function onEditorKeypress(ed, event) {
+  let private = privates.get(ed);
+  switch (event.keyCode) {
+    case event.DOM_VK_UP:
+    case event.DOM_VK_DOWN:
+    case event.DOM_VK_LEFT:
+    case event.DOM_VK_RIGHT:
+    case event.DOM_VK_HOME:
+    case event.DOM_VK_END:
+    case event.DOM_VK_BACK_SPACE:
+    case event.DOM_VK_DELETE:
+    case event.DOM_VK_ENTER:
+    case event.DOM_VK_RETURN:
+    case event.DOM_VK_ESCAPE:
+      private.doNotAutocomplete = true;
+      private.popup.hidePopup();
+      break;
+
+    default:
+      private.doNotAutocomplete = false;
+  }
+}
+
+/**
+ * Returns the private popup. This method is used by tests to test the feature.
+ */
+function getPopup({ ed }) {
+  return privates.get(ed).popup;
+}
+// Export functions
+
+module.exports.setupAutoCompletion = setupAutoCompletion;
+module.exports.getAutocompletionPopup = getPopup;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/css-autocompleter.js
@@ -0,0 +1,798 @@
+/* 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/. */
+
+const { Cc, Ci, Cu } = require('chrome');
+const cssTokenizer  = require("devtools/sourceeditor/css-tokenizer");
+const promise = Cu.import("resource://gre/modules/Promise.jsm");
+
+/**
+ * Here is what this file (+ ./css-tokenizer.js) do.
+ *
+ * The main objective here is to provide as much suggestions to the user editing
+ * a stylesheet in Style Editor. The possible things that can be suggested are:
+ *  - CSS property names
+ *  - CSS property values
+ *  - CSS Selectors
+ *  - Some other known CSS keywords
+ *
+ * Gecko provides a list of both property names and their corresponding values.
+ * We take out a list of matching selectors using the Inspector actor's
+ * `getSuggestionsForQuery` method. Now the only thing is to parse the CSS being
+ * edited by the user, figure out what token or word is being written and last
+ * but the most difficult, what is being edited.
+ *
+ * The file 'css-tokenizer' helps in converting the CSS into meaningful tokens,
+ * each having a certain type associated with it. These tokens help us to figure
+ * out the currently edited word and to write a CSS state machine to figure out
+ * what the user is currently editing. By that, I mean, whether he is editing a
+ * selector or a property or a value, or even fine grained information like an
+ * id in the selector.
+ *
+ * The `resolveState` method iterated over the tokens spitted out by the
+ * tokenizer, using switch cases, follows a state machine logic and finally
+ * figures out these informations:
+ *  - The state of the CSS at the cursor (one out of CSS_STATES)
+ *  - The current token that is being edited `cmpleting`
+ *  - If the state is "selector", the selector state (one of SELECTOR_STATES)
+ *  - If the state is "selector", the current selector till the cursor
+ *  - If the state is "value", the corresponding property name
+ *
+ * In case of "value" and "property" states, we simply use the information
+ * provided by Gecko to filter out the possible suggestions.
+ * For "selector" state, we request the Inspector actor to query the page DOM
+ * and filter out the possible suggestions.
+ * For "media" and "keyframes" state, the only possible suggestions for now are
+ * "media" and "keyframes" respectively, although "media" can have suggestions
+ * like "max-width", "orientation" etc. Similarly "value" state can also have
+ * much better logical suggestions if we fine grain identify a sub state just
+ * like we do for the "selector" state.
+ */
+
+// Autocompletion types.
+
+const CSS_STATES = {
+  "null": "null",
+  property: "property",    // foo { bar|: … }
+  value: "value",          // foo {bar: baz|}
+  selector: "selector",    // f| {bar: baz}
+  media: "media",          // @med| , or , @media scr| { }
+  keyframes: "keyframes",  // @keyf|
+  frame: "frame",          // @keyframs foobar { t|
+};
+
+const SELECTOR_STATES = {
+  "null": "null",
+  id: "id",                // #f|
+  class: "class",          // #foo.b|
+  tag: "tag",              // fo|
+  pseudo: "pseudo",        // foo:|
+  attribute: "attribute",  // foo[b|
+  value: "value",          // foo[bar=b|
+};
+
+const { properties, propertyNames } = getCSSKeywords();
+
+/**
+ * Constructor for the autocompletion object.
+ *
+ * @param options {Object} An options object containing the following options:
+ *        - walker {Object} The object used for query selecting from the current
+ *                 target's DOM.
+ *        - maxEntries {Number} Maximum selectors suggestions to display.
+ */
+function CSSCompleter(options = {}) {
+  this.walker = options.walker;
+  this.maxEntries = options.maxEntries || 15;
+}
+
+CSSCompleter.prototype = {
+
+  /**
+   * Returns a list of suggestions based on the caret position.
+   *
+   * @param source {String} String of the source code.
+   * @param caret {Object} Cursor location with line and ch properties.
+   *
+   * @returns [{object}] A sorted list of objects containing the following
+   *          peroperties:
+   *          - label {String} Full keyword for the suggestion
+   *          - preLabel {String} Already entered part of the label
+   */
+  complete: function(source, caret) {
+    // Getting the context from the caret position.
+    if (!this.resolveState(source, caret)) {
+      // We couldn't resolve the context, we won't be able to complete.
+      return Promise.resolve([]);
+    }
+
+    // Properly suggest based on the state.
+    switch(this.state) {
+      case CSS_STATES.property:
+        return this.completeProperties(this.completing);
+
+      case CSS_STATES.value:
+        return this.completeValues(this.propertyName, this.completing);
+
+      case CSS_STATES.selector:
+        return this.suggestSelectors();
+
+      case CSS_STATES.media:
+      case CSS_STATES.keyframes:
+        if ("media".startsWith(this.completing)) {
+          return Promise.resolve([{
+            label: "media",
+            preLabel: this.completing
+          }]);
+        } else if ("keyframes".startsWith(this.completing)) {
+          return Promise.resolve([{
+            label: "keyrames",
+            preLabel: this.completing
+          }]);
+        }
+    }
+    return Promise.resolve([]);
+  },
+
+  /**
+   * Resolves the state of CSS at the cursor location. This method implements a
+   * custom written CSS state machine. The various switch statements provide the
+   * transition rules for the state. It also finds out various informatino about
+   * the nearby CSS like the property name being completed, the complete
+   * selector, etc.
+   *
+   * @param source {String} String of the source code.
+   * @param caret {Object} Cursor location with line and ch properties.
+   *
+   * @returns CSS_STATE
+   *          One of CSS_STATE enum or null if the state cannot be resolved.
+   */
+  resolveState: function(source, {line, ch}) {
+    // Function to return the last element of an array
+    let peek = arr => arr[arr.length - 1];
+    let tokens = cssTokenizer(source, {loc:true});
+    let tokIndex = tokens.length - 1;
+    if (tokens[tokIndex].loc.end.line < line ||
+       (tokens[tokIndex].loc.end.line === line &&
+        tokens[tokIndex].loc.end.column < ch)) {
+      // If the last token is not an EOF, we didn't tokenize it correctly.
+      // This special case is handled in case we couldn't tokenize, but the last
+      // token that *could be tokenized* was an identifier.
+      return null;
+    }
+    // Since last token is EOF, the cursor token is last - 1
+    tokIndex--;
+
+    // _state can be one of CSS_STATES;
+    let _state = CSS_STATES.null;
+    let cursor = 0;
+    // This will maintain a stack of paired elements like { & }, @m & }, : & ; etc
+    let scopeStack = [];
+    let token = null;
+    let propertyName = null;
+    let selector = "";
+    let selectorBeforeNot = "";
+    let selectorState = SELECTOR_STATES.null;
+    while (cursor <= tokIndex && (token = tokens[cursor++])) {
+      switch (_state) {
+        case CSS_STATES.property:
+          // From CSS_STATES.property, we can either go to CSS_STATES.value state
+          // when we hit the first ':' or CSS_STATES.selector if "}" is reached.
+          switch(token.tokenType) {
+            case ":":
+              scopeStack.push(":");
+              if (tokens[cursor - 2].tokenType != "WHITESPACE")
+                propertyName = tokens[cursor - 2].value;
+              else
+                propertyName = tokens[cursor - 3].value;
+              _state = CSS_STATES.value;
+              break;
+
+            case "}":
+              if (/[{f]/.test(peek(scopeStack))) {
+                let popped = scopeStack.pop();
+                if (popped == "f") {
+                  _state = CSS_STATES.frame;
+                } else {
+                  selector = "";
+                  _state = CSS_STATES.null;
+                }
+              }
+              break;
+          }
+          break;
+
+        case CSS_STATES.value:
+          // From CSS_STATES.value, we can go to one of CSS_STATES.property,
+          // CSS_STATES.frame, CSS_STATES.selector and CSS_STATES.null
+          switch(token.tokenType) {
+            case ";":
+              if (/[:]/.test(peek(scopeStack))) {
+                scopeStack.pop();
+                _state = CSS_STATES.property;
+              }
+              break;
+
+            case "}":
+              if (peek(scopeStack) == ":")
+                scopeStack.pop();
+
+              if (/[{f]/.test(peek(scopeStack))) {
+                let popped = scopeStack.pop();
+                if (popped == "f") {
+                  _state = CSS_STATES.frame;
+                } else {
+                  selector = "";
+                  _state = CSS_STATES.null;
+                }
+              }
+              break;
+          }
+          break;
+
+        case CSS_STATES.selector:
+          // From CSS_STATES.selector, we can only go to CSS_STATES.property when
+          // we hit "{"
+          if (token.tokenType == "{") {
+            scopeStack.push("{");
+            _state = CSS_STATES.property;
+            break;
+          }
+          switch(selectorState) {
+            case SELECTOR_STATES.id:
+            case SELECTOR_STATES.class:
+            case SELECTOR_STATES.tag:
+              switch(token.tokenType) {
+                case "HASH":
+                  selectorState = SELECTOR_STATES.id;
+                  selector += "#" + token.value;
+                  break;
+
+                case "DELIM":
+                  if (token.value == ".") {
+                    selectorState = SELECTOR_STATES.class;
+                    selector += ".";
+                    if (cursor <= tokIndex &&
+                        tokens[cursor].tokenType == "IDENT") {
+                      token = tokens[cursor++];
+                      selector += token.value;
+                    }
+                  } else if (token.value == "#") {
+                    selectorState = SELECTOR_STATES.id;
+                    selector += "#";
+                  } else if (/[>~+]/.test(token.value)) {
+                    selectorState = SELECTOR_STATES.null;
+                    selector += token.value;
+                  } else if (token.value == ",") {
+                    selectorState = SELECTOR_STATES.null;
+                    selector = "";
+                  }
+                  break;
+
+                case ":":
+                  selectorState = SELECTOR_STATES.pseudo;
+                  selector += ":";
+                  if (cursor > tokIndex)
+                    break;
+
+                  token = tokens[cursor++];
+                  switch(token.tokenType) {
+                    case "FUNCTION":
+                      selectorState = SELECTOR_STATES.null;
+                      selectorBeforeNot = selector;
+                      selector = "";
+                      scopeStack.push("(");
+                      break;
+
+                    case "IDENT":
+                      selector += token.value;
+                      break;
+                  }
+                  break;
+
+                case "[":
+                  selectorState = SELECTOR_STATES.attribute;
+                  scopeStack.push("[");
+                  selector += "[";
+                  break;
+
+                case ")":
+                  if (peek(scopeStack) == "(") {
+                    scopeStack.pop();
+                    selector = selectorBeforeNot + "not(" + selector + ")";
+                    selectorState = SELECTOR_STATES.null;
+                  }
+                  break;
+
+                case "WHITESPACE":
+                  selectorState = SELECTOR_STATES.null;
+                  selector && (selector += " ");
+                  break;
+              }
+              break;
+
+            case SELECTOR_STATES.null:
+              // From SELECTOR_STATES.null state, we can go to one of
+              // SELECTOR_STATES.id, SELECTOR_STATES.class or SELECTOR_STATES.tag
+              switch(token.tokenType) {
+                case "HASH":
+                  selectorState = SELECTOR_STATES.id;
+                  selector += "#" + token.value;
+                  break;
+
+                case "IDENT":
+                  selectorState = SELECTOR_STATES.tag;
+                  selector += token.value;
+                  break;
+
+                case "DELIM":
+                  if (token.value == ".") {
+                    selectorState = SELECTOR_STATES.class;
+                    selector += ".";
+                    if (cursor <= tokIndex &&
+                        tokens[cursor].tokenType == "IDENT") {
+                      token = tokens[cursor++];
+                      selector += token.value;
+                    }
+                  } else if (token.value == "#") {
+                    selectorState = SELECTOR_STATES.id;
+                    selector += "#";
+                  } else if (token.value == "*") {
+                    selectorState = SELECTOR_STATES.tag;
+                    selector += "*";
+                  } else if (/[>~+]/.test(token.value)) {
+                    selector += token.value;
+                  } else if (token.value == ",") {
+                    selectorState = SELECTOR_STATES.null;
+                    selector = "";
+                  }
+                  break;
+
+                case ":":
+                  selectorState = SELECTOR_STATES.pseudo;
+                  selector += ":";
+                  if (cursor > tokIndex)
+                    break;
+
+                  token = tokens[cursor++];
+                  switch(token.tokenType) {
+                    case "FUNCTION":
+                      selectorState = SELECTOR_STATES.null;
+                      selectorBeforeNot = selector;
+                      selector = "";
+                      scopeStack.push("(");
+                      break;
+
+                    case "IDENT":
+                      selector += token.value;
+                      break;
+                  }
+                  break;
+
+                case "[":
+                  selectorState = SELECTOR_STATES.attribute;
+                  scopeStack.push("[");
+                  selector += "[";
+                  break;
+
+                case ")":
+                  if (peek(scopeStack) == "(") {
+                    scopeStack.pop();
+                    selector = selectorBeforeNot + "not(" + selector + ")";
+                    selectorState = SELECTOR_STATES.null;
+                  }
+                  break;
+
+                case "WHITESPACE":
+                  selector && (selector += " ");
+                  break;
+              }
+              break;
+
+            case SELECTOR_STATES.pseudo:
+              switch(token.tokenType) {
+                case "DELIM":
+                  if (/[>~+]/.test(token.value)) {
+                    selectorState = SELECTOR_STATES.null;
+                    selector += token.value;
+                  } else if (token.value == ",") {
+                    selectorState = SELECTOR_STATES.null;
+                    selector = "";
+                  }
+                  break;
+
+                case ":":
+                  selectorState = SELECTOR_STATES.pseudo;
+                  selector += ":";
+                  if (cursor > tokIndex)
+                    break;
+
+                  token = tokens[cursor++];
+                  switch(token.tokenType) {
+                    case "FUNCTION":
+                      selectorState = SELECTOR_STATES.null;
+                      selectorBeforeNot = selector;
+                      selector = "";
+                      scopeStack.push("(");
+                      break;
+
+                    case "IDENT":
+                      selector += token.value;
+                      break;
+                  }
+                  break;
+
+                case "[":
+                  selectorState = SELECTOR_STATES.attribute;
+                  scopeStack.push("[");
+                  selector += "[";
+                  break;
+
+                case "WHITESPACE":
+                  selectorState = SELECTOR_STATES.null;
+                  selector && (selector += " ");
+                  break;
+              }
+              break;
+
+            case SELECTOR_STATES.attribute:
+              switch(token.tokenType) {
+                case "DELIM":
+                  if (/[~|^$*]/.test(token.value)) {
+                    selector += token.value;
+                    token = tokens[cursor++];
+                  }
+                  if(token.value == "=") {
+                    selectorState = SELECTOR_STATES.value;
+                    selector += token.value;
+                  }
+                  break;
+
+                case "IDENT":
+                case "STRING":
+                  selector += token.value;
+                  break;
+
+                case "]":
+                  if (peek(scopeStack) == "[")
+                    scopeStack.pop();
+
+                  selectorState = SELECTOR_STATES.null;
+                  selector += "]";
+                  break;
+
+                case "WHITESPACE":
+                  selector && (selector += " ");
+                  break;
+              }
+              break;
+
+            case SELECTOR_STATES.value:
+              switch(token.tokenType) {
+                case "STRING":
+                case "IDENT":
+                  selector += token.value;
+                  break;
+
+                case "]":
+                  if (peek(scopeStack) == "[")
+                    scopeStack.pop();
+
+                  selectorState = SELECTOR_STATES.null;
+                  selector += "]";
+                  break;
+
+                case "WHITESPACE":
+                  selector && (selector += " ");
+                  break;
+              }
+              break;
+          }
+          break;
+
+        case CSS_STATES.null:
+          // From CSS_STATES.null state, we can go to either CSS_STATES.media or
+          // CSS_STATES.selector.
+          switch(token.tokenType) {
+            case "HASH":
+              selectorState = SELECTOR_STATES.id;
+              selector = "#" + token.value;
+              _state = CSS_STATES.selector;
+              break;
+
+            case "IDENT":
+              selectorState = SELECTOR_STATES.tag;
+              selector = token.value;
+              _state = CSS_STATES.selector;
+              break;
+
+            case "DELIM":
+              if (token.value == ".") {
+                selectorState = SELECTOR_STATES.class;
+                selector = ".";
+                _state = CSS_STATES.selector;
+                if (cursor <= tokIndex &&
+                    tokens[cursor].tokenType == "IDENT") {
+                  token = tokens[cursor++];
+                  selector += token.value;
+                }
+              } else if (token.value == "#") {
+                selectorState = SELECTOR_STATES.id;
+                selector = "#";
+                _state = CSS_STATES.selector;
+              } else if (token.value == "*") {
+                selectorState = SELECTOR_STATES.tag;
+                selector = "*";
+                _state = CSS_STATES.selector;
+              }
+              break;
+
+            case ":":
+              _state = CSS_STATES.selector;
+              selectorState = SELECTOR_STATES.pseudo;
+              selector += ":";
+              if (cursor > tokIndex)
+                break;
+
+              token = tokens[cursor++];
+              switch(token.tokenType) {
+                case "FUNCTION":
+                  selectorState = SELECTOR_STATES.null;
+                  selectorBeforeNot = selector;
+                  selector = "";
+                  scopeStack.push("(");
+                  break;
+
+                case "IDENT":
+                  selector += token.value;
+                  break;
+              }
+              break;
+
+            case "[":
+              _state = CSS_STATES.selector;
+              selectorState = SELECTOR_STATES.attribute;
+              scopeStack.push("[");
+              selector += "[";
+              break;
+
+            case "AT-KEYWORD":
+              _state = token.value.startsWith("m") ? CSS_STATES.media
+                                                   : CSS_STATES.keyframes;
+              break;
+
+            case "}":
+              if (peek(scopeStack) == "@m")
+                scopeStack.pop();
+
+              break;
+          }
+          break;
+
+        case CSS_STATES.media:
+          // From CSS_STATES.media, we can only go to CSS_STATES.null state when
+          // we hit the first '{'
+          if (token.tokenType == "{") {
+            scopeStack.push("@m");
+            _state = CSS_STATES.null;
+          }
+          break;
+
+        case CSS_STATES.keyframes:
+          // From CSS_STATES.keyframes, we can only go to CSS_STATES.frame state
+          // when we hit the first '{'
+          if (token.tokenType == "{") {
+            scopeStack.push("@k");
+            _state = CSS_STATES.frame;
+          }
+          break;
+
+        case CSS_STATES.frame:
+          // From CSS_STATES.frame, we can either go to CSS_STATES.property state
+          // when we hit the first '{' or to CSS_STATES.selector when we hit '}'
+          if (token.tokenType == "{") {
+            scopeStack.push("f");
+            _state = CSS_STATES.property;
+          } else if (token.tokenType == "}") {
+            if (peek(scopeStack) == "@k")
+              scopeStack.pop();
+
+            _state = CSS_STATES.null;
+          }
+          break;
+      }
+    }
+    this.state = _state;
+    if (!token)
+      return _state;
+
+    if (token && token.tokenType != "WHITESPACE") {
+      this.completing = ((token.value || token.repr || token.tokenType) + "")
+                          .slice(0, ch - token.loc.start.column)
+                          .replace(/^[.#]$/, "");
+    } else {
+      this.completing = "";
+    }
+    // Special check for !important; case.
+    if (tokens[cursor - 2] && tokens[cursor - 2].value == "!" &&
+        this.completing == "important".slice(0, this.completing.length)) {
+      this.completing = "!" + this.completing;
+    }
+    this.propertyName = _state == CSS_STATES.value ? propertyName : null;
+    selector = selector.slice(0, selector.length + token.loc.end.column - ch);
+    this.selector = _state == CSS_STATES.selector ? selector : null;
+    this.selectorState = _state == CSS_STATES.selector ? selectorState : null;
+    return _state;
+  },
+
+  /**
+   * Queries the DOM Walker actor for suggestions regarding the selector being
+   * completed
+   */
+  suggestSelectors: function () {
+    let walker = this.walker;
+    if (!walker)
+      return Promise.resolve([]);
+
+    let query = this.selector;
+    // Even though the selector matched atleast one node, there is still
+    // possibility of suggestions.
+    switch(this.selectorState) {
+      case SELECTOR_STATES.null:
+        query += "*";
+        break;
+
+      case SELECTOR_STATES.tag:
+        query = query.slice(0, query.length - this.completing.length);
+        break;
+
+      case SELECTOR_STATES.id:
+      case SELECTOR_STATES.class:
+      case SELECTOR_STATES.pseudo:
+        if (/^[.:#]$/.test(this.completing)) {
+          query = query.slice(0, query.length - this.completing.length);
+          this.completing = "";
+        } else {
+          query = query.slice(0, query.length - this.completing.length - 1);
+        }
+        break;
+    }
+
+    if (/[\s+>~]$/.test(query) &&
+        this.selectorState != SELECTOR_STATES.attribute &&
+        this.selectorState != SELECTOR_STATES.value) {
+      query += "*";
+    }
+
+    // Set the values that this request was supposed to suggest to.
+    this._currentQuery = query;
+    return walker.getSuggestionsForQuery(query, this.completing, this.selectorState)
+                 .then(result => this.prepareSelectorResults(result));
+  },
+
+ /**
+  * Prepares the selector suggestions returned by the walker actor.
+  */
+  prepareSelectorResults: function(result) {
+    if (this._currentQuery != result.query)
+      return [];
+
+    result = result.suggestions;
+    let query = this.selector;
+    let completion = [];
+    for (let value of result) {
+      switch(this.selectorState) {
+        case SELECTOR_STATES.id:
+        case SELECTOR_STATES.class:
+        case SELECTOR_STATES.pseudo:
+          if (/^[.:#]$/.test(this.completing)) {
+            value[0] = query.slice(0, query.length - this.completing.length) +
+                       value[0];
+          } else {
+            value[0] = query.slice(0, query.length - this.completing.length - 1) +
+                       value[0];
+          }
+          break;
+
+        case SELECTOR_STATES.tag:
+          value[0] = query.slice(0, query.length - this.completing.length) +
+                     value[0];
+          break;
+
+        case SELECTOR_STATES.null:
+          value[0] = query + value[0];
+          break;
+
+        default:
+         value[0] = query.slice(0, query.length - this.completing.length) +
+                    value[0];
+      }
+      completion.push({
+        label: value[0],
+        preLabel: query,
+        score: value[1]
+      });
+      if (completion.length > this.maxEntries - 1)
+        break;
+    }
+    return completion;
+  },
+
+  /**
+   * Returns CSS property name suggestions based on the input.
+   *
+   * @param startProp {String} Initial part of the property being completed.
+   */
+  completeProperties: function(startProp) {
+    let finalList = [];
+    let length = propertyNames.length;
+    let i = 0, count = 0;
+    for (; i < length && count < this.maxEntries; i++) {
+      if (propertyNames[i].startsWith(startProp)) {
+        count++;
+        finalList.push({
+          preLabel: startProp,
+          label: propertyNames[i]
+        });
+      } else if (propertyNames[i] > startProp) {
+        // We have crossed all possible matches alphabetically.
+        break;
+      }
+    }
+    return Promise.resolve(finalList);
+  },
+
+  /**
+   * Returns CSS value suggestions based on the corresponding property.
+   *
+   * @param propName {String} The property to which the value being completed
+   *        belongs.
+   * @param startValue {String} Initial part of the value being completed.
+   */
+  completeValues: function(propName, startValue) {
+    let finalList = [];
+    let list = ["!important;", ...(properties[propName] || [])];
+    let length = list.length;
+    let i = 0, count = 0;
+    for (; i < length && count < this.maxEntries; i++) {
+      if (list[i].startsWith(startValue)) {
+        count++;
+        finalList.push({
+          preLabel: startValue,
+          label: list[i]
+        });
+      } else if (list[i] > startValue) {
+        // We have crossed all possible matches alphabetically.
+        break;
+      }
+    }
+    return Promise.resolve(finalList);
+  },
+}
+
+/**
+ * Returns a list of all property names and a map of property name vs possible
+ * CSS values provided by the Gecko engine.
+ *
+ * @return {Object} An object with following properties:
+ *         - propertyNames {Array} Array of string containing all the possible
+ *                         CSS property names.
+ *         - properties {Object|Map} A map where key is the property name and
+ *                      value is an array of string containing all the possible
+ *                      CSS values the property can have.
+ */
+function getCSSKeywords() {
+  let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+                   .getService(Ci.inIDOMUtils);
+  let props = {};
+  let propNames = domUtils.getCSSPropertyNames(domUtils.INCLUDE_ALIASES);
+  propNames.forEach(prop => {
+    props[prop] = domUtils.getCSSValuesForProperty(prop).sort();
+  });
+  return {
+    properties: props,
+    propertyNames: propNames.sort()
+  };
+}
+
+module.exports = CSSCompleter;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/css-tokenizer.js
@@ -0,0 +1,717 @@
+/**
+ * This file is taken from the below mentioned url and is under CC0 license.
+ * https://github.com/tabatkins/css-parser/blob/master/tokenizer.js
+ * Please retain this comment while updating this file from upstream.
+ */
+
+(function (root, factory) {
+    // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
+    // Rhino, and plain browser loading.
+    if (typeof define === 'function' && define.amd) {
+        define(['exports'], factory);
+    } else if (typeof exports !== 'undefined') {
+        factory(exports);
+    } else {
+        factory(root);
+    }
+}(this, function (exports) {
+
+var between = function (num, first, last) { return num >= first && num <= last; }
+function digit(code) { return between(code, 0x30,0x39); }
+function hexdigit(code) { return digit(code) || between(code, 0x41,0x46) || between(code, 0x61,0x66); }
+function uppercaseletter(code) { return between(code, 0x41,0x5a); }
+function lowercaseletter(code) { return between(code, 0x61,0x7a); }
+function letter(code) { return uppercaseletter(code) || lowercaseletter(code); }
+function nonascii(code) { return code >= 0xa0; }
+function namestartchar(code) { return letter(code) || nonascii(code) || code == 0x5f; }
+function namechar(code) { return namestartchar(code) || digit(code) || code == 0x2d; }
+function nonprintable(code) { return between(code, 0,8) || between(code, 0xe,0x1f) || between(code, 0x7f,0x9f); }
+function newline(code) { return code == 0xa || code == 0xc; }
+function whitespace(code) { return newline(code) || code == 9 || code == 0x20; }
+function badescape(code) { return newline(code) || isNaN(code); }
+
+// Note: I'm not yet acting smart enough to actually handle astral characters.
+var maximumallowedcodepoint = 0x10ffff;
+
+function tokenize(str, options) {
+  if(options == undefined) options = {transformFunctionWhitespace:false, scientificNotation:false};
+  var i = -1;
+  var tokens = [];
+  var state = "data";
+  var code;
+  var currtoken;
+
+  // Line number information.
+  var line = 0;
+  var column = 0;
+  // The only use of lastLineLength is in reconsume().
+  var lastLineLength = 0;
+  var incrLineno = function() {
+    line += 1;
+    lastLineLength = column;
+    column = 0;
+  };
+  var locStart = {line:line, column:column};
+
+  var next = function(num) { if(num === undefined) num = 1; return str.charCodeAt(i+num); };
+  var consume = function(num) {
+    if(num === undefined)
+      num = 1;
+    i += num;
+    code = str.charCodeAt(i);
+    if (newline(code)) incrLineno();
+    else column += num;
+    //console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
+    return true;
+  };
+  var reconsume = function() {
+    i -= 1;
+    if (newline(code)) {
+      line -= 1;
+      column = lastLineLength;
+    } else {
+      column -= 1;
+    }
+    locStart.line = line;
+    locStart.column = column;
+    return true;
+  };
+  var eof = function() { return i >= str.length; };
+  var donothing = function() {};
+  var emit = function(token) {
+    if(token) {
+      token.finish();
+    } else {
+      token = currtoken.finish();
+    }
+    if (options.loc === true) {
+      token.loc = {};
+      token.loc.start = {line:locStart.line, column:locStart.column};
+      locStart = {line: line, column: column};
+      token.loc.end = locStart;
+    }
+    tokens.push(token);
+    //console.log('Emitting ' + token);
+    currtoken = undefined;
+    return true;
+  };
+  var create = function(token) { currtoken = token; return true; };
+  var parseerror = function() { console.log("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + " in state " + state + ".");return true; };
+  var switchto = function(newstate) {
+    state = newstate;
+    //console.log('Switching to ' + state);
+    return true;
+  };
+  var consumeEscape = function() {
+    // Assume the the current character is the \
+    consume();
+    if(hexdigit(code)) {
+      // Consume 1-6 hex digits
+      var digits = [];
+      for(var total = 0; total < 6; total++) {
+        if(hexdigit(code)) {
+          digits.push(code);
+          consume();
+        } else { break; }
+      }
+      var value = parseInt(digits.map(String.fromCharCode).join(''), 16);
+      if( value > maximumallowedcodepoint ) value = 0xfffd;
+      // If the current char is whitespace, cool, we'll just eat it.
+      // Otherwise, put it back.
+      if(!whitespace(code)) reconsume();
+      return value;
+    } else {
+      return code;
+    }
+  };
+
+  for(;;) {
+    if(i > str.length*2) return "I'm infinite-looping!";
+    consume();
+    switch(state) {
+    case "data":
+      if(whitespace(code)) {
+        emit(new WhitespaceToken);
+        while(whitespace(next())) consume();
+      }
+      else if(code == 0x22) switchto("double-quote-string");
+      else if(code == 0x23) switchto("hash");
+      else if(code == 0x27) switchto("single-quote-string");
+      else if(code == 0x28) emit(new OpenParenToken);
+      else if(code == 0x29) emit(new CloseParenToken);
+      else if(code == 0x2b) {
+        if(digit(next()) || (next() == 0x2e && digit(next(2)))) switchto("number") && reconsume();
+        else emit(new DelimToken(code));
+      }
+      else if(code == 0x2d) {
+        if(next(1) == 0x2d && next(2) == 0x3e) consume(2) && emit(new CDCToken);
+        else if(digit(next()) || (next(1) == 0x2e && digit(next(2)))) switchto("number") && reconsume();
+        else if(namestartchar(next())) switchto("identifier") && reconsume();
+        else emit(new DelimToken(code));
+      }
+      else if(code == 0x2e) {
+        if(digit(next())) switchto("number") && reconsume();
+        else emit(new DelimToken(code));
+      }
+      else if(code == 0x2f) {
+        if(next() == 0x2a) switchto("comment");
+        else emit(new DelimToken(code));
+      }
+      else if(code == 0x3a) emit(new ColonToken);
+      else if(code == 0x3b) emit(new SemicolonToken);
+      else if(code == 0x3c) {
+        if(next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) consume(3) && emit(new CDOToken);
+        else emit(new DelimToken(code));
+      }
+      else if(code == 0x40) switchto("at-keyword");
+      else if(code == 0x5b) emit(new OpenSquareToken);
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit(new DelimToken(code));
+        else switchto("identifier") && reconsume();
+      }
+      else if(code == 0x5d) emit(new CloseSquareToken);
+      else if(code == 0x7b) emit(new OpenCurlyToken);
+      else if(code == 0x7d) emit(new CloseCurlyToken);
+      else if(digit(code)) switchto("number") && reconsume();
+      else if(code == 0x55 || code == 0x75) {
+        if(next(1) == 0x2b && hexdigit(next(2))) consume() && switchto("unicode-range");
+        else if((next(1) == 0x52 || next(1) == 0x72) && (next(2) == 0x4c || next(2) == 0x6c) && (next(3) == 0x28)) consume(3) && switchto("url");
+        else switchto("identifier") && reconsume();
+      }
+      else if(namestartchar(code)) switchto("identifier") && reconsume();
+      else if(eof()) { emit(new EOFToken); return tokens; }
+      else emit(new DelimToken(code));
+      break;
+
+    case "double-quote-string":
+      if(currtoken == undefined) create(new StringToken);
+
+      if(code == 0x22) emit() && switchto("data");
+      else if(eof()) parseerror() && emit() && switchto("data");
+      else if(newline(code)) parseerror() && emit(new BadStringToken) && switchto("data") && reconsume();
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit(new BadStringToken) && switchto("data");
+        else if(newline(next())) consume();
+        else currtoken.append(consumeEscape());
+      }
+      else currtoken.append(code);
+      break;
+
+    case "single-quote-string":
+      if(currtoken == undefined) create(new StringToken);
+
+      if(code == 0x27) emit() && switchto("data");
+      else if(eof()) parseerror() && emit() && switchto("data");
+      else if(newline(code)) parseerror() && emit(new BadStringToken) && switchto("data") && reconsume();
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit(new BadStringToken) && switchto("data");
+        else if(newline(next())) consume();
+        else currtoken.append(consumeEscape());
+      }
+      else currtoken.append(code);
+      break;
+
+    case "hash":
+      if(namechar(code)) create(new HashToken(code)) && switchto("hash-rest");
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit(new DelimToken(0x23)) && switchto("data") && reconsume();
+        else create(new HashToken(consumeEscape())) && switchto('hash-rest');
+      }
+      else emit(new DelimToken(0x23)) && switchto('data') && reconsume();
+      break;
+
+    case "hash-rest":
+      if(namechar(code)) currtoken.append(code);
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit(new DelimToken(0x23)) && switchto("data") && reconsume();
+        else currtoken.append(consumeEscape());
+      }
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "comment":
+      if(code == 0x2a) {
+        if(next() == 0x2f) consume() && switchto('data');
+        else donothing();
+      }
+      else if(eof()) parseerror() && switchto('data') && reconsume();
+      else donothing();
+      break;
+
+    case "at-keyword":
+      if(code == 0x2d) {
+        if(namestartchar(next())) consume() && create(new AtKeywordToken([0x40,code])) && switchto('at-keyword-rest');
+        else emit(new DelimToken(0x40)) && switchto('data') && reconsume();
+      }
+      else if(namestartchar(code)) create(new AtKeywordToken(code)) && switchto('at-keyword-rest');
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit(new DelimToken(0x23)) && switchto("data") && reconsume();
+        else create(new AtKeywordToken(consumeEscape())) && switchto('at-keyword-rest');
+      }
+      else emit(new DelimToken(0x40)) && switchto('data') && reconsume();
+      break;
+
+    case "at-keyword-rest":
+      if(namechar(code)) currtoken.append(code);
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit() && switchto("data") && reconsume();
+        else currtoken.append(consumeEscape());
+      }
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "identifier":
+      if(code == 0x2d) {
+        if(namestartchar(next())) create(new IdentifierToken(code)) && switchto('identifier-rest');
+        else switchto('data') && reconsume();
+      }
+      else if(namestartchar(code)) create(new IdentifierToken(code)) && switchto('identifier-rest');
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && switchto("data") && reconsume();
+        else create(new IdentifierToken(consumeEscape())) && switchto('identifier-rest');
+      }
+      else switchto('data') && reconsume();
+      break;
+
+    case "identifier-rest":
+      if(namechar(code)) currtoken.append(code);
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit() && switchto("data") && reconsume();
+        else currtoken.append(consumeEscape());
+      }
+      else if(code == 0x28) emit(new FunctionToken(currtoken)) && switchto('data');
+      else if(whitespace(code) && options.transformFunctionWhitespace) switchto('transform-function-whitespace');
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "transform-function-whitespace":
+      if(whitespace(code)) donothing();
+      else if(code == 0x28) emit(new FunctionToken(currtoken)) && switchto('data');
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "number":
+      create(new NumberToken());
+
+      if(code == 0x2d) {
+        if(digit(next())) consume() && currtoken.append([0x2d,code]) && switchto('number-rest');
+        else if(next(1) == 0x2e && digit(next(2))) consume(2) && currtoken.append([0x2d,0x2e,code]) && switchto('number-fraction');
+        else switchto('data') && reconsume();
+      }
+      else if(code == 0x2b) {
+        if(digit(next())) consume() && currtoken.append([0x2b,code]) && switchto('number-rest');
+        else if(next(1) == 0x2e && digit(next(2))) consume(2) && currtoken.append([0x2b,0x2e,code]) && switchto('number-fraction');
+        else switchto('data') && reconsume();
+      }
+      else if(digit(code)) currtoken.append(code) && switchto('number-rest');
+      else if(code == 0x2e) {
+        if(digit(next())) consume() && currtoken.append([0x2e,code]) && switchto('number-fraction');
+        else switchto('data') && reconsume();
+      }
+      else switchto('data') && reconsume();
+      break;
+
+    case "number-rest":
+      if(digit(code)) currtoken.append(code);
+      else if(code == 0x2e) {
+        if(digit(next())) consume() && currtoken.append([0x2e,code]) && switchto('number-fraction');
+        else emit() && switchto('data') && reconsume();
+      }
+      else if(code == 0x25) emit(new PercentageToken(currtoken)) && switchto('data') && reconsume();
+      else if(code == 0x45 || code == 0x65) {
+        if(!options.scientificNotation) create(new DimensionToken(currtoken,code)) && switchto('dimension');
+        else if(digit(next())) consume() && currtoken.append([0x25,code]) && switchto('sci-notation');
+        else if((next(1) == 0x2b || next(1) == 0x2d) && digit(next(2))) currtoken.append([0x25,next(1),next(2)]) && consume(2) && switchto('sci-notation');
+        else create(new DimensionToken(currtoken,code)) && switchto('dimension');
+      }
+      else if(code == 0x2d) {
+        if(namestartchar(next())) consume() && create(new DimensionToken(currtoken,[0x2d,code])) && switchto('dimension');
+        else if(next(1) == 0x5c && badescape(next(2))) parseerror() && emit() && switchto('data') && reconsume();
+        else if(next(1) == 0x5c) consume() && create(new DimensionToken(currtoken, [0x2d,consumeEscape()])) && switchto('dimension');
+        else emit() && switchto('data') && reconsume();
+      }
+      else if(namestartchar(code)) create(new DimensionToken(currtoken, code)) && switchto('dimension');
+      else if(code == 0x5c) {
+        if(badescape(next)) emit() && switchto('data') && reconsume();
+        else create(new DimensionToken(currtoken,consumeEscape)) && switchto('dimension');
+      }
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "number-fraction":
+      currtoken.type = "number";
+
+      if(digit(code)) currtoken.append(code);
+      else if(code == 0x2e) emit() && switchto('data') && reconsume();
+      else if(code == 0x25) emit(new PercentageToken(currtoken)) && switchto('data') && reconsume();
+      else if(code == 0x45 || code == 0x65) {
+        if(!options.scientificNotation) create(new DimensionToken(currtoken,code)) && switchto('dimension');
+        else if(digit(next())) consume() && currtoken.append([0x25,code]) && switchto('sci-notation');
+        else if((next(1) == 0x2b || next(1) == 0x2d) && digit(next(2))) currtoken.append([0x25,next(1),next(2)]) && consume(2) && switchto('sci-notation');
+        else create(new DimensionToken(currtoken,code)) && switchto('dimension');
+      }
+      else if(code == 0x2d) {
+        if(namestartchar(next())) consume() && create(new DimensionToken(currtoken,[0x2d,code])) && switchto('dimension');
+        else if(next(1) == 0x5c && badescape(next(2))) parseerror() && emit() && switchto('data') && reconsume();
+        else if(next(1) == 0x5c) consume() && create(new DimensionToken(currtoken, [0x2d,consumeEscape()])) && switchto('dimension');
+        else emit() && switchto('data') && reconsume();
+      }
+      else if(namestartchar(code)) create(new DimensionToken(currtoken, code)) && switchto('dimension');
+      else if(code == 0x5c) {
+        if(badescape(next)) emit() && switchto('data') && reconsume();
+        else create(new DimensionToken(currtoken,consumeEscape)) && switchto('dimension');
+      }
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "dimension":
+      if(namechar(code)) currtoken.append(code);
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && emit() && switchto('data') && reconsume();
+        else currtoken.append(consumeEscape());
+      }
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "sci-notation":
+      if(digit(code)) currtoken.append(code);
+      else emit() && switchto('data') && reconsume();
+      break;
+
+    case "url":
+      if(code == 0x22) switchto('url-double-quote');
+      else if(code == 0x27) switchto('url-single-quote');
+      else if(code == 0x29) emit(new URLToken) && switchto('data');
+      else if(whitespace(code)) donothing();
+      else switchto('url-unquoted') && reconsume();
+      break;
+
+    case "url-double-quote":
+      if(currtoken == undefined) create(new URLToken);
+
+      if(code == 0x22) switchto('url-end');
+      else if(newline(code)) parseerror() && switchto('bad-url');
+      else if(code == 0x5c) {
+        if(newline(next())) consume();
+        else if(badescape(next())) parseerror() && emit(new BadURLToken) && switchto('data') && reconsume();
+        else currtoken.append(consumeEscape());
+      }
+      else currtoken.append(code);
+      break;
+
+    case "url-single-quote":
+      if(currtoken == undefined) create(new URLToken);
+
+      if(code == 0x27) switchto('url-end');
+      else if(newline(code)) parseerror() && switchto('bad-url');
+      else if(code == 0x5c) {
+        if(newline(next())) consume();
+        else if(badescape(next())) parseerror() && emit(new BadURLToken) && switchto('data') && reconsume();
+        else currtoken.append(consumeEscape());
+      }
+      else currtoken.append(code);
+      break;
+
+    case "url-end":
+      if(whitespace(code)) donothing();
+      else if(code == 0x29) emit() && switchto('data');
+      else parseerror() && switchto('bad-url') && reconsume();
+      break;
+
+    case "url-unquoted":
+      if(currtoken == undefined) create(new URLToken);
+
+      if(whitespace(code)) switchto('url-end');
+      else if(code == 0x29) emit() && switchto('data');
+      else if(code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) parseerror() && switchto('bad-url');
+      else if(code == 0x5c) {
+        if(badescape(next())) parseerror() && switchto('bad-url');
+        else currtoken.append(consumeEscape());
+      }
+      else currtoken.append(code);
+      break;
+
+    case "bad-url":
+      if(code == 0x29) emit(new BadURLToken) && switchto('data');
+      else if(code == 0x5c) {
+        if(badescape(next())) donothing();
+        else consumeEscape()
+      }
+      else donothing();
+      break;
+
+    case "unicode-range":
+      // We already know that the current code is a hexdigit.
+
+      var start = [code], end = [code];
+
+      for(var total = 1; total < 6; total++) {
+        if(hexdigit(next())) {
+          consume();
+          start.push(code);
+          end.push(code);
+        }
+        else break;
+      }
+
+      if(next() == 0x3f) {
+        for(;total < 6; total++) {
+          if(next() == 0x3f) {
+            consume();
+            start.push("0".charCodeAt(0));
+            end.push("f".charCodeAt(0));
+          }
+          else break;
+        }
+        emit(new UnicodeRangeToken(start,end)) && switchto('data');
+      }
+      else if(next(1) == 0x2d && hexdigit(next(2))) {
+        consume();
+        consume();
+        end = [code];
+        for(var total = 1; total < 6; total++) {
+          if(hexdigit(next())) {
+            consume();
+            end.push(code);
+          }
+          else break;
+        }
+        emit(new UnicodeRangeToken(start,end)) && switchto('data');
+      }
+      else emit(new UnicodeRangeToken(start)) && switchto('data');
+      break;
+
+    default:
+      console.log("Unknown state '" + state + "'");
+    }
+  }
+}
+
+function stringFromCodeArray(arr) {
+  return String.fromCharCode.apply(null,arr.filter(function(e){return e;}));
+}
+
+function CSSParserToken(options) { return this; }
+CSSParserToken.prototype.finish = function() { return this; }
+CSSParserToken.prototype.toString = function() { return this.tokenType; }
+CSSParserToken.prototype.toJSON = function() { return this.toString(); }
+
+function BadStringToken() { return this; }
+BadStringToken.prototype = new CSSParserToken;
+BadStringToken.prototype.tokenType = "BADSTRING";
+
+function BadURLToken() { return this; }
+BadURLToken.prototype = new CSSParserToken;
+BadURLToken.prototype.tokenType = "BADURL";
+
+function WhitespaceToken() { return this; }
+WhitespaceToken.prototype = new CSSParserToken;
+WhitespaceToken.prototype.tokenType = "WHITESPACE";
+WhitespaceToken.prototype.toString = function() { return "WS"; }
+
+function CDOToken() { return this; }
+CDOToken.prototype = new CSSParserToken;
+CDOToken.prototype.tokenType = "CDO";
+
+function CDCToken() { return this; }
+CDCToken.prototype = new CSSParserToken;
+CDCToken.prototype.tokenType = "CDC";
+
+function ColonToken() { return this; }
+ColonToken.prototype = new CSSParserToken;
+ColonToken.prototype.tokenType = ":";
+
+function SemicolonToken() { return this; }
+SemicolonToken.prototype = new CSSParserToken;
+SemicolonToken.prototype.tokenType = ";";
+
+function OpenCurlyToken() { return this; }
+OpenCurlyToken.prototype = new CSSParserToken;
+OpenCurlyToken.prototype.tokenType = "{";
+
+function CloseCurlyToken() { return this; }
+CloseCurlyToken.prototype = new CSSParserToken;
+CloseCurlyToken.prototype.tokenType = "}";
+
+function OpenSquareToken() { return this; }
+OpenSquareToken.prototype = new CSSParserToken;
+OpenSquareToken.prototype.tokenType = "[";
+
+function CloseSquareToken() { return this; }
+CloseSquareToken.prototype = new CSSParserToken;
+CloseSquareToken.prototype.tokenType = "]";
+
+function OpenParenToken() { return this; }
+OpenParenToken.prototype = new CSSParserToken;
+OpenParenToken.prototype.tokenType = "(";
+
+function CloseParenToken() { return this; }
+CloseParenToken.prototype = new CSSParserToken;
+CloseParenToken.prototype.tokenType = ")";
+
+function EOFToken() { return this; }
+EOFToken.prototype = new CSSParserToken;
+EOFToken.prototype.tokenType = "EOF";
+
+function DelimToken(code) {
+  this.value = String.fromCharCode(code);
+  return this;
+}
+DelimToken.prototype = new CSSParserToken;
+DelimToken.prototype.tokenType = "DELIM";
+DelimToken.prototype.toString = function() { return "DELIM("+this.value+")"; }
+
+function StringValuedToken() { return this; }
+StringValuedToken.prototype = new CSSParserToken;
+StringValuedToken.prototype.append = function(val) {
+  if(val instanceof Array) {
+    for(var i = 0; i < val.length; i++) {
+      this.value.push(val[i]);
+    }
+  } else {
+    this.value.push(val);
+  }
+  return true;
+}
+StringValuedToken.prototype.finish = function() {
+  this.value = stringFromCodeArray(this.value);
+  return this;
+}
+
+function IdentifierToken(val) {
+  this.value = [];
+  this.append(val);
+}
+IdentifierToken.prototype = new StringValuedToken;
+IdentifierToken.prototype.tokenType = "IDENT";
+IdentifierToken.prototype.toString = function() { return "IDENT("+this.value+")"; }
+
+function FunctionToken(val) {
+  // These are always constructed by passing an IdentifierToken
+  this.value = val.finish().value;
+}
+FunctionToken.prototype = new CSSParserToken;
+FunctionToken.prototype.tokenType = "FUNCTION";
+FunctionToken.prototype.toString = function() { return "FUNCTION("+this.value+")"; }
+
+function AtKeywordToken(val) {
+  this.value = [];
+  this.append(val);
+}
+AtKeywordToken.prototype = new StringValuedToken;
+AtKeywordToken.prototype.tokenType = "AT-KEYWORD";
+AtKeywordToken.prototype.toString = function() { return "AT("+this.value+")"; }
+
+function HashToken(val) {
+  this.value = [];
+  this.append(val);
+}
+HashToken.prototype = new StringValuedToken;
+HashToken.prototype.tokenType = "HASH";
+HashToken.prototype.toString = function() { return "HASH("+this.value+")"; }
+
+function StringToken(val) {
+  this.value = [];
+  this.append(val);
+}
+StringToken.prototype = new StringValuedToken;
+StringToken.prototype.tokenType = "STRING";
+StringToken.prototype.toString = function() { return "\""+this.value+"\""; }
+
+function URLToken(val) {
+  this.value = [];
+  this.append(val);
+}
+URLToken.prototype = new StringValuedToken;
+URLToken.prototype.tokenType = "URL";
+URLToken.prototype.toString = function() { return "URL("+this.value+")"; }
+
+function NumberToken(val) {
+  this.value = [];
+  this.append(val);
+  this.type = "integer";
+}
+NumberToken.prototype = new StringValuedToken;
+NumberToken.prototype.tokenType = "NUMBER";
+NumberToken.prototype.toString = function() {
+  if(this.type == "integer")
+    return "INT("+this.value+")";
+  return "NUMBER("+this.value+")";
+}
+NumberToken.prototype.finish = function() {
+  this.repr = stringFromCodeArray(this.value);
+  this.value = this.repr * 1;
+  if(Math.abs(this.value) % 1 != 0) this.type = "number";
+  return this;
+}
+
+function PercentageToken(val) {
+  // These are always created by passing a NumberToken as val
+  val.finish();
+  this.value = val.value;
+  this.repr = val.repr;
+}
+PercentageToken.prototype = new CSSParserToken;
+PercentageToken.prototype.tokenType = "PERCENTAGE";
+PercentageToken.prototype.toString = function() { return "PERCENTAGE("+this.value+")"; }
+
+function DimensionToken(val,unit) {
+  // These are always created by passing a NumberToken as the val
+  val.finish();
+  this.num = val.value;
+  this.unit = [];
+  this.repr = val.repr;
+  this.append(unit);
+}
+DimensionToken.prototype = new CSSParserToken;
+DimensionToken.prototype.tokenType = "DIMENSION";
+DimensionToken.prototype.toString = function() { return "DIM("+this.num+","+this.unit+")"; }
+DimensionToken.prototype.append = function(val) {
+  if(val instanceof Array) {
+    for(var i = 0; i < val.length; i++) {
+      this.unit.push(val[i]);
+    }
+  } else {
+    this.unit.push(val);
+  }
+  return true;
+}
+DimensionToken.prototype.finish = function() {
+  this.unit = stringFromCodeArray(this.unit);
+  this.repr += this.unit;
+  return this;
+}
+
+function UnicodeRangeToken(start,end) {
+  // start and end are array of char codes, completely finished
+  start = parseInt(stringFromCodeArray(start),16);
+  if(end === undefined) end = start + 1;
+  else end = parseInt(stringFromCodeArray(end),16);
+
+  if(start > maximumallowedcodepoint) end = start;
+  if(end < start) end = start;
+  if(end > maximumallowedcodepoint) end = maximumallowedcodepoint;
+
+  this.start = start;
+  this.end = end;
+  return this;
+}
+UnicodeRangeToken.prototype = new CSSParserToken;
+UnicodeRangeToken.prototype.tokenType = "UNICODE-RANGE";
+UnicodeRangeToken.prototype.toString = function() {
+  if(this.start+1 == this.end)
+    return "UNICODE-RANGE("+this.start.toString(16).toUpperCase()+")";
+  if(this.start < this.end)
+    return "UNICODE-RANGE("+this.start.toString(16).toUpperCase()+"-"+this.end.toString(16).toUpperCase()+")";
+  return "UNICODE-RANGE()";
+}
+UnicodeRangeToken.prototype.contains = function(code) {
+  return code >= this.start && code < this.end;
+}
+
+
+// Exportation.
+// TODO: also export the various tokens objects?
+module.exports = tokenize;
+
+}));
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -765,17 +765,17 @@ Editor.prototype = {
    * }
    *
    * editor.extend({ hello: hello });
    * editor.hello('Mozilla');
    */
   extend: function (funcs) {
     Object.keys(funcs).forEach((name) => {
       let cm  = editors.get(this);
-      let ctx = { ed: this, cm: cm };
+      let ctx = { ed: this, cm: cm, Editor: Editor};
 
       if (name === "initialize") {
         funcs[name](ctx);
         return;
       }
 
       this[name] = funcs[name].bind(null, ctx);
     });
--- a/browser/devtools/sourceeditor/moz.build
+++ b/browser/devtools/sourceeditor/moz.build
@@ -4,12 +4,15 @@
 # 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/.
 
 TEST_DIRS += ['test']
 
 JS_MODULES_PATH = 'modules/devtools/sourceeditor'
 
 EXTRA_JS_MODULES += [
+    'autocomplete.js',
+    'css-autocompleter.js',
+    'css-tokenizer.js',
     'debugger.js',
     'editor.js'
 ]
 
--- a/browser/devtools/sourceeditor/test/browser.ini
+++ b/browser/devtools/sourceeditor/test/browser.ini
@@ -4,20 +4,25 @@ support-files =
   cm_driver.js
   cm_mode_javascript_test.js
   cm_mode_test.css
   cm_mode_test.js
   cm_test.js
   cm_emacs_test.js
   cm_vim_test.js
   codemirror.html
+  css_statemachine_testcases.css
+  css_statemachine_tests.json
+  css_autocompletion_tests.json
   vimemacs.html
   head.js
 
 [browser_editor_basic.js]
 [browser_editor_cursor.js]
 [browser_editor_history.js]
 [browser_editor_markers.js]
 [browser_editor_movelines.js]
 [browser_editor_addons.js]
 [browser_codemirror.js]
+[browser_css_autocompletion.js]
+[browser_css_statemachine.js]
 [browser_vimemacs.js]
 [browser_sourceeditor_initialization.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_css_autocompletion.js
@@ -0,0 +1,146 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const cssAutoCompleter  = require("devtools/sourceeditor/css-autocompleter");
+const {InspectorFront} = require("devtools/server/actors/inspector");
+const { Cc, Ci } = require("chrome");
+
+const CSS_URI = "http://mochi.test:8888/browser/browser/devtools/sourceeditor" +
+                "/test/css_statemachine_testcases.css";
+const TESTS_URI = "http://mochi.test:8888/browser/browser/devtools/sourceeditor" +
+                  "/test/css_autocompletion_tests.json";
+
+const source = read(CSS_URI);
+const tests = eval(read(TESTS_URI));
+
+const TEST_URI = "data:text/html;charset=UTF-8," + encodeURIComponent(
+  ["<!DOCTYPE html>",
+   "<html>",
+   " <head>",
+   "  <title>CSS State machine tests.</title>",
+   "  <style type='text/css'>",
+   "#progress {",
+   "  width: 500px; height: 30px;",
+   "  border: 1px solid black;",
+   "  position: relative",
+   "}",
+   "#progress div {",
+   "  width: 0%; height: 100%;",
+   "  background: green;",
+   "  position: absolute;",
+   "  z-index: -1; top: 0",
+   "}",
+   "#progress.failed div {",
+   "  background: red !important;",
+   "}",
+   "#progress.failed:after {",
+   "  content: 'Some tests failed';",
+   "  color: white",
+   "}",
+   "#progress:before {",
+   "  content: 'Running test ' attr(data-progress) ' of " + tests.length + "';",
+   "  color: white;",
+   "  text-shadow: 0 0 2px darkgreen;",
+   "}",
+   "  </style>",
+   " </head>",
+   " <body>",
+   "  <h2>State machine tests for CSS autocompleter.</h2><br>",
+   "  <div id='progress' data-progress='0'>",
+   "   <div></div>",
+   "  </div>",
+   "  <div id='devtools-menu' class='devtools-toolbarbutton'></div>",
+   "  <div id='devtools-toolbarbutton' class='devtools-menulist'></div>",
+   "  <div id='devtools-anotherone'></div>",
+   "  <div id='devtools-yetagain'></div>",
+   "  <div id='devtools-itjustgoeson'></div>",
+   "  <div id='devtools-okstopitnow'></div>",
+   "  <div class='hidden-labels-box devtools-toolbarbutton devtools-menulist'></div>",
+   "  <div class='devtools-menulist'></div>",
+   "  <div class='devtools-menulist'></div>",
+   "  <tabs class='devtools-toolbarbutton'><tab></tab><tab></tab><tab></tab></tabs><tabs></tabs>",
+   "  <button class='category-name visible'></button>",
+   "  <div class='devtools-toolbarbutton' label='true'>",
+   "   <hbox class='toolbarbutton-menubutton-button'></hbox></div>",
+   " </body>",
+   " </html>"
+  ].join("\n"));
+
+let doc = null;
+let index = 0;
+let completer = null;
+let progress;
+let progressDiv;
+let inspector;
+
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    runTests();
+  }, true);
+  content.location = TEST_URI;
+}
+
+function runTests() {
+  progress = doc.getElementById("progress");
+  progressDiv = doc.querySelector("#progress > div");
+  let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+  target.makeRemote().then(() => {
+    inspector = InspectorFront(target.client, target.form);
+    inspector.getWalker().then(walker => {
+      completer = new cssAutoCompleter({walker: walker});
+      checkStateAndMoveOn();
+    });
+  });
+}
+
+function checkStateAndMoveOn() {
+  if (index == tests.length) {
+    finishUp();
+    return;
+  }
+
+  let test = tests[index];
+  progress.dataset.progress = ++index;
+  progressDiv.style.width = 100*index/tests.length + "%";
+  completer.complete(limit(source, test[0]),
+                     {line: test[0][0], ch: test[0][1]}).then(suggestions => {
+    checkState(test[1], suggestions);
+  }).then(checkStateAndMoveOn);
+}
+
+function checkState(expected, actual) {
+  if (expected.length != actual.length) {
+    ok(false, "Number of suggestions did not match up for state " + index +
+              ". Expected: " + expected.length + ", Actual: " + actual.length);
+    progress.classList.add("failed");
+    return;
+  }
+
+  for (let i = 0; i < actual.length; i++) {
+    if (expected[i] != actual[i].label) {
+      ok (false, "Suggestion " + i + " of state " + index + " did not match up" +
+                 ". Expected: " + expected[i] + ". Actual: " + actual[i].label);
+      return;
+    }
+  }
+  ok(true, "Test " + index + " passed. ");
+}
+
+function finishUp() {
+  completer.walker.release().then(() => {
+    inspector.destroy();
+    inspector = null;
+    completer = null;
+  });
+  progress = null;
+  progressDiv = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_css_statemachine.js
@@ -0,0 +1,113 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const cssAutoCompleter  = require("devtools/sourceeditor/css-autocompleter");
+const { Cc, Ci } = require("chrome");
+
+const CSS_URI = "http://mochi.test:8888/browser/browser/devtools/sourceeditor" +
+                "/test/css_statemachine_testcases.css";
+const TESTS_URI = "http://mochi.test:8888/browser/browser/devtools/sourceeditor" +
+                  "/test/css_statemachine_tests.json";
+
+const source = read(CSS_URI);
+const tests = eval(read(TESTS_URI));
+
+const TEST_URI = "data:text/html;charset=UTF-8," + encodeURIComponent(
+  ["<!DOCTYPE html>",
+   "<html>",
+   " <head>",
+   "  <title>CSS State machine tests.</title>",
+   "  <style type='text/css'>",
+   "#progress {",
+   "  width: 500px; height: 30px;",
+   "  border: 1px solid black;",
+   "  position: relative",
+   "}",
+   "#progress div {",
+   "  width: 0%; height: 100%;",
+   "  background: green;",
+   "  position: absolute;",
+   "  z-index: -1; top: 0",
+   "}",
+   "#progress.failed div {",
+   "  background: red !important;",
+   "}",
+   "#progress.failed:after {",
+   "  content: 'Some tests failed';",
+   "  color: white",
+   "}",
+   "#progress:before {",
+   "  content: 'Running test ' attr(data-progress) ' of " + tests.length + "';",
+   "  color: white;",
+   "  text-shadow: 0 0 2px darkgreen;",
+   "}",
+   "  </style>",
+   " </head>",
+   " <body>",
+   "  <h2>State machine tests for CSS autocompleter.</h2><br>",
+   "  <div id='progress' data-progress='0'>",
+   "   <div></div>",
+   "  </div>",
+   " </body>",
+   " </html>"
+  ].join("\n"));
+
+let doc = null;
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    runTests();
+  }, true);
+  content.location = TEST_URI;
+}
+
+function runTests() {
+  let completer = new cssAutoCompleter();
+  let checkState = state => {
+    if (state[0] == 'null' && (!completer.state || completer.state == 'null')) {
+      return true;
+    } else if (state[0] == completer.state && state[0] == 'selector' &&
+               state[1] == completer.selectorState &&
+               state[2] == completer.completing &&
+               state[3] == completer.selector) {
+      return true;
+    } else if (state[0] == completer.state && state[0] == 'value' &&
+               state[2] == completer.completing &&
+               state[3] == completer.propertyName) {
+      return true;
+    } else if (state[0] == completer.state &&
+               state[2] == completer.completing &&
+               state[0] != 'selector' && state[0] != 'value') {
+      return true;
+    }
+    return false;
+  };
+
+  let progress = doc.getElementById("progress");
+  let progressDiv = doc.querySelector("#progress > div");
+  let i = 0;
+  for (let test of tests) {
+    progress.dataset.progress = ++i;
+    progressDiv.style.width = 100*i/tests.length + "%";
+    completer.resolveState(limit(source, test[0]),
+                           {line: test[0][0], ch: test[0][1]});
+    if (checkState(test[1])) {
+      ok(true, "Test " + i + " passed. ");
+    }
+    else {
+      ok(false, "Test " + i + " failed. Expected state : [" + test[1] + "] but" +
+         " found [" + completer.state + ", " + completer.selectorState + ", " +
+         completer.completing + ", " +
+         (completer.propertyName || completer.selector) + "].");
+      progress.classList.add("failed");
+    }
+  }
+  gBrowser.removeCurrentTab();
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/css_autocompletion_tests.json
@@ -0,0 +1,35 @@
+// Test states to be tested for css state machine in css-autocompelter.js file.
+// Test cases are of the following format:
+// [
+//   [
+//     line, // The line location of the cursor
+//     ch    // The column locaiton of the cursor
+//   ],
+//   suggestions // Array of expected results
+// ]
+[
+  [[0, 10], []],
+  [[4,  7], ['.devtools-menulist', '.devtools-toolbarbutton']],
+  [[5,  8], ['-moz-animation', '-moz-animation-delay', '-moz-animation-direction',
+             '-moz-animation-duration', '-moz-animation-fill-mode',
+             '-moz-animation-iteration-count', '-moz-animation-name',
+             '-moz-animation-play-state', '-moz-animation-timing-function',
+             '-moz-appearance']],
+  [[12, 20], ['none', 'number-input']],
+  [[12, 22], ['none']],
+  [[17, 22], ['hsl', 'hsla']],
+  [[22,  5], ['color', 'color-interpolation', 'color-interpolation-filters']],
+  [[25, 26], ['.devtools-toolbarbutton > tab',
+              '.devtools-toolbarbutton > .toolbarbutton-menubutton-button',
+              '.devtools-toolbarbutton > hbox']],
+  [[25, 31], ['.devtools-toolbarbutton > hbox.toolbarbutton-menubutton-button']],
+  [[29, 20], ['.devtools-menulist:after', '.devtools-menulist:active']],
+  [[30, 10], ['#devtools-anotherone', '#devtools-itjustgoeson', '#devtools-menu',
+              '#devtools-okstopitnow', '#devtools-toolbarbutton', '#devtools-yetagain']],
+  [[39, 39], ['.devtools-toolbarbutton:not([label]) > tab']],
+  [[43, 51], ['.devtools-toolbarbutton:not([checked=true]):hover:after',
+              '.devtools-toolbarbutton:not([checked=true]):hover:active']],
+  [[58, 36], ['!important;']],
+  [[73, 42], [':last-child', ':lang(', ':last-of-type', ':link']],
+  [[77, 25], ['.visible']],
+]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/css_statemachine_testcases.css
@@ -0,0 +1,121 @@
+/* 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/. */
+
+.devtools-toolbar {
+  -moz-appearance: none;
+           padding:4px 3px;border-bottom-width: 1px;
+  border-bottom-style: solid;
+}
+
+#devtools-menu.devtools-menulist,
+.devtools-toolbarbutton#devtools-menu {
+  -moz-appearance: none;
+  -moz-box-align: center;
+  min-width: 78px;
+  min-height: 22px;
+  text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
+  border: 1px solid hsla(210,8%,5%,.45);
+  border-radius: 3px;
+  background: linear-gradient(hsla(212,7%,57%,.35), hsla(212,7%,57%,.1)) padding-box;
+  box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset, 0 0 0 1px hsla(210,16%,76%,.15) inset, 0 1px 0 hsla(210,16%,76%,.15);
+  margin: 0 3px;
+  color: inherit;
+}
+
+.devtools-toolbarbutton > hbox.toolbarbutton-menubutton-button {
+  -moz-box-orient: horizontal;
+}
+
+.devtools-menulist:active,
+#devtools-toolbarbutton:focus {
+  outline: 1px dotted hsla(210,30%,85%,0.7);
+  outline-offset   :  -4px;
+}
+
+.devtools-toolbarbutton:not([label]) {
+  min-width: 32px;
+}
+
+.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
+  display: none;
+}
+
+.devtools-toolbarbutton:not([checked=true]):hover:active {
+  border-color: hsla(210,8%,5%,.6);
+}
+
+.devtools-menulist["open"    ="true"],
+.devtools-toolbarbutton["open"  =   true],
+.devtools-toolbarbutton[checked=   "true"] {
+  border-color: hsla(210,8%,5%,.6) !important;
+}
+
+.devtools-toolbarbutton["checked"="true"] {
+  color: hsl(208,100%,60%);
+}
+
+.devtools-toolbarbutton[checked=true]:hover {
+  background-color: transparent !important;
+}
+
+.devtools-toolbarbutton[checked=true]:hover:active {
+  background-color: hsla(210,8%,5%,.2) !important;
+}
+
+.devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-button {
+  -moz-appearance: none;
+}
+
+.devtools-sidebar-tabs > tabs > tab:first-of-type {
+  -moz-margin-start: -3px;
+}
+
+.devtools-sidebar-tabs > tabs > tab:not(:last-of-type) {
+  background-size: calc(100% - 2px) 100%, 1px 100%;
+}
+
+.hidden-labels-box:not(.visible) > label,
+.hidden-labels-box.visible ~ .hidden-labels-box > label:last-child {
+  display: none;
+}
+
+/* Maximize the size of the viewport when the window is small */
+@media (max-width: 800px) {
+  .category-name {
+    display: none;
+  }
+}
+
+@media all and (min-width: 300px) {
+  #error-box {
+    max-width: 50%;
+    margin: 0 auto;
+    background-image: url('chrome://global/skin/icons/information-32.png');
+    min-height: 36px;
+    -moz-padding-start: 38px;
+  }
+
+  button {
+    width: auto !important;
+    min-width: 150px;
+  }
+
+  @keyframes downloadsIndicatorNotificationFinish {
+    from { opacity: 0; transform: scale(1); }
+    20%  {
+      opacity: .65;
+      animation-timing-function: ease-in;
+    } to { opacity: 0;
+      transform: scale(8); }
+  }
+}
+
+@keyframes smooth {
+  from { opacity: 0; transform: scale(1); }
+  20%  { opacity: .65; animation-timing-function: ease-in; }
+  to   {
+    opacity  : 0;
+    transform: scale(8);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/css_statemachine_tests.json
@@ -0,0 +1,84 @@
+// Test states to be tested for css state machine in css-autocompelter.js file.
+// Test cases are of the following format:
+// [
+//   [
+//     line, // The line location of the cursor
+//     ch    // The column locaiton of the cursor
+//   ],
+//   [
+//     state,         // one of CSS_STATES
+//     selectorState, // one of SELECTOR_STATES
+//     completing,    // what is being completed
+//     propertyName,  // what property is being completed in case of value state
+//                    // or the current selector that is being completed
+//   ]
+// ]
+[
+  [[0, 10], ['null', '', '', '']],
+  [[4,  3], ['selector', 'class', 'de', '.de']],
+  [[5,  8], ['property', 'null', '-moz-a']],
+  [[5, 21], ['value', 'null', 'no', '-moz-appearance']],
+  [[6, 18], ['property', 'null', 'padding']],
+  [[6, 24], ['value', 'null', '3', 'padding']],
+  [[6, 29], ['property', 'null', 'bo']],
+  [[6, 50], ['value', 'null', '1p', 'border-bottom-width']],
+  [[7, 24], ['value', 'null', 's', 'border-bottom-style']],
+  [[9,  0], ['null', 'null', '', '']],
+  [[10, 6], ['selector', 'id', 'devto', '#devto']],
+  [[10, 17], ['selector', 'class', 'de', '#devtools-menu.de']],
+  [[11,  5], ['selector', 'class', 'devt', '.devt']],
+  [[11, 30], ['selector', 'id', 'devtoo', '.devtools-toolbarbutton#devtoo']],
+  [[12, 10], ['property', 'null', '-moz-app']],
+  [[16, 27], ['value', 'null', 'hsl', 'text-shadow']],
+  [[19, 24], ['value', 'null', 'linear-gra', 'background']],
+  [[19, 55], ['value', 'null', 'hsl', 'background']],
+  [[19, 79], ['value', 'null', 'paddin', 'background']],
+  [[20, 47], ['value', 'null', 'ins', 'box-shadow']],
+  [[22, 15], ['value', 'null', 'inheri', 'color']],
+  [[25, 26], ['selector', 'null', '', '.devtools-toolbarbutton > ']],
+  [[25, 28], ['selector', 'tag', 'hb', '.devtools-toolbarbutton > hb']],
+  [[25, 41], ['selector', 'class', 'toolbarbut', '.devtools-toolbarbutton > hbox.toolbarbut']],
+  [[29, 21], ['selector', 'pseudo', 'ac', '.devtools-menulist:ac']],
+  [[30, 27], ['selector', 'pseudo', 'foc', '#devtools-toolbarbutton:foc']],
+  [[31, 18], ['value', 'null', 'dot', 'outline']],
+  [[32, 25], ['value', 'null', '-4p', 'outline-offset']],
+  [[35, 26], ['selector', 'pseudo', 'no', '.devtools-toolbarbutton:no']],
+  [[35, 28], ['selector', 'null', 'not', '']],
+  [[35, 30], ['selector', 'attribute', 'l', '[l']],
+  [[39, 46], ['selector', 'class', 'toolba', '.devtools-toolbarbutton:not([label]) > .toolba']],
+  [[43, 39], ['selector', 'value', 'tr', '[checked=tr']],
+  [[43, 47], ['selector', 'pseudo', 'hov', '.devtools-toolbarbutton:not([checked=true]):hov']],
+  [[43, 53], ['selector', 'pseudo', 'act', '.devtools-toolbarbutton:not([checked=true]):hover:act']],
+  [[47, 22], ['selector', 'attribute', 'op', '.devtools-menulist[op']],
+  [[47, 33], ['selector', 'value', 'tr', '.devtools-menulist[open =tr']],
+  [[48, 38], ['selector', 'value', 'tr', '.devtools-toolbarbutton[open = tr']],
+  [[49, 40], ['selector', 'value', 'true', '.devtools-toolbarbutton[checked= true']],
+  [[53, 34], ['selector', 'value', '=', '.devtools-toolbarbutton[checked=']],
+  [[58, 38], ['value', 'null', '!impor', 'background-color']],
+  [[61, 41], ['selector', 'pseudo', 'hov', '.devtools-toolbarbutton[checked=true]:hov']],
+  [[65, 47], ['selector', 'class', 'to', '.devtools-toolbarbutton[type=menu-button] > .to']],
+  [[69, 44], ['selector', 'pseudo', 'first-of', '.devtools-sidebar-tabs > tabs > tab:first-of']],
+  [[73, 45], ['selector', 'pseudo', 'last', ':last']],
+  [[77, 27], ['selector', 'class', 'vis', '.vis']],
+  [[78, 34], ['selector', 'class', 'hidd', '.hidden-labels-box.visible ~ .hidd']],
+  [[83,  5], ['media', 'null', 'medi']],
+  [[83, 22], ['media', 'null', '800']],
+  [[84,  9], ['selector', 'class', 'catego', '.catego']],
+  [[89,  9], ['media', 'null', 'al']],
+  [[90,  6], ['selector', 'id', 'err', '#err']],
+  [[93, 11], ['property', 'null', 'backgro']],
+  [[98,  6], ['selector', 'tag', 'butt', 'butt']],
+  [[99, 22], ['value', 'null', '!impor', 'width']],
+  [[103, 5], ['keyframes', 'null', 'ke']],
+  [[104, 7], ['frame', 'null', 'fro']],
+  [[104, 15], ['property', 'null', 'opac']],
+  [[104, 29], ['property', 'null', 'transf']],
+  [[104, 38], ['value', 'null', 'scal', 'transform']],
+  [[105,  8], ['frame', 'null', '']],
+  [[113,  6], ['keyframes', 'null', 'keyfr']],
+  [[114,  4], ['frame', 'null', 'fr']],
+  [[115,  3], ['frame', 'null', '2']],
+  [[117,  8], ['property', 'null', 'opac']],
+  [[117, 16], ['value', 'null', '0', 'opacity']],
+  [[121,  0], ['null', '', '']],
+]
--- a/browser/devtools/sourceeditor/test/head.js
+++ b/browser/devtools/sourceeditor/test/head.js
@@ -1,15 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const { require } = devtools;
 const Editor  = require("devtools/sourceeditor/editor");
 
 function setup(cb) {
   const opt = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
   const url = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
     "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
     "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
     " title='Editor' width='600' height='500'><box flex='1'/></window>";
@@ -39,9 +40,38 @@ function ch(exp, act, label) {
   is(exp.line, act.line, label + " (line)");
   is(exp.ch, act.ch, label + " (ch)");
 }
 
 function teardown(ed, win) {
   ed.destroy();
   win.close();
   finish();
-}
\ No newline at end of file
+}
+
+/**
+ * This method returns the portion of the input string `source` up to the
+ * [line, ch] location.
+ */
+function limit(source, [line, ch]) {
+  line++;
+  let list = source.split("\n");
+  if (list.length < line)
+    return source;
+  if (line == 1)
+    return list[0].slice(0, ch);
+  return [...list.slice(0, line - 1), list[line - 1].slice(0, ch)].join("\n");
+}
+
+function read(url) {
+  let scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+    .getService(Ci.nsIScriptableInputStream);
+
+  let channel = Services.io.newChannel(url, null, null);
+  let input = channel.open();
+  scriptableStream.init(input);
+
+  let data = scriptableStream.read(input.available());
+  scriptableStream.close();
+  input.close();
+
+  return data;
+}
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -11,16 +11,17 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PluralForm.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
+Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
 Cu.import("resource:///modules/devtools/SplitView.jsm");
 Cu.import("resource:///modules/devtools/StyleSheetEditor.jsm");
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
 
 const LOAD_ERROR = "error-load";
@@ -55,37 +56,27 @@ function StyleEditorUI(debuggee, target,
   this.selectedEditor = null;
 
   this._updateSourcesLabel = this._updateSourcesLabel.bind(this);
   this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
   this._onNewDocument = this._onNewDocument.bind(this);
   this._clear = this._clear.bind(this);
   this._onError = this._onError.bind(this);
 
-  this.createUI();
-
-  this._debuggee.getStyleSheets().then((styleSheets) => {
-    this._resetStyleSheetList(styleSheets);
-
-    this._target.on("will-navigate", this._clear);
-    this._target.on("navigate", this._onNewDocument);
-  });
-
   this._prefObserver = new PrefObserver("devtools.styleeditor.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument);
 }
 
 StyleEditorUI.prototype = {
   /**
    * Get whether any of the editors have unsaved changes.
    *
    * @return boolean
    */
-  get isDirty()
-  {
+  get isDirty() {
     if (this._markedDirty === true) {
       return true;
     }
     return this.editors.some((editor) => {
       return editor.sourceEditor && !editor.sourceEditor.isClean();
     });
   },
 
@@ -100,16 +91,34 @@ StyleEditorUI.prototype = {
    * Index of selected stylesheet in document.styleSheets
    */
   get selectedStyleSheetIndex() {
     return this.selectedEditor ?
            this.selectedEditor.styleSheet.styleSheetIndex : -1;
   },
 
   /**
+   * Initiates the style editor ui creation and the inspector front to get
+   * reference to the walker.
+   */
+  initialize: function() {
+    let toolbox = gDevTools.getToolbox(this._target);
+    return toolbox.initInspector().then(() => {
+      this._walker = toolbox.walker;
+    }).then(() => this.createUI())
+      .then(() => this._debuggee.getStyleSheets())
+      .then((styleSheets) => {
+      this._resetStyleSheetList(styleSheets);
+
+      this._target.on("will-navigate", this._clear);
+      this._target.on("navigate", this._onNewDocument);
+    });
+  },
+
+  /**
    * Build the initial UI and wire buttons with event handlers.
    */
   createUI: function() {
     let viewRoot = this._root.parentNode.querySelector(".splitview-root");
 
     this._view = new SplitView(viewRoot);
 
     wire(this._view.rootElement, ".style-editor-newButton", function onNew() {
@@ -231,17 +240,18 @@ StyleEditorUI.prototype = {
    * @param {StyleSheet}  styleSheet
    *        Object representing stylesheet
    * @param {nsIfile}  file
    *         Optional file object that sheet was imported from
    * @param {Boolean} isNew
    *         Optional if stylesheet is a new sheet created by user
    */
   _addStyleSheetEditor: function(styleSheet, file, isNew) {
-    let editor = new StyleSheetEditor(styleSheet, this._window, file, isNew);
+    let editor =
+      new StyleSheetEditor(styleSheet, this._window, file, isNew, this._walker);
 
     editor.on("property-change", this._summaryChange.bind(this, editor));
     editor.on("style-applied", this._summaryChange.bind(this, editor));
     editor.on("error", this._onError);
 
     this.editors.push(editor);
 
     editor.fetchSource(this._sourceLoaded.bind(this, editor));
@@ -253,18 +263,17 @@ StyleEditorUI.prototype = {
    * new stylesheet on the debuggee for it.
    *
    * @param {mixed} file
    *        Optional nsIFile or filename string.
    *        If not set a file picker will be shown.
    * @param {nsIWindow} parentWindow
    *        Optional parent window for the file picker.
    */
-  _importFromFile: function(file, parentWindow)
-  {
+  _importFromFile: function(file, parentWindow) {
     let onFileSelected = function(file) {
       if (!file) {
         // nothing selected
         return;
       }
       NetUtil.asyncFetch(file, (stream, status) => {
         if (!Components.isSuccessCode(status)) {
           this.emit("error", LOAD_ERROR);
@@ -405,18 +414,19 @@ StyleEditorUI.prototype = {
           this._selectEditor(editor);
         }
 
         if (this._styleSheetToSelect
             && this._styleSheetToSelect.href == editor.styleSheet.href) {
           this.switchToSelectedSheet();
         }
 
-        // If this is the first stylesheet, select it
-        if (!this.selectedEditor
+        // If this is the first stylesheet and there is no pending request to
+        // select a particular style sheet, select this sheet.
+        if (!this.selectedEditor && !this._styleSheetBoundToSelect
             && editor.styleSheet.styleSheetIndex == 0) {
           this._selectEditor(editor);
         }
 
         this.emit("editor-added", editor);
       }.bind(this),
 
       onShow: function(summary, details, data) {
@@ -438,16 +448,21 @@ StyleEditorUI.prototype = {
   /**
    * Switch to the editor that has been marked to be selected.
    */
   switchToSelectedSheet: function() {
     let sheet = this._styleSheetToSelect;
 
     for each (let editor in this.editors) {
       if (editor.styleSheet.href == sheet.href) {
+        // The _styleSheetBoundToSelect will always hold the latest pending
+        // requested style sheet (with line and column) which is not yet
+        // selected by the source editor. Only after we select that particular
+        // editor and go the required line and column, it will become null.
+        this._styleSheetBoundToSelect = this._styleSheetToSelect;
         this._selectEditor(editor, sheet.line, sheet.col);
         this._styleSheetToSelect = null;
         return;
       }
     }
   },
 
   /**
@@ -461,16 +476,17 @@ StyleEditorUI.prototype = {
    *         Column number to jump to
    */
   _selectEditor: function(editor, line, col) {
     line = line || 0;
     col = col || 0;
 
     editor.getSourceEditor().then(() => {
       editor.sourceEditor.setCursor({line: line, ch: col});
+      this._styleSheetBoundToSelect = null;
     });
 
     this.getEditorSummary(editor).then((summary) => {
       this._view.activeSummary = summary;
     })
   },
 
   getEditorSummary: function(editor) {
@@ -499,18 +515,17 @@ StyleEditorUI.prototype = {
    *        and the editor is not initialized we focus the first stylesheet. If
    *        a stylesheet is not passed and the editor is initialized we ignore
    *        the call.
    * @param {Number} [line]
    *        Line to which the caret should be moved (zero-indexed).
    * @param {Number} [col]
    *        Column to which the caret should be moved (zero-indexed).
    */
-  selectStyleSheet: function(href, line, col)
-  {
+  selectStyleSheet: function(href, line, col) {
     this._styleSheetToSelect = {
       href: href,
       line: line,
       col: col,
     };
 
     /* Switch to the editor for this sheet, if it exists yet.
        Otherwise each editor will be checked when it's created. */
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -10,31 +10,35 @@ this.EXPORTED_SYMBOLS = ["StyleSheetEdit
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const Editor  = require("devtools/sourceeditor/editor");
 const promise = require("sdk/core/promise");
 const {CssLogic} = require("devtools/styleinspector/css-logic");
+const AutoCompleter = require("devtools/sourceeditor/autocomplete");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
 Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
 
 const LOAD_ERROR = "error-load";
 const SAVE_ERROR = "error-save";
 
 // max update frequency in ms (avoid potential typing lag and/or flicker)
 // @see StyleEditor.updateStylesheet
 const UPDATE_STYLESHEET_THROTTLE_DELAY = 500;
 
+// Pref which decides if CSS autocompletion is enabled in Style Editor or not.
+const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled";
+
 /**
  * StyleSheetEditor controls the editor linked to a particular StyleSheet
  * object.
  *
  * Emits events:
  *   'property-change': A property on the underlying stylesheet has changed
  *   'source-editor-load': The source editor for this editor has been loaded
  *   'error': An error has occured
@@ -42,26 +46,29 @@ const UPDATE_STYLESHEET_THROTTLE_DELAY =
  * @param {StyleSheet|OriginalSource}  styleSheet
  *        Stylesheet or original source to show
  * @param {DOMWindow}  win
  *        panel window for style editor
  * @param {nsIFile}  file
  *        Optional file that the sheet was imported from
  * @param {boolean} isNew
  *        Optional whether the sheet was created by the user
+ * @param {Walker} walker
+ *        Optional walker used for selectors autocompletion
  */
-function StyleSheetEditor(styleSheet, win, file, isNew) {
+function StyleSheetEditor(styleSheet, win, file, isNew, walker) {
   EventEmitter.decorate(this);
 
   this.styleSheet = styleSheet;
   this._inputElement = null;
   this._sourceEditor = null;
   this._window = win;
   this._isNew = isNew;
   this.savedFile = file;
+  this.walker = walker;
 
   this.errorMessage = null;
 
   let readOnly = false;
   if (styleSheet.isOriginalSource) {
     // live-preview won't work with sources that need compilation
     readOnly = true;
   }
@@ -192,16 +199,20 @@ StyleSheetEditor.prototype = {
       readOnly: this._state.readOnly,
       autoCloseBrackets: "{}()[]",
       extraKeys: this._getKeyBindings(),
       contextMenu: "sourceEditorContextMenu"
     };
     let sourceEditor = new Editor(config);
 
     sourceEditor.appendTo(inputElement).then(() => {
+      if (Services.prefs.getBoolPref(AUTOCOMPLETION_PREF)) {
+        sourceEditor.extend(AutoCompleter);
+        sourceEditor.setupAutoCompletion(this.walker);
+      }
       sourceEditor.on("save", () => {
         this.saveToFile();
       });
 
       sourceEditor.on("change", () => {
         this.updateStyleSheet();
       });
 
@@ -391,16 +402,19 @@ StyleSheetEditor.prototype = {
 
     return bindings;
   },
 
   /**
    * Clean up for this editor.
    */
   destroy: function() {
+    if (this.sourceEditor) {
+      this.sourceEditor.destroy();
+    }
     this.styleSheet.off("property-change", this._onPropertyChange);
     this.styleSheet.off("error", this._onError);
   }
 }
 
 
 const TAB_CHARS = "\t";
 
--- a/browser/devtools/styleeditor/styleeditor-panel.js
+++ b/browser/devtools/styleeditor/styleeditor-panel.js
@@ -60,21 +60,23 @@ StyleEditorPanel.prototype = {
       if (this.target.form.styleSheetsActor) {
         this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
       }
       else {
         /* We're talking to a pre-Firefox 29 server-side */
         this._debuggee = StyleEditorFront(this.target.client, this.target.form);
       }
       this.UI = new StyleEditorUI(this._debuggee, this.target, this._panelDoc);
-      this.UI.on("error", this._showError);
+      this.UI.initialize().then(() => {
+        this.UI.on("error", this._showError);
 
-      this.isReady = true;
+        this.isReady = true;
 
-      deferred.resolve(this);
+        deferred.resolve(this);
+      });
     }, console.error);
 
     return deferred.promise;
   },
 
   /**
    * Show an error message from the style editor in the toolbox
    * notification box.
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/autocomplete.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<head>
+  <title>testcase for autocomplete testing</title>
+  <style type="text/css">
+  div {
+    font-size: 4em;
+  }
+
+  div > span {
+     text-decoration: underline;
+  }
+
+  div + button {
+    border: 2px dotted red;
+  }
+  </style>
+</head>
+<body>
+  <div>parent <span>child</span></div><button>sibling</button>
+</body>
+</html>
--- a/browser/devtools/styleeditor/test/browser.ini
+++ b/browser/devtools/styleeditor/test/browser.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 support-files =
+  autocomplete.html
   browser_styleeditor_cmd_edit.html
   four.html
   head.js
   import.css
   import.html
   import2.css
   longload.html
   media-small.css
@@ -21,16 +22,17 @@ support-files =
   simple.html
   sourcemaps.css
   sourcemaps.css.map
   sourcemaps.scss
   sourcemaps.html
   test_private.css
   test_private.html
 
+[browser_styleeditor_autocomplete.js]
 [browser_styleeditor_bug_740541_iframes.js]
 [browser_styleeditor_bug_851132_middle_click.js]
 [browser_styleeditor_bug_870339.js]
 [browser_styleeditor_cmd_edit.js]
 [browser_styleeditor_enabled.js]
 [browser_styleeditor_filesave.js]
 [browser_styleeditor_import.js]
 [browser_styleeditor_import_rule.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
@@ -0,0 +1,178 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "autocomplete.html";
+const MAX_SUGGESTIONS = 15;
+
+// Pref which decides if CSS autocompletion is enabled in Style Editor or not.
+const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled";
+
+// Test cases to test that autocompletion works correctly when enabled.
+// Format:
+// [
+//   -1 for pressing Ctrl + Space or the particular key to press,
+//   Number of suggestions in the popup (-1 if popup is closed),
+//   Index of selected suggestion,
+//   1 to check whether the selected suggestion is inserted into the editor or not
+// ]
+let TEST_CASES = [
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  [-1, 1, 0],
+  ['VK_DOWN', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  [-1, MAX_SUGGESTIONS, 0],
+  ['VK_END', -1],
+  ['VK_RETURN', -1],
+  ['b', MAX_SUGGESTIONS, 0],
+  ['a', 11, 0],
+  ['VK_TAB', 11, 0, 1],
+  ['VK_TAB', 11, 1, 1],
+  [':', -1],
+  ['b', 9, 0],
+  ['l', 4, 0],
+  ['VK_TAB', 4, 0, 1],
+  ['VK_TAB', 4, 1, 1],
+  ['VK_TAB', 4, 2, 1],
+  ['VK_DOWN', -1],
+  ['VK_RETURN', -1],
+  ['b', 2, 0],
+  ['u', 1, 0],
+  ['VK_TAB', -1],
+  ['{', -1],
+  ['VK_HOME', -1],
+  ['VK_DOWN', -1],
+  ['VK_DOWN', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  ['VK_RIGHT', -1],
+  [-1, 1, 0],
+];
+
+let gEditor;
+let gPopup;
+let index = 0;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndOpenStyleEditor(function(panel) {
+    panel.UI.on("editor-added", testEditorAdded);
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function testEditorAdded(aEvent, aEditor) {
+  info("Editor added, getting the source editor and starting tests");
+  aEditor.getSourceEditor().then(editor => {
+    info("source editor found, starting tests.");
+    gEditor = editor.sourceEditor;
+    gPopup = gEditor.getAutocompletionPopup();
+    waitForFocus(testState, gPanelWindow);
+  });
+}
+
+function testState() {
+  if (index == TEST_CASES.length) {
+    testAutocompletionDisabled();
+    return;
+  }
+
+  let [key] = TEST_CASES[index];
+  let mods = {};
+
+  if (key == -1) {
+    info("pressing Ctrl + Space to get result: [" + TEST_CASES[index] +
+         "] for index " + index);
+    gEditor.once("after-suggest", checkState);
+    key = " ";
+    mods.accelKey = true;
+  }
+  else if (/(down|left|right|return|home|end)/ig.test(key)) {
+    info("pressing key " + key + " to get result: [" + TEST_CASES[index] +
+         "] for index " + index);
+    gEditor.once("cursorActivity", checkState);
+  }
+  else if (key == "VK_TAB") {
+    info("pressing key " + key + " to get result: [" + TEST_CASES[index] +
+         "] for index " + index);
+    gEditor.once("suggestion-entered", checkState);
+  }
+  else {
+    info("pressing key " + key + " to get result: [" + TEST_CASES[index] +
+         "] for index " + index);
+    gEditor.once("after-suggest", checkState);
+  }
+  EventUtils.synthesizeKey(key, mods, gPanelWindow);
+}
+
+function checkState() {
+  executeSoon(() => {
+    info("After keypress for index " + index);
+    let [key, total, current, inserted] = TEST_CASES[index];
+    if (total != -1) {
+      ok(gPopup.isOpen, "Popup is open for index " + index);
+      is(total, gPopup.itemCount,
+         "Correct total suggestions for index " + index);
+      is(current, gPopup.selectedIndex,
+         "Correct index is selected for index " + index);
+      if (inserted) {
+        let { preLabel, label } = gPopup.getItemAtIndex(current);
+        let { line, ch } = gEditor.getCursor();
+        let lineText = gEditor.getText(line);
+        is(lineText.substring(ch - label.length, ch), label,
+           "Current suggestion from the popup is inserted into the editor.");
+      }
+    }
+    else {
+      ok(gPopup._panel.state != "open" && gPopup._panel.state != "showing",
+         "Popup is closed for index " + index);
+    }
+    index++;
+    testState();
+  });
+}
+
+function testAutocompletionDisabled() {
+  gBrowser.removeCurrentTab();
+
+  index = 0;
+  info("Starting test to check if autocompletion is disabled correctly.")
+  Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false);
+
+  addTabAndOpenStyleEditor(function(panel) {
+    panel.UI.on("editor-added", testEditorAddedDisabled);
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function testEditorAddedDisabled(aEvent, aEditor) {
+  info("Editor added, getting the source editor and starting tests");
+  aEditor.getSourceEditor().then(editor => {
+    ok(!editor.sourceEditor.getAutocompletionPopup,
+       "Autocompletion popup does not exist");
+    cleanup();
+  });
+}
+
+function cleanup() {
+  Services.prefs.clearUserPref(AUTOCOMPLETION_PREF);
+  gEditor = null;
+  gPopup = null;
+  finish();
+}
--- a/browser/devtools/styleinspector/test/browser_bug893965_css_property_completion_new_property.js
+++ b/browser/devtools/styleinspector/test/browser_bug893965_css_property_completion_new_property.js
@@ -100,19 +100,17 @@ function checkState(event) {
       is(editor.input.value, completion,
          "Correct value is autocompleted for state " + state);
     }
     if (total == 0) {
       ok(!(editor.popup && editor.popup.isOpen), "Popup is closed for state " +
          state);
     }
     else {
-      ok(editor.popup._panel.state == "open" ||
-         editor.popup._panel.state == "showing",
-         "Popup is open for state " + state);
+      ok(editor.popup.isOpen, "Popup is open for state " + state);
       is(editor.popup.getItems().length, total,
          "Number of suggestions match for state " + state);
       is(editor.popup.selectedIndex, index,
          "Correct item is selected for state " + state);
     }
     checkStateAndMoveOn(state + 1);
   });
 }
--- a/browser/devtools/styleinspector/test/browser_bug894376_css_value_completion_existing_property_value_pair.js
+++ b/browser/devtools/styleinspector/test/browser_bug894376_css_value_completion_existing_property_value_pair.js
@@ -112,19 +112,17 @@ function checkState(event) {
       is(editor.input.value, completion,
          "Correct value is autocompleted for state " + state);
     }
     if (total == 0) {
       ok(!(editor.popup && editor.popup.isOpen), "Popup is closed for state " +
          state);
     }
     else {
-      ok(editor.popup._panel.state == "open" ||
-         editor.popup._panel.state == "showing",
-         "Popup is open for state " + state);
+      ok(editor.popup.isOpen, "Popup is open for state " + state);
       is(editor.popup.getItems().length, total,
          "Number of suggestions match for state " + state);
       is(editor.popup.selectedIndex, index,
          "Correct item is selected for state " + state);
     }
     checkStateAndMoveOn(state + 1);
   });
 }
--- a/browser/devtools/styleinspector/test/browser_bug894376_css_value_completion_new_property_value_pair.js
+++ b/browser/devtools/styleinspector/test/browser_bug894376_css_value_completion_new_property_value_pair.js
@@ -109,19 +109,17 @@ function checkState(event) {
       is(editor.input.value, completion,
          "Correct value is autocompleted for state " + state);
     }
     if (total == 0) {
       ok(!(editor.popup && editor.popup.isOpen), "Popup is closed for state " +
          state);
     }
     else {
-      ok(editor.popup._panel.state == "open" ||
-         editor.popup._panel.state == "showing",
-         "Popup is open for state " + state);
+      ok(editor.popup.isOpen, "Popup is open for state " + state);
       is(editor.popup.getItems().length, total,
          "Number of suggestions match for state " + state);
       is(editor.popup.selectedIndex, index,
          "Correct item is selected for state " + state);
     }
     checkStateAndMoveOn(state + 1);
   });
 }
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -125,16 +125,21 @@
   -  panel. -->
 <!ENTITY options.styleeditor.label            "Style Editor">
 
 <!-- LOCALIZATION NOTE (options.stylesheetSourceMaps.label): This is the
    - label for the checkbox that toggles showing original sources in the Style Editor -->
 <!ENTITY options.stylesheetSourceMaps.label      "Show original sources">
 <!ENTITY options.stylesheetSourceMaps.tooltip    "Show original sources (e.g. Sass files) in the Style Editor and Inspector">
 
+<!-- LOCALIZATION NOTE (options.stylesheetAutocompletion.label): This is the
+   - label for the checkbox that toggles autocompletion of css in the Style Editor -->
+<!ENTITY options.stylesheetAutocompletion.label      "Autocomplete CSS">
+<!ENTITY options.stylesheetAutocompletion.tooltip    "Autocomplete CSS properties, values and selectors in Style Editor as you type">
+
 <!-- LOCALIZATION NOTE (options.profiler.label): This is the label for the
   -  heading of the group of JavaScript Profiler preferences in the options
   -  panel. -->
 <!ENTITY options.profiler.label            "JavaScript Profiler">
 
 <!-- LOCALIZATION NOTE (options.showPlatformData.label): This is the
   -  label for the checkbox that toggles the display of the platform data in the,
   -  Profiler i.e. devtools.profiler.ui.show-platform-data a boolean preference
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -44,17 +44,19 @@ pref("layers.offmainthreadcomposition.en
 pref("layers.async-pan-zoom.enabled", true);
 pref("layers.componentalpha.enabled", false);
 
 // Prefs to control the async pan/zoom behaviour
 pref("apz.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
 pref("apz.pan_repaint_interval", 50);   // prefer 20 fps
 pref("apz.fling_repaint_interval", 50); // prefer 20 fps
 pref("apz.fling_stopped_threshold", "0.2");
-
+pref("apz.x_skate_size_multiplier", "2.5");
+pref("apz.y_skate_size_multiplier", "2.5");
+pref("apz.min_skate_speed", "10.0");
 // 0 = free, 1 = standard, 2 = sticky
 pref("apz.axis_lock_mode", 2);
 pref("apz.cross_slide.enabled", true);
 
 // Enable Microsoft TSF support by default for imes.
 pref("intl.tsf.enable", true);
 pref("intl.tsf.support_imm", false);
 
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -358,16 +358,36 @@ documenttab[selected] .documenttab-selec
 #overlay-back {
   background-image: url(chrome://browser/skin/images/overlay-back.png);
 }
 
 #overlay-plus {
   background-image: url(chrome://browser/skin/images/overlay-plus.png);
 }
 
+@media (min-resolution: @min_res_140pc@) {
+  #overlay-back {
+    background-image: url(chrome://browser/skin/images/overlay-back@1.4x.png);
+  }
+
+  #overlay-plus {
+    background-image: url(chrome://browser/skin/images/overlay-plus@1.4x.png);
+  }
+}
+
+@media (min-resolution: @min_res_180pc@) {
+  #overlay-back {
+    background-image: url(chrome://browser/skin/images/overlay-back@1.8x.png);
+  }
+
+  #overlay-plus {
+    background-image: url(chrome://browser/skin/images/overlay-plus@1.8x.png);
+  }
+}
+
 #overlay-back:-moz-locale-dir(ltr),
 #overlay-plus:-moz-locale-dir(rtl) {
   left: -70px;
   background-position: right 6px center;
 }
 
 #overlay-plus:-moz-locale-dir(ltr),
 #overlay-back:-moz-locale-dir(rtl) {
index adf85a4ae3cf6e16aca833305197ddf91a074dd2..47f2e22be38165a9abedc6df0c23cb069d802e55
GIT binary patch
literal 2151
zc$}S8dsGu=77rLO3W~^^1v>_4dF3%7kcUJF;jM`fQj3V<B$+&bBs7^ofE*=6ib7Rv
zLG<t_mR6yNS{AX&S^+JlLNRVFax5Tgy8`lTX)P;gcd+2@+0)}6`<*l2_ucQ_-|ybJ
z-@P-36L>M}*1E67U@+_AVk7zJnzHoZtkId?z_vseHzX<<Nm5CW97qjg*kY9s2I3Tu
z6z0Q_IQQMJ;ZO`_l}0W|Mv}SPnIe^f2rbzVb&4#^(g+RHWkI407y*Q^RIX&<XWFmh
z0lAoke<zqr=4NqVnLIX64JYOC1fskQ5krg*+X{s0m?(h)Mj$|^$W&^WIu`yVFB9D_
z9h30DOA{o6h5sX{WNre$QK?}dm>5hDkwGdzV-Ue$kV*#w0SXxmB9VhgU=RVMFv$!i
z7z`{AJnBs?mN5B|(aXM2iG`OTNEVYs%E`$g<^&N{YAFe1Fc>5<g+!qcPz!=4SBXG6
zf>PtP!Vn2-L~3~!B3CJaB^eT`v<M52UwH*Z7MJ^qSgBb~6q+)U4$2~dL^4UCAgx$y
z5I!t<y|qS=n+22jutud-i_rU!c>R~F*F%>K(P)@E)N=Gvpv*{>NUMOANL(Zfk7|fw
zxtK|&)2VcrPA8B-5lEnd5+OlI710Q=NGuVCP$8Nmh`Pe_XLw35g%J#fM9|2b2oQ`4
zVn;_uMS`>tbRtutf<soYaY_vWDMj!KR{kF>`?XjmM-4-WN-a>SGFPe~L8d}f8ks5!
z;BWw6E+mpGmt?=CJ6aAER?D+takN^c0A41UDgToIU@-^>iy$!pra%k=RY)ci=nN=`
zK&Qi~LMRc!R1tm|FaCd%M?!-l{ZHkr9HHg7w0+eFsPL+jU?ti<YP3(XH?A7QV6Y`|
zk!*qP!DNj*)+y2P)`bc<rTmZ&AJ0$59gh2zy~ZEs^_6p}tyg`+MXu~qoPSHvTK`N#
zw!eQsb7oqgcVS6c;m;f!$9u)z5rMnCr#v3NzxkPZIA2ryTkf4VUVJoMwIyeN>U4E`
z%^G=nIv>AZYmc=|2`|OA7C4*n7DMkv7wj;G84ehlIs-cPXfNnu%WmEs07^QVKJQa3
z?sux@%zhfR;G5YT(~%Jx9t{_g3^%Z?GiApNbDoQ|ReCoMV%@c3{l?Iq(u{6nkM>}5
zz*xepTh@8I`$w%FVq$P(zc+1R*FKJMtM8<BUbAYIntVnYiRBgRx4u=kyJ__oj@47b
zQw|MU8yVlVNb2Bro9AFY(d8G|GuC^gvC6uv$B#e4uNbe(H(UUJJFV)>*=~IVy00y<
z{o<F^f5Q##OMhdiSeVK)(UaOX=(kmt`I`w>Nvy@#?tE(DXg~%&{uGY+!LuI0<7pvr
z{-E;4P`KeJ-sIO?HrIKUTD=p88<Q1Q_u4Y-^gHGLO7rs$rxEY&{Cxt@LC=;4^2{JM
z*Y^k3extOYr~L6FOV4d(4yQK%Ab8^Bgnfiz6##aUb2MTX?=<@u#UAmP-%2-p-WS&K
zq1OF<tUD6QJ}Nz@V(C60&bfG6Fb%*hkkj|cg0BNxl%}@}Eo1PAnCsH%6V(mYsiRX-
zFQx_YpSYx*qiiFUU($SbbKL4h?=Yla><BkCK(|xohi<N89)5P)6>jS{-g?)-d(-s1
z0{6Dbf?2dzf4A%MF1gak^D!lMwH4uqY-ipx&iH2(MgC~4Zk44r6CF&8b(Q2}^H{3i
zJ7g>CUw}?WS96+G(!TIYn=>5+!;R-)FZ-Y|8ug3|XpEl1%s8Yj)>Zk|4hNW08vD5B
zkV$xC4@zBob=?E!P3nh4ebw8&R_;ZkEthekOKFTjpMT!r_cLdsITi;5{lcGwXI<vo
z*M7;~Rb@*H3UfXG&3MbNP0owFU4I$&>}l3}4pd~qdb$VsA3yGtOYLSJd>b+w-<|j9
zE<CtUEn>>@8}`ORKyk#Hr?%g+>z@`>Qo6c5Ht7)j9mJfSx~s!>fv#&CuS;BAp~W^I
zw||-l*K|<asZYqd+LjPs^>@!V=nSa(q%M8ES7xt!cEMHudUpLJR#1JhblfMCDl4x!
z^qIjvBs#Q*yZQ)o$3;fvSL73EFjICfY8H_U(2Lsp5wq|131iHC`rIMs@u!88Chv<U
zIcrAMSAIVH2)wd+#O_R+Z1+hItE3Cx1ywx!(3*Q2F*kfa5*R%&Zu#t^S%J%&mWzD}
z*#`@hjqy+ZI+04e93OtUpHsMJf}cC!Ipu6tOdM}fhcDQC&jWvo56|y+fNO^4wC%Gy
zN+hlE;OUHyr={41QoEt3kfNjU;ad(I-#qC~0;Gwi25f6i-ug|eUJUmA{$=8!<kJ&I
z?2S?O-oJO$Ke#b-344V<T8ukEvbnai|LZ-2-#j~MO&d5MaxYGFbWd;I@UMuotuhP3
jM)S&-Ah-Ftr7b4bc+Yi1=7inSuRkt|7kNHnU*W$2U&&U{
new file mode 100644
index 0000000000000000000000000000000000000000..edd9d7b849b2b6898e71307302ffcea2e586e3dc
GIT binary patch
literal 2730
zc$}S9c~n#95>JJ)iXfYf2&ADD5N?v21(K8y)(F&~0t&V)hU5k!Ccz{?SOftD1r*U&
zwN}6l<*>UTMK(bwA|SL7K(H)P5m6y|LP^<du+sO=J3am5edpZyX6DXsX6`KK+~Yfa
zw&`eX)`G!cI$oaczREjk_0dpMQmIAZG35mpc?635_-s)uLjb{CSo|;u;l*WyL%tA$
z6?d>5a)iNDgdG1sQ6POgnaSs(7^^lYF*gdfdK{_ZC<ZeU5+TB%a1M`xoT+U@A~-Ax
zGJrtG(4*X-2##mG0P>6X@n^<IGD$2X)d}G!CMz9qArS*1<{sb)$zlrfgI}_eUsa=#
zhz};BNDA`Lqyp(X5pH||gdm^@022e^5e_62NC5Ff&<=scfH*V;hX!!~h$Ul4WRQUP
zc#z6y0v4O>>rVR^ODR#15h76(8I6vOjYY-cP<%l+8YGcOXbcvO#R5tTKp4jpF~k5*
zXtCzO9TGAHoG1~8&qJ)rj4*z*h=N3}eF8U%PXEg>Px!G=%95eQj3_jS!l1ca^qRF$
z<O{L?*;?oy7X_hxAt66nz*P2wZSh~B{u#PzsLY1EL%>lsg>k^0&y40mJdu|>1*v?4
zVsThx43UT@LPR2f0hu6x2iajj7@p|>Kui`p%pT8hVB_#>e*O;c3gWR2G#3{K5F~-1
z2hN2?^Kb_p?3DzA^&r@<VZC@l5rfBs*07xaU`hXoCA$eAhKMil=kpJ&O~K9xzKAc3
z;71|c+z{4u29v{Em2Fmg^f6UPz=?raGy$KB_)ugr=Wh~#*cbwljUfR<A_)WF*?1Db
z02%fGfkb2wAPA3V;z8s`JnR209$J|c`hO;8O{E;iRraqvPzrx-62w!kk3hL6*MZ5+
zFqm4Gm%EF<c<4=D+_62oKl`cM?gEGNJ%Nk0R5LQ#<>^6xLi^$F2Hs^xR&FkfrRU+1
z75GHWz;o{|Hs{Mi0&o`i7x!A(AbaaC*KJTQvWZi(PEY5XmmplhXzb&J?l?2?ed@%b
z<NWfWnVR{h@A8GkL&L+S-{lv>*QMUj$VZAZ$87#&iZR9XrG>>~uUo%uv2EdYD#Q{u
z$B??fx{#$43ajP$chXO6R4;G68E`kP1=pJK_Br4Fm`SU)rqc!;qi*Awk>icrjiJbp
z`{fOT_X3v$R+rr<t?yc1>L(tQ6n#HZe<b{q`{u5)*4`+2mZfv|(e5sZS=yNPm0b&n
z>Jqp9=^<tiP4*jswsi5w>)S`w6}MX+XpEGX8&ZR5lQliUx=R$zCX-%*^p(-!{1RHi
z>D(=C!#_t$+YZvrRw~L+3Gi6}=Typ*X;2zpj<#p(mK>L6IJ$OArfzxV+q3JE>H5?y
zF!GV=!^?w`#ep)Z`nqZcu4z&7R@$C2TuG}tKDPXdC#R?l$v2x%FL7?g$S)sNzjw8T
zy2&Up*SlhR(`eW=z%Ofky)`_vFr}g0GhreNZNx`o6+`QslRF`Fgxw@?jy`9+`Chc_
z@W}I9&7Ru(`?rryA$L|WCVSVr6l^jOm5hNZ>F~P;X+vpR7a5>S^`Jy$!B1+NeJA{6
z$HZuL%|rjl^$WM99VhOnSH)jticj5sDtV?(?A4ovgKpL98h&68L|K4wG)H^(r1AZi
zMx`fc(v*xxCCm)71vA~rz+bU_o_7BpXX-~ja)p+7(f}P7zPDz)wX7(pNjvvlb_=ZH
zh}AV!imKR-*78LgiB@iYhU#!#?N<k}bKBzd?d+gM!*h~1mNubd^HEPa`}bK494ffI
zt7juYF``x9^pG(6;?{-wYr9{K2bmvDx{~|4zUwIZ7yM=KJ!5ZP+$wWdgV*#+$QP`O
z?K1;7NZ__sLxsXrez3DFvuN)&iw?V#nHxPhmIayn(>7!^R>G1(<+W1g!`7_+h|++J
z#NrBrWar6{sshEyQ}E=&3W|K3H69GaCvfNT<m$&qt!DQ%oBZsTnDcBg^13c-DXn<K
zN)g{R>js}6o0`yGYFa?VYA{2#e<#S9u}n(q=~c<uU9t@);-s`acuGtMC^8H0xKo!h
zbyvjcc68Yyj>SnGZ8=q_xni&?2L|*%sw`ADd)Kf7bKq4<MrF+ye|>QL*g(yj#Iwff
zXh5bnRhO=p6hHR)_I9arLo)oQvHfyQvjxv_VWRn-uWJ`l1d^wDUnKQHw&B)|&1v?T
zVKItMMNmOzcg)^0hZg~UwOaKT;RV{Z;g#pFpyR|F^?8-HjCZYqinGDeTXQp!?Je4~
z+QG-a;_V6hel|dgYfU~w_qY?3Sg+^{_Nw$QKTG)qDjbMbORXA&Og=Y^>o7P_vQVoy
zCrI2|sJoTuTGs>Fa$R40$*WLJ%e=(sIn%i`g=o1j-S}}{t6DpuWqKy!bjr}%d55Sj
zqTy#&K71=Nb@L_JYn>p6lPczCu!#l3dB6bpQcGok;ypf~UuRzCuV~!-W6VV|*a0O>
z>DQ^3`6`SbbKQcQ-WZN&IL1ypn@;Tw40wNUMdL2^nQaACR8-yPyk)0<YFSUbUY+@J
zp{&tCPrJ`(pY2?}>ddymnpf-P18PI5Zy!wDfQ>$4xS#K7Djm^R;Wnun^uM1+imBh+
zkZm%F=-{A+$)X*p^H=9C94s)2(2hBsdG}(cbmLW%xsck2&foeNH=0@`mSkwDI6W@?
zeNd_Bnb%1$P=>tfR;sIW=Tj5ceK{kCG)8jlv$_YewE@G^_HEd{5|p#aO>b3?quSY`
z#a*Sw8-6cuC~1D(7jS1$*G9Fp>EVO$K6pW7mI8T>UZhGlmUnfE-!~gheZ63zVfjWY
z)j|@3dA6+Scf<P1d6P;V;+IwB58<J~JuxaP8eL1LWKSgO`rd`%$F`|WPRZ>nPQcA+
zMW;>6WQX?q<X&z&B{#*H+d@CZD;i~F__mU==adoJM5tx{^d}3SdSsXxh&6&Y-Sscn
z<1#6ZyJMZcuT|A4gBtSGe`3ZX50>fflx4Z|W=i+?4n68O*JnCx(;BjEJi%0@LiA(>
zQ+uWAu@8V~igh{D;^)0oS~wM&igp>)d^Ung`m$pFJ*xjw3-;tpcF!DjawVC0gi7d5
z&L({(L77sn%)ib9p14+z=zTTYhi!P4W4gP}`c<>Ms6O~NS2bw&dHL{JS3O_V9hUtP
rKh+%@9h*K-51HYg%<e7AR`+3U84+i0G(OK;{d@HC@NqA9-Iw|Y&zG#6
new file mode 100644
index 0000000000000000000000000000000000000000..cd70472c00c3cf1ba4717d0f3215f68bd43cef25
GIT binary patch
literal 3240
zc$}SAX;f257LKwnBEc4<l{UP%fKA9s2oXe*7d8n5hzf{GmIp-0VhEcHpaMQtMI!==
z0t&b=+8}D9NE$)J$m$A;I_!hE&|s@5;yik!XE}ZP$J9BmZr!T;eRb>Jy64nO<9NHx
z)Bj!{g+k5qa0mSmSu*qKY9sy4anr?!Fjuew6#mjsMJ!Jap<IMgK7{cQ^THrMh$oES
zeg|?yp){gI+yF%Y+lwxcifwr_HnwqM8EU3EI>*U)f(S@~;X`2}i4*Ylx(dLEgigRl
zJ2rtWV?yB~_Z@P`e}^|$up>g?AOxJ(U>xJ<2!R+<@Gx=WNJ$hu&I$OPmyV2Qrttvg
zcN0Z~6Ywvm0@xf3Q!0lrcD8mn0f9)t*gMz~?T8c_(FQ{z5XpD~8BZkRh$K3}fljo;
zd}{!*n_L)5_XAzO?S*JgK)6C7qvP?hv9Y$XWLv2`3{P}$aKIBtcoGSRSm2`KB??{~
zP7;NkWdNZlfm|e0h@=wCjLzdrqZLj7Fna{CjLrUoSQ7OuP)Nw|aXcBGXiLD0#rRq4
zD1{#s`q$P`+;|y;_k*IO(Q*Njk5KG?Z1vaB8AHSyx{q9hB!w3VN(IqkNTTonod9IT
zRwxqE2{amo2GM9Z0#QK3QHY^@9G@bv$3X&ND4$B<*@u!Tvpj!>XAmhQdsi2#J(1`@
zB(lgZuC6SQXir5t0*PfuoyB@cq7*!d0Gh>${*AT!ODvr!hj<F9oGX<^&fWq}xKtsH
z3YW?-OeV&P%@c?uGrILmj=s4H$we`c&{ZxKV}1`ZUGygo@F?~|h!85kQ3WI(j>4xR
zhbK@Sa3n+*Qut)5&;bIz;f4Qi@bHLJ`2V>%vs1`*oEiR62Z-`VB|#FTeB?--FpS>Q
zP$=EI9-s?1?#Zh{J$U2#@Ah9bIF%JxQ(BXar3M~?a&vRnz1dXqIt8b%pUQIGz(<QW
z96DXfRo>@U2No3Mh892>fu~0@3+Sh^`?ur|TqtaCN(9j6!>ZcYwlBNKG+c&n49CBy
z{qo$u_v5eqMoDMR!X_wK7X@k>qY;S#P{!!BAPNH{!Kk%hQJUJy<sP@F`ZT5ZLD{R~
zilXm|DaOPFl#<MnU>}j}km~D^@$Uwi%5#RQ){mcts_&F!F4SwcYEZS88=hLi7{Tz*
zoncNY@4cZUN~n;ru(8#1)fu<IU?aW!I;FYtoD~-IO8ul{DNQ|WP91H|%aSu1$2&O7
zTGrK9?Pxx0L1HxCFR479eXLxT|JZMcbFR47xYA|-POcFJ#H>^YdqykGffMP$IfRv2
zqdx};9TyL#!YfbJ1TUbr{c`Pz|C;@o#=nB&;7wQa;IK{Vxt{Z2-8@r(_eJ;7Ggo1S
z7P?^wP;YE=uFg(>B)2b|hi*81#67@X`{~W{kzr<!Ua=~oPDyeOEpNoi8@S0>{-R*h
z`=g|3iWKGD%9EwI2awC2KGp`F46jm~DWmwcS$38sVmi%2N;YDE*MqSpCrU#$8CaIE
zuffS-tGb`36sIk^*?&yp{aEMhF>AK9+jg*;>7({49~IxaRO}J{1@78UD)eJ@QWJ-#
zhI#dA;lIIMC&w#EdRI9TU0=p|z(|q6*_m4+e$F0YTW{aPXM<}>YRkQD7Ah|&kJyIT
zupQ*&K?~32tf#dNll5Fx%`cU5uy%8C^3&r>!G*QS8^GH8lamm|<L4vy^8HX+VwjdZ
zehuM=lT$j{Q(?!3M-l4j`Nym*tRsp~%tO0z%4p6WWv1x^1u~85OO}1^>Sd6L#8}Us
z;jkDOAIQV6IxH)64={Wd3DXKlTLO1KZqB+BdM1?F=w{Wge0XK;GMhc2RB%_zq(`S+
zrROr66h4G~bMfPe)0w^-{3YW0x7o1_{9<4YGu}PWDX~~l{%UU!6&w)R|G?RBcM$0P
zA-r@em|#QRBfnThw_Tw3HQDczbVI<+H2viR?Vqo^Fc!OkxiS*;bJeHa{Zn0eH#i<D
zco#Pzg~SbfU1zHI!3FyT7$4p=rPaDw{jzDoT5f+fvFPA*1cExp&>LnlRwD#sVl__v
z(yo2dckkz7?{ujNuqdQ`Ts1lcuP)M<zSTc{zo!oOqO|uRRK{vC(bM6*IG2L=I1x4d
z+o*3AP?fxtbD^P*Qg1W7f2?dEsD`h-{J4=GyYm34we%m@K`*$uwJJ<qxZ)=*A9~`|
zRDZIM&WE&#x4b><xJ+vMaGcczx5mhtyL*$b^yY3a?Ax(zsI2RxgY!j$pL|XV%?@Vk
zJbB2vY1HLh-(e$h10{=zW+vKh_gX1GmIp8-d!LzpUQVm(1AuiOODDd(M!^FQf?3|T
z4;q83099<QNvWo9dUN3mx1vnUHuR)Hrl%Da_5<r&MzT!yo?*Ruqqv-da%Hu!Cf6<j
zEB24?p}`-`)H;2bK9X{Nqf6s0qlp_yi{NH|u@^(n<pDs#uWpG$@iBcd>IDc>5SSVk
zTX?_^p&IC%cHG_UW3>{YPd#Zp?)AyJ=rqO?y@2$-Xjev+_U-4iw7i3rW{}={HYYOu
z7~Jv#=;yyW84^(NYVJLUL3e(cj$zf+c|LaclB;kMw?@sWqU)*&rq`a$<A;wBo*@&C
zix>GcK3MR%Wl2x`<%Lk}l8b1cfuxBEpsgu^td%%MA}lls^=xd6*7VuvsPVa7vqw9x
z{KzD6y+Mm`r$O$oE7WdX2et*iof_+12R12lM&6s+V6RrwTx)zgjc8u+Jt^J$^0)TN
zpT1zI3lx7_mj2$<@IXpJhWvcheYA7>lWYya%d?AmrQIPpz6OrxwXUvNJ^x7;nrMmj
z(Ok3M_eKz$Ij<<&0y(nL_B!He({ZrLwc~a+Y`FEb(J3!q?$seBt4;M}t}E!(+<ZW5
ziy!xTa89K)r3%YuH03Du(qA6MlBoMUW03VY)kJ?tbw_~EaVfnUO|0Li+@Aijiz5lK
zP4c?}3V(=6064e@X6E?h4T#$0_rrQ_`wl>`?hLdJOw}1X;LE)qWo=OSm4I4gk`rG*
z3i91N$}!~xF^QW1Qje=Z*%KI1l-mqXlbb-{mZF|=_onaLVh0$TXaaz=!1{yLA!6sL
zpPUqzmILH`QgD9eKb~ykfJ5umoMF}9J5e7hPq>ZfzK%?5uI-%)69x$@hIaW!d(lI@
zz#+EUB3<a!lGJJOsoNwc-?b)74(4Q%Eml|%Rss`s6IjjpKXyMq2MD%?ha=A3_L}G#
zVR~LUy3^dd{GHzB#xtph%Z?=grMYEC9C_<kt38*X#l#F!lAP0NC#dWFg06rKo!1WC
zIr;v_?k&@6`2_FQn2}ZXTHX3rqAyQqOl}7$&2wwaJ31^gRBzh$!7+gYQ{?Nr^{@OU
zhr^F82l}7qNyC_M_|i9X8g7L2^}L%xfrk6uT0aZkwUceud%?lx<90p!OBLp)#C@@Y
zmjwxpO=#k+saI3O0oVD}Y=5Sb@D9u0n^Bn9=)bb>wN>uwA>(p4qfc;Nh*s(0j2O>8
zi#F%c9`l#pN$`n>fIy@<`h3OcmWAU+N$^)+D~3`iE8GMXY89I;1*T742AYNpD<TX^
zFRc^Y)rHR-dr`PlwDEXTbmXDv3RaC}-`Os!l1Q(HstjTGVooR=V46Nixv?hYZV%?J
z4I|!?&n@qW26#lZKKd>*O_!V3NO~k3TcHblU&ehSJDS@cW-t(TwItJ+xNvOWux(p=
zO;kmu^QGWrHmpeue^syVo}T6B=Rs}O$pcdT+pC>Fn_bJ#owMuR!6ngyR{7Y(p^K`?
zXv_BfA9d6k=sDi4$m0Noo`XCRfVqeSH4*t=uZWT1t#h<d^~J^M?`~VJpZRz2V0nWV
I7$JNA2@#`SSpWb4
index 168ce7bc32cdfc369398f0d167dd445a85d5fa15..e2399199c7a91a3fbba6f7325e9436b2592b5f0a
GIT binary patch
literal 1535
zc$}S7e^3-v96x>x76jFpGWes`g;LbL-M!sC*!AuR?m!PbR5*tp!i;;n@9sA4cGulS
z4vf^CX_T3mrV^8AgutfBq{cC4C^9B14JC-06eh~?2YJclB&9ZHwTB!wjl&<knSJkl
zzwi6`e82Deec#=)zIb(V;(|nj!I11+<0#SB%E<RXf<9-zog1q!X<B}{Rw{Y5I#vOO
zTwd}3$QfjPpaigd{gy9*#bAh21$Vht?pn)mQqagoY>c6xY={g?PDp0C8lXWQ;1fhE
zeC?x8U`XJta2e&oT(TYbg*6QdC~YWqa}6~d&BHl1$P!|7f*{aXC=?8cDigB8Q@o76
zkEl@?nljO9tnl5S%3bRryQBb!GExYK;Uttr8*vIJv+<RX3Bw5#BT$?`a1(>k3{FAQ
z1J=DMyq76)6ioZlB`fULG?_usy1F`J9buFdABxj7jbbL$WI}WcM6DM!HiU?3dX&Kd
zR8A3OO^`$=BC{T;R<puz^a?@Q<(eTD)#*g(DMLf7jN(QN4F=JuwW^f>@9fs9yIuxp
z2~ee4h12WdP5+mx*`X0bJsM`6BIre70}hF+4FXYfI;^mMV&ny$At{dL%oKsp7)v1}
zi+d27@^FZUH(?&k<0VOgjPl$MZznN|rb#*r$7vkTCvpo4@*Q}VS)VXdK4p$#ouaC-
zA_t;a;T|@7RxD#z0INxgTap6NSFqkMX_D%fWXNuZmbq9?5F>JCq(;-B0!7#g_yR==
zLQ_d*g!=@*XR&}{%^oBx8wUt!(u?l#usEV?Ndn+rnq|H4G@k#z$wT#^(Eq%gs7im1
zk?l+$=)z1V0a0%sMemckw(k7~LoDoc<hnzn6Jvub;@L$ff30wC+fY*VXw#c=ZQ1va
zY~P)fiYPC^f1nvT*E^3@z2UsHdDDfaU8lvaFK!Aq%tXES=BoOwALsnuGcb8{@|lUR
zZdf?t__b{rclaF%3;eO<(9?0r<WR<z)<uog>Tr=z@$;r*?lfXxUT_~0&U;Y}6sGQM
z9_-cD3EdUpB|~5L>`PfbP`h+-0@L<fc-Qr|v0<O>p%cqS!xy{L+gctQKNCC9NXFb{
zp4-0jsR;dyc$FI1*nIWn8{cBK<>{Gi2lh{vZaFT6JAZpJ+}CyJJTW|Pumk*=)4!Ko
zYAuSZ<mPPX{Jcs#)H*a?XzQa)uMGFjKRRN`ZWefB*WAh>Z<%lYH>Y|FSAEbv;#hp(
zVPWCj+e9brgne?J#U>3m|Cn;5n(g_NFlU+PjO1NApZ0pS>q=wT{8q=iMe717Yd?Gy
zJif%dA~EjoMYq07YB|yznB%^EDezvZB|Q4htqUi+iu$32{RhzTluoFzka>S}<Amp6
zYvw`u^8URYYU*F{=g!8EL-GFhoi7;t?GNhCT{{w54arkFr`5jwAm&TEA~$88sgN&U
zs=B>;%$v4-jnG|#mRJ1L)O_m5g(~`o&Wq~G>e4)0ZTPdQJBwQ~(a<l?x<^HNd@RX)
s`nAI~^=FHBKhYa=uWIpr-(-59p=0bEYFl;qX5^Rb%rACy=WTxeA0LP=IsgCw
new file mode 100644
index 0000000000000000000000000000000000000000..2bf7aa3eb50a76e915e0a3564b1763b3b55f12a4
GIT binary patch
literal 1807
zc$}S8YfKYa7_C`!U7{kZiTGHUovI+($Fv>F3=|gI0#yqHP*6~qPNz_yoiZJyAYyz(
zP<-LKAsEueC@3b1(G?T~LKdY)1>MCr#)yV)z>1*AGqO{qth-D6u{W8y_uM(>yXSuQ
zewlqy5n;1k7rC-ntl5fiSv1q<JD!;{n0F_;ypZV@((-sZhSbp67)7u`)TD|46gn)0
zh$b*~PUcM_kj47OsEv)M<CW`BoYZkK2Zw9Y8CZ@M7;G|NcsfA?Dk4Rzmw>%>jUb>^
zOTai-$yXYrM5;DCmm*?vBVzH~bR1EG!9hTv31tH42pR)Sx(vM$HA%ovaZzUMnC5}N
zCkmY|0Y96HS4II+k|F?@3v+NjBm%?;7lI*?KeP%E@F5|OFXTZ&4kSSN2nxZ#L<5=C
zD76NSmW58N#UKfoO4A0E$IH&n=4K1IB$dL05Cq}z1w4U(!!S6;96gPhIC>-789_!E
zaY}2TwWJ<!U`$13(Gn1JZb4^IDklT$jT4DtQpPi31|G!a^K?3%lWnA<3C-8p#@HMK
z!HXu0WEO=p=b>SLvDDY44kF_Qjij{9p<o#@63@~RdRifqfXo-JTB}AyFpl7Uu#kiB
zF_<I5AQcCJRX9hb7VuSkl}02Iikxw#(o02r7(qlbF$5tHBo~H+hRS7-*pGSf1#;NW
zsa5EWG^WQ1r&jy7R{B*fDy0aFCaGAG%y8a<s8o_Bjj5yokV*j`C5CJD4!pu~MiW*E
zO1qU%hf<^t_>^Q+JB0y1L=6!bEaG5@NWc+^aF~N=)P5WS!o_~DnjnN4#>8j6`u`@6
z$C%>%>*hG8nCs{mPSybfCM${1Gvz}ub+Y|j%1NgD1PWP5tm(<?XKh6r)-P%46Fzq7
zUR`)J<78_Ru=-K*l`~rvr}h>-zOwHpqr1!RN&NZTP1SdQDFVXz`}drY9~r{g@7)dr
zpDA@+HM6WTGjGK6&E~MgUgMh_vu$8+8`b(^xWQ)LW+~m)kfIkYT6Wtra>cYhz_WR`
zX|7vmNMPZ&47mLE{jvP>!`5Q!YW4Y-9*c_uihGKcojv1$Ub0rX*{;3)oNbY{hIuO2
zp6IIhv(Xs9jsvR>YsY%oWT<Ct%fss__%U-1hidw5^X4UF)s4(c^N}@ql05%%qx-nm
z>%-S#4Gq_=@0#7(nx>^yhmXB|?U~%VpwObMcAqry4@aNDzAxvO?+i*fam@oca=bpH
zZT{G-s##Grfwm1z!_?EG7Pc648}Ybsc!LjlO4By<qP_m!+>c(vCH4X<mA2{9EHFPT
zZPV^mebCGW>|XiD6<6DBmQmmSvm<Ud0_?hh_*a>g1$Bdps$Cs}HoCGrNbEMYtiP-_
zuIX9(ue%o>9NnL^wBGka#T<KbZDCSWPj|e}kNI_ITuWYMLBKly=6V0w#q!9py%*QF
zgnrkzvODpImCKr+9~i5AaR0)h;mSQj1MOv(_oGApS&usI4b+cZi|I=1udv_E`}w$7
zSF<LuuXODD@r2@AX~7;PAbI*w!d3IDo{l7Yv-gwHpc|1}%sbZIslFUz*VWA1?#XWO
zrhMRnKD%%Hfs&{9PcI$5(!Y4RZIpfS!ItF44^XnDj);4@>sZ%&R%heEJoM(;g2s+J
zK2unp0x%@+BX_2CC3@$Z@)sw!!kq=StuKcA7m_uFUgpu_^y#)+VR=D7!<-;r|A!X~
z>T-wB{+yjT{TpkayC*HU3R_F?CF4cbjY+odm%Y|LYpK~%`!4frZ)iltA#d@+aEru{
zrb<XV^sHmh1y|o3jQ%6gkp9F~bgmPUu9ZxfsB)RiYT1tI9<1`WxiiF33f%FFQph7@
J=T|51`U{E^(LVqH
new file mode 100644
index 0000000000000000000000000000000000000000..d2a452c819a782595bcaed54284a6c9f9420eb1c
GIT binary patch
literal 2011
zc$}S84@?th6mK>W1lg)lP!MU4B}%ltUjG2sQW&M6s09Y7Fr0XIy_Q<qE4?ces%~?r
zpaMgu)-lC7&;=CY6442$DE^C$X3T-0;(#b~6^kqAM8RDhxMgu9+n3z;-uu4yd*Az;
zD@u%C<n8I_$zU+N;}*-4XnN4~c}%AFbHDlc(_}UklS(BMDk>K-;*2OIf#R$<9iqmQ
za73AxeFYC^Fy1CLDXCPdVwo5tbX>%x!!_#+4A%;eG#e01i&HEVS8McAz|n99U}=<6
zV5LaGQy8N0OwHnaBc7ZepMvFUF<1#iMzF%oV%mTXrx2D|m!&7gW-0K>u9)Us#~{FZ
zr9x??z@JW~DiT@Igb`<nxFQb5gM_S5m<x#@VHmWK#pgi+kS72k0SDrXd9WA~vBnla
zzh+db#7XklvA1YR3S?50K@5Vqxw+h20hchUK?sInkjDr4d=9O_A@lSUV&>?{pm7Uw
zoWzV81EnGKESHR+go%;@z<3BcgF^Azu$~-C6rD2Aj2J+O%L8>fa9o?Dl5o|;+GI+e
z0SA+Ck}w%Dx*n>azdbcE)TKy!BTg`C=%OH5aso5ya6J_#mjd(+SE*5og(3{bLPP=%
z%tJ&RAp)TsScGC6RLSR|JX9qV3WVc!-h_{q2|`5CP*^Ag!4MQ9h>DGkkwc*&^v2`I
zh(gA(ae9(M^cX&l)%=AGoe(RIHsT0H7*hx$YrG2*GYN_yGYJDLI+`_KfnXZFOAdC`
zXv|gIsL8>Vu|`72dX;3c<_!+OxR5UhQ}XF(g*ZpZqk~6yAwmugheA9AhQnY!0*v96
z|2KIc?G*Tr&KW<V+tJ0oo&#EVJxREp9v>q;Cy9-vOX%?%jgv>Em^&ZdxxQ`J@>ype
z3ow?C+vtj^Qv||uF<w`Kr%VzQ&Unjh2+Z`I1n6$L1^>|cZ9TU-X2-6g8GeJ><k-l|
zKAsqYdLB$$)=Qd+Nz<p>n#k+7`<$PuEe^@kb<(=kdz~eUgaqGN^$;77GXdG8H<3A<
z8th3+R!Le%U;F;;&w%>|+x3XG?9n|Jpt_ca#j^tkEF%F<mF=|k?)pL5i%Y}ScP$Ow
zS=9|aJ98GXcX#!l=;As8TAx=Gcg%8@GszlBcRf4fe3>;sX46`14aKXj_CoVES${0D
za%kQHC3ry<GpnA9@y0Uy-mPgqWN<IKeNW?D?ZdtbUqP7(;%_;5O*G}0o0os%p$jrw
zAJ^XP?mj^T|3Wr0rvV<**yoN;9x-prj~@zxwCL`O3p0OF*CmwapFB47`@;J>H`n%k
zXxn71KqHEuVYQo1itazN&O;v!^sRsIhDT%}%p|LanD%$f^HqmU{u-hs+?PClf$1oP
z=aLnsCEd@v0*LRbnD+S8Kv6^6aHUbKN_IGQC|=Y&`@|!;lj%ekNKo&AOFcCQj_AL5
z^tIi0OH<GzA7rf)%Q<e)b}2cn<(cf3du{Bddz^x>yW+}R^@5jcCr=Pq0UX$43dHi;
z(>)%4Wm@cL_mREm6jwgIw=Cm8;F&cq-+!>7d4ny#a!~!?nq(NiDM6(Yhsa*m*1R|N
zW?jIs>MgwW$)~=Vce(dgYd}l>(Ot|McdVocDi%~pJ|D`8Tz%xOyQ!_WD5JDPzc044
z+p5ND2kma|CMsKcHv41S?w+O251^UO4YsBDJ99o-S2GhT$J#F3zH`&v&U^Z@&-mc<
zOgK$t9=zQ@b@;&0a7<r%@#vH@hnty^C3)=L9D98cGpf*IT0!A|B_1+);rZO-&Dt-g
zqY-=j8V_xwo5Q{RMoCm;*Fk*glBYL5%PU@WJ)_F9rghi$ZDo0jj+|&N^*gmcC@si#
zI%q|@|JH2Gwo+!Z@3X2muk#-2$g%Ljjm)KKGn{$0PIlAFT&Pran&b62qXsLv7%sf(
zNUz8qNwF!PxD7-u*hf}b7*A@TP*wNHua?F+0=7)j)35(YVmWbj`TBKthNCJRBch-C
jtzPu_Oi;hk?}N`WTBPiPQsioh>vtO$6EClot=aeoO928x
--- a/browser/metro/theme/jar.mn
+++ b/browser/metro/theme/jar.mn
@@ -137,17 +137,21 @@ chrome.jar:
   skin/images/pinned-hdpi.png               (images/pinned-hdpi.png)
   skin/images/tile-selected-check-hdpi.png  (images/tile-selected-check-hdpi.png)
   skin/images/plus-34.png                   (images/plus-34.png)
   skin/images/plus-24.png                   (images/plus-24.png)
   skin/images/progresscircle.png            (images/progresscircle.png)
   skin/images/progresscircle-bg.png         (images/progresscircle-bg.png)
 
   skin/images/overlay-back.png              (images/overlay-back.png)
+  skin/images/overlay-back@1.4x.png         (images/overlay-back@1.4x.png)
+  skin/images/overlay-back@1.8x.png         (images/overlay-back@1.8x.png)
   skin/images/overlay-plus.png              (images/overlay-plus.png)
+  skin/images/overlay-plus@1.4x.png         (images/overlay-plus@1.4x.png)
+  skin/images/overlay-plus@1.8x.png         (images/overlay-plus@1.8x.png)
   skin/images/autoscroll.png                (images/autoscroll.png)
 
   skin/images/arrow-top.png                 (images/arrow-top.png)
   skin/images/arrow-top@1.4x.png            (images/arrow-top@1.4x.png)
   skin/images/arrow-top@1.8x.png            (images/arrow-top@1.8x.png)
   skin/images/arrow-left.png                (images/arrow-left.png)
   skin/images/arrow-left@1.4x.png           (images/arrow-left@1.4x.png)
   skin/images/arrow-left@1.8x.png           (images/arrow-left@1.8x.png)
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -675,17 +675,16 @@ menuitem:not([type]):not(.menuitem-toolt
   list-style-image: url("chrome://global/skin/icons/Restore.gif");
 }
 #close-button {
   list-style-image: url("chrome://global/skin/icons/Close.gif");
 }
 
 /* Location bar */
 #urlbar {
-  width: 7em;
   -moz-appearance: textfield;
   padding: 0;
 }
 
 .urlbar-textbox-container {
   -moz-appearance: none;
   -moz-box-align: stretch;
 }
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1475,17 +1475,16 @@ toolbar .toolbarbutton-1 > .toolbarbutto
   display: none;
 }
 
 /* ::::: nav-bar-inner ::::: */
 
 #urlbar,
 .searchbar-textbox {
   font: icon;
-  width: 7em;
   -moz-appearance: none;
   box-shadow: 0 1px rgba(255, 255, 255, 0.2), inset 0 1px hsla(0,0%,0%,.05);
   margin: 0 4px;
   padding: 0;
   border: 1px solid;
   border-color: #626262 #787878 #8c8c8c;
   background-clip: padding-box;
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -805,17 +805,16 @@ menuitem.bookmark-item {
   margin: 1px 3px;
   padding: 0;
   background-clip: padding-box;
   border: 1px solid ThreeDShadow;
   border-radius: 2px;
 }
 
 #urlbar {
-  width: 7em;
   -moz-padding-end: 2px;
 }
 
 @media (-moz-windows-default-theme) {
   #urlbar,
   .searchbar-textbox {
     @navbarTextboxCustomBorder@
   }
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -81,16 +81,17 @@ GK_ATOM(allowsameorigin,"allow-same-orig
 GK_ATOM(allowscripts,"allow-scripts")
 GK_ATOM(allowtopnavigation,"allow-top-navigation")
 GK_ATOM(allowuntrusted, "allowuntrusted")
 GK_ATOM(alt, "alt")
 GK_ATOM(alternate, "alternate")
 GK_ATOM(always, "always")
 GK_ATOM(ancestor, "ancestor")
 GK_ATOM(ancestorOrSelf, "ancestor-or-self")
+GK_ATOM(anchor, "anchor")
 GK_ATOM(_and, "and")
 GK_ATOM(any, "any")
 GK_ATOM(mozapp, "mozapp")
 GK_ATOM(applet, "applet")
 GK_ATOM(applyImports, "apply-imports")
 GK_ATOM(applyTemplates, "apply-templates")
 GK_ATOM(mozapptype, "mozapptype")
 GK_ATOM(apz, "apz")
--- a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
+++ b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
@@ -9208,410 +9208,414 @@ 18206c24014
 18467a24276
 > could've
 19035a24845
 > cul-de-sac
 19246c25056
 < cysteine
 ---
 > cysteine/M
-20196,20197c26006,26007
+19935a25746
+> dequeue/DSG
+20196,20197c26007,26008
 < dialog/SM
 < dialogue/SM
 ---
 > dialog/SMGD
 > dialogue/SMRGD
-20481a26292
+20481a26293
 > disclose/DSG
-20830c26641
+20830c26642
 < dogie/M
 ---
 > dogie/SM
-20895a26707
+20895a26708
 > donator/MS
-21820a27633
+21820a27634
 > elicitor/MS
-22071a27885
+22071a27886
 > encyclopaedia
-22556a28371
+22196a28012
+> enqueue/DSG
+22556a28373
 > estoppel
-22638c28453
+22638c28455
 < euthanize
 ---
 > euthanize/DSG
-22719a28535
+22719a28537
 > exabyte/MS
-22947a28764
+22947a28766
 > experimentalism
-23207,23208d29023
+23207,23208d29025
 < faecal
 < faeces/M
-23215c29030
+23215c29032
 < faggoting's
 ---
 > faggot/SMG
-23701a29517
+23701a29519
 > filesystem/MS
-24155c29971
+24155c29973
 < fluidized
 ---
 > fluidize/DSG
-24216a30033
+24216a30035
 > foci
-24736d30552
+24736d30554
 < frier/M
-24855,24856c30671,30672
+24855,24856c30673,30674
 < fucker/M!
 < fuckhead/S!
 ---
 > fucker/SM!
 > fuckhead/SM!
-24953d30768
+24953d30770
 < furore/MS
-25125c30940
+25125c30942
 < gaolbird/S
 ---
 > gaolbirds
-25180d30994
+25180d30996
 < gasolene/M
-25190a31005
+25190a31007
 > gastroenterologist/M
-25262c31077
+25262c31079
 < geezer/M
 ---
 > geezer/MS
-25327c31142
+25327c31144
 < genomic
 ---
 > genomic/S
-25462a31278
+25462a31280
 > gigabit/MS
-25464a31281,31283
+25464a31283,31285
 > gigajoule/MS
 > gigapixel/MS
 > gigawatt/MS
-25560d31378
+25560d31380
 < glamourize/DSG
-25674c31492
+25674c31494
 < glycerine's
 ---
 > glycerine/M
-25905c31723
+25905c31725
 < gram/MS
 ---
 > gram/KMS
-25909d31726
+25909d31728
 < gramme/SM
-26063c31880,31881
+26063c31882,31883
 < greybeard
 ---
 > grey/MDRTGSP
 > greybeard/SM
-26066c31884
+26066c31886
 < greyness
 ---
 > greyness/M
-26246,26247d32063
+26246,26247d32065
 < guerilla's
 < guerillas
-26432,26436d32247
+26432,26436d32249
 < haemoglobin's
 < haemophilia/M
 < haemorrhage/DSMG
 < haemorrhoid/S
 < haemorrhoids/M
-27167c32978
+27167c32980
 < hexane
 ---
 > hexane/SM
-27273a33085
+27273a33087
 > hippopotami
-27875d33686
+27875d33688
 < hyaena/SM
-28017c33828
+28017c33830
 < iPod/M
 ---
 > iPod/MS
-28105a33917
+28105a33919
 > idolator/SM
-28513c34325
+28513c34327
 < inbound
 ---
 > inbound/s
-28650a34463
+28650a34465
 > indices
-28812d34624
+28812d34626
 < inflexion/SM
-29216a35029
+29216a35031
 > intern/GDL
-29266a35080
+29266a35082
 > interruptible/U
-29272a35087,35090
+29272a35089,35092
 > intersex
 > intersexual/MS
 > intersexualism
 > intersexuality
-29724c35542
+29724c35544
 < jewellery's
 ---
 > jewellery/M
-29870a35689
+29870a35691
 > judgement/MS
-30066c35885
+30066c35887
 < kiddie/M
 ---
 > kiddie/SM
-30262,30263c36081
+30262,30263c36083
 < kraut's
 < kraut/S!
 ---
 > kraut/MS!
-30665a36484
+30665a36486
 > lector/MS
-31031c36850
+31031c36852
 < linguini's
 ---
 > linguini/M
-31151,31152c36970
+31151,31152c36972
 < liver's
 < liver/S
 ---
 > liver/MS
-32230c38048
+32230c38050
 < meanie/M
 ---
 > meanie/MS
-32317,32318c38135
+32317,32318c38137
 < megadeath/M
 < megadeaths
 ---
 > megadeath/SM
-32320c38137
+32320c38139
 < megajoules
 ---
 > megajoule/SM
-32329c38146
+32329c38148
 < megapixel/S
 ---
 > megapixel/MS
-32708a38526
+32708a38528
 > might've
-32717a38536
+32717a38538
 > migrator/SM
-32760a38580
+32760a38582
 > millennia
-32777d38596
+32777d38598
 < millionnaire/M
-32934a38754
+32934a38756
 > miscommunication/S
-32991a38812
+32991a38814
 > misjudgement/MS
-33784a39606
+33784a39608
 > must've
-33963c39785
+33963c39787
 < native/MS
 ---
 > native/MSY
-34169,34171c39991,39992
+34169,34171c39993,39994
 < neurone/S
 < neurophysiology
 < neuroscience
 ---
 > neurophysiology/M
 > neuroscience/MS
-34275c40096
+34275c40098
 < nightie/M
 ---
 > nightie/SM
-35104a40926
+35104a40928
 > octopi
-35219d41040
+35219d41042
 < oleomargarin/M
-35226a41048
+35226a41050
 > oligo
-35913c41735
+35913c41737
 < oversize/D
 ---
 > oversize
-36056,36059d41877
+36056,36059d41879
 < paederast/S
 < paediatrician's
 < paediatricians
 < paediatrics/M
-36291a42110
+36291a42112
 > paralyses
-36403d42221
+36403d42223
 < parrakeet/MS
-36449d42266
+36449d42268
 < partizan/SM
-37093a42911
+37093a42913
 > petabyte/MS
-37102c42920
+37102c42922
 < petitioner/M
 ---
 > petitioner/MS
-37264a43083
+37264a43085
 > phosphorylate/DSGN
-37316d43134
+37316d43136
 < phrenetic
-37796a43615
+37796a43617
 > plugin/MS
-37987c43806
+37987c43808
 < polypeptide/S
 ---
 > polypeptide/MS
-38291d44109
+38291d44111
 < practise's
-38451a44270
+38451a44272
 > prejudgement/MS
-38805a44625
+38805a44627
 > profiler/SM
-38835a44656
+38835a44658
 > programmatically
-38891a44713,44714
+38891a44715,44716
 > pronate/DSGN
 > pronator/MS
-38951c44774
+38951c44776
 < proprietorship/M
 ---
 > proprietorship/MS
-39039a44863
+39039a44865
 > provender/M
-39564a45389
+39564a45391
 > quinoa
-40036a45862
+40036a45864
 > recency
-40140a45967
+40140a45969
 > recurse/DGSV
-40141a45969
+40141a45971
 > recuse/DGS
-40208a46037
+40208a46039
 > refactor/SMDG
-40244d46072
+40244d46074
 < reflexion/SM
-40659d46486
+40659d46488
 < resizing
-40829c46656
+40829c46658
 < reverie/M
 ---
 > reverie/MS
-41415a47243
+41415a47245
 > sabre/MS
-41914c47742
+41914c47744
 < schnaps's
 ---
 > schnaps/M
-41949c47777
+41949c47779
 < schrod's
 ---
 > schrod/SM
-41998a47827
+41998a47829
 > scot-free
-42883,42885c48712
+42883,42885c48714
 < shit's
 < shit/S!
 < shite/S!
 ---
 > shit/MS!
-42887,42888c48714,48715
+42887,42888c48716,48717
 < shithead/S!
 < shitload/!
 ---
 > shithead/MS!
 > shitload/MS!
-42891c48718
+42891c48720
 < shitty/RT!
 ---
 > shitty/TR!
-42976a48804
+42976a48806
 > should've
-43008c48836
+43008c48838
 < showtime
 ---
 > showtime/MS
-43328c49156
+43328c49158
 < size/MGBDRS
 ---
 > size/AMGBDRS
-43724,43726c49552
+43724,43726c49554
 < smoulder's
 < smouldered
 < smoulders
 ---
 > smoulder/GSMD
-44062c49888
+44062c49890
 < sonofabitch
 ---
 > sonofabitch/!
-44346a50173
+44346a50175
 > spelled
-44348a50176
+44348a50178
 > spelt
-44371a50200
+44371a50202
 > spick/S!
-44383c50212
+44383c50214
 < spik/S
 ---
 > spik/S!
-46106a51936
+46106a51938
 > syllabi
-46160c51990
+46160c51992
 < synch/GMD
 ---
 > synch/GMDS
-46167d51996
+46167d51998
 < synchs
-46203,46204c52032,52033
+46203,46204c52034,52035
 < sysadmin/S
 < sysop/S
 ---
 > sysadmin/MS
 > sysop/MS
-46752a52582
+46752a52584
 > terabit/MS
-46753a52584,52585
+46753a52586,52587
 > terahertz/M
 > terapixel/MS
-46817a52650
+46817a52652
 > testcase/MS
-46831a52665
+46831a52667
 > testsuite/MS
-46925a52760
+46925a52762
 > theremin/MS
-47455c53290
+47455c53292
 < toolbar
 ---
 > toolbar/MS
-47755a53591
+47755a53593
 > transfect/DSMG
-47774a53611,53612
+47774a53613,53614
 > transgenderism
 > transgene/MS
-47951c53789
+47951c53791
 < triage/M
 ---
 > triage/MG
-48869a54708
+48869a54710
 > unlikeable
-49211c55050
+49211c55052
 < vagina/M
 ---
 > vagina/MS
-49368,49369c55207
+49368,49369c55209
 < velour's
 < velours's
 ---
 > velour/MS
-49478a55317
+49478a55319
 > vertices
-50148a55988
+50148a55990
 > weaponize/DSG
-50260,50261d56099
+50260,50261d56101
 < werwolf/M
 < werwolves
-50728c56566
+50728c56568
 < women
 ---
 > women/M
-50794c56632
+50794c56634
 < wop/S!
 ---
 > wop/MS!
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-57454
+57456
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -26010,16 +26010,17 @@ deprive/GDS
 deprogram/S
 deprogramming
 depth/M
 depths
 deputation/SM
 depute/DSG
 deputize/DSG
 deputy/SM
+dequeue/DSG
 derailleur/MS
 derailment/MS
 derangement/M
 derby/SM
 derelict/MS
 dereliction/MS
 deride/D
 derision/M
@@ -28280,16 +28281,17 @@ enmity/SM
 ennoble/DSGL
 ennoblement/M
 ennui/M
 enormity/SM
 enormous/YP
 enormousness/M
 enough/M
 enplane/DSG
+enqueue/DSG
 enquirer/S
 enquiringly
 enrage/GDS
 enrapture/DSG
 enrich/DSLG
 enrichment/M
 enrobed
 enroll/DLSG
--- a/layout/xul/nsMenuFrame.cpp
+++ b/layout/xul/nsMenuFrame.cpp
@@ -663,16 +663,37 @@ nsMenuFrame::AttributeChanged(int32_t aN
       aAttribute == nsGkAtoms::name) {
     nsCOMPtr<nsIRunnable> event =
       new nsMenuAttributeChangedEvent(this, aAttribute);
     nsContentUtils::AddScriptRunner(event);
   }
   return NS_OK;
 }
 
+nsIContent*
+nsMenuFrame::GetAnchor()
+{
+  mozilla::dom::Element* anchor = nullptr;
+
+  nsAutoString id;
+  mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::anchor, id);
+  if (!id.IsEmpty()) {
+    nsIDocument* doc = mContent->OwnerDoc();
+
+    anchor =
+      doc->GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, id);
+    if (!anchor) {
+      anchor = doc->GetElementById(id);
+    }
+  }
+
+  // Always return the menu's content if the anchor wasn't set or wasn't found.
+  return anchor && anchor->GetPrimaryFrame() ? anchor : mContent;
+}
+
 void
 nsMenuFrame::OpenMenu(bool aSelectFirstItem)
 {
   if (!mContent)
     return;
 
   gEatMouseMove = true;
 
@@ -720,17 +741,17 @@ NS_IMETHODIMP
 nsMenuFrame::DoLayout(nsBoxLayoutState& aState)
 {
   // lay us out
   nsresult rv = nsBoxFrame::DoLayout(aState);
 
   nsMenuPopupFrame* popupFrame = GetPopup();
   if (popupFrame) {
     bool sizeToPopup = IsSizedToPopup(mContent, false);
-    popupFrame->LayoutPopup(aState, this, sizeToPopup);
+    popupFrame->LayoutPopup(aState, this, GetAnchor()->GetPrimaryFrame(), sizeToPopup);
   }
 
   return rv;
 }
 
 #ifdef DEBUG_LAYOUT
 NS_IMETHODIMP
 nsMenuFrame::SetDebug(nsBoxLayoutState& aState, bool aDebug)
--- a/layout/xul/nsMenuFrame.h
+++ b/layout/xul/nsMenuFrame.h
@@ -123,16 +123,22 @@ public:
                           nsIFrame*       aOldFrame) MOZ_OVERRIDE;
 
   virtual nsIAtom* GetType() const MOZ_OVERRIDE { return nsGkAtoms::menuFrame; }
 
   NS_IMETHOD SelectMenu(bool aActivateFlag);
 
   virtual nsIScrollableFrame* GetScrollTargetFrame() MOZ_OVERRIDE;
 
+  // Retrieve the element that the menu should be anchored to. By default this is
+  // the menu itself. However, the anchor attribute may refer to the value of an
+  // anonid within the menu's binding, or, if not found, the id of an element in
+  // the document.
+  nsIContent* GetAnchor();
+
   /**
    * NOTE: OpenMenu will open the menu asynchronously.
    */
   void OpenMenu(bool aSelectFirstItem);
   // CloseMenu closes the menu asynchronously
   void CloseMenu(bool aDeselectMenu);
 
   bool IsChecked() { return mChecked; }
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -374,17 +374,18 @@ nsMenuPopupFrame::IsLeaf() const
   // the parent menu is dependent on the size of the popup, so the frames
   // need to exist in order to calculate this size.
   nsIContent* parentContent = mContent->GetParent();
   return (parentContent &&
           !parentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::sizetopopup));
 }
 
 void
-nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup)
+nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
+                              nsIFrame* aAnchor, bool aSizedToPopup)
 {
   if (!mGeneratedChildren)
     return;
 
   SchedulePaint();
 
   bool shouldPosition = true;
   bool isOpen = IsOpen();
@@ -422,34 +423,34 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayou
   // if the size changed then set the bounds to be the preferred size
   bool sizeChanged = (mPrefSize != prefSize);
   if (sizeChanged) {
     SetBounds(aState, nsRect(0, 0, prefSize.width, prefSize.height), false);
     mPrefSize = prefSize;
   }
 
   if (shouldPosition) {
-    SetPopupPosition(aParentMenu, false);
+    SetPopupPosition(aAnchor, false, aSizedToPopup);
   }
 
   nsRect bounds(GetRect());
   Layout(aState);
 
   // if the width or height changed, readjust the popup position. This is a
   // special case for tooltips where the preferred height doesn't include the
   // real height for its inline element, but does once it is laid out.
   // This is bug 228673 which doesn't have a simple fix.
   if (!aParentMenu) {
     nsSize newsize = GetSize();
     if (newsize.width > bounds.width || newsize.height > bounds.height) {
       // the size after layout was larger than the preferred size,
       // so set the preferred size accordingly
       mPrefSize = newsize;
       if (isOpen) {
-        SetPopupPosition(nullptr, false);
+        SetPopupPosition(nullptr, false, aSizedToPopup);
       }
     }
   }
 
   nsPresContext* pc = PresContext();
   nsView* view = GetView();
 
   if (sizeChanged) {
@@ -1115,17 +1116,17 @@ nsMenuPopupFrame::FlipOrResize(nscoord& 
   // smaller than the calculated popup size, just use the original size instead.
   if (popupSize <= 0 || aSize < popupSize) {
     popupSize = aSize;
   }
   return std::min(popupSize, aScreenEnd - aScreenPoint);
 }
 
 nsresult
-nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove)
+nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup)
 {
   if (!mShouldAutoPosition)
     return NS_OK;
 
   // If this is due to a move, return early if the popup hasn't been laid out
   // yet. On Windows, this can happen when using a drag popup before it opens.
   if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
     return NS_OK;
@@ -1147,39 +1148,33 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
 
     if (!aAnchorFrame) {
       aAnchorFrame = rootFrame;
       if (!aAnchorFrame)
         return NS_OK;
     }
   }
 
-  bool sizedToPopup = false;
-  if (aAnchorFrame->GetContent()) {
-    // the popup should be the same size as the anchor menu, for example, a menulist.
-    sizedToPopup = nsMenuFrame::IsSizedToPopup(aAnchorFrame->GetContent(), false);
-  }
-
   // the dimensions of the anchor in its app units
   nsRect parentRect = aAnchorFrame->GetScreenRectInAppUnits();
 
   // the anchor may be in a different document with a different scale,
   // so adjust the size so that it is in the app units of the popup instead
   // of the anchor.
   parentRect = parentRect.ConvertAppUnitsRoundOut(
     aAnchorFrame->PresContext()->AppUnitsPerDevPixel(),
     presContext->AppUnitsPerDevPixel());
 
   // Set the popup's size to the preferred size. Below, this size will be
   // adjusted to fit on the screen or within the content area. If the anchor
   // is sized to the popup, use the anchor's width instead of the preferred
   // width. The preferred size should already be set by the parent frame.
   NS_ASSERTION(mPrefSize.width >= 0 || mPrefSize.height >= 0,
                "preferred size of popup not set");
-  mRect.width = sizedToPopup ? parentRect.width : mPrefSize.width;
+  mRect.width = aSizedToPopup ? parentRect.width : mPrefSize.width;
   mRect.height = mPrefSize.height;
 
   // the screen position in app units where the popup should appear
   nsPoint screenPoint;
 
   // For anchored popups, the anchor rectangle. For non-anchored popups, the
   // size will be 0.
   nsRect anchorRect = parentRect;
@@ -1365,17 +1360,17 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr
   }
 
   presContext->GetPresShell()->GetViewManager()->
     MoveViewTo(view, viewPoint.x, viewPoint.y);
 
   // Now that we've positioned the view, sync up the frame's origin.
   nsBoxFrame::SetPosition(viewPoint - GetParent()->GetOffsetTo(rootFrame));
 
-  if (sizedToPopup) {
+  if (aSizedToPopup) {
     nsBoxLayoutState state(PresContext());
     // XXXndeakin can parentSize.width still extend outside?
     SetBounds(state, nsRect(mRect.x, mRect.y, parentRect.width, mRect.height));
   }
 
   return NS_OK;
 }
 
@@ -1929,17 +1924,17 @@ nsMenuPopupFrame::MoveTo(int32_t aLeft, 
     margin.left += offsetForContextMenu;
     margin.top += offsetForContextMenu;
   }
 
   nsPresContext* presContext = PresContext();
   mScreenXPos = aLeft - presContext->AppUnitsToIntCSSPixels(margin.left);
   mScreenYPos = aTop - presContext->AppUnitsToIntCSSPixels(margin.top);
 
-  SetPopupPosition(nullptr, true);
+  SetPopupPosition(nullptr, true, false);
 
   nsCOMPtr<nsIContent> popup = mContent;
   if (aUpdateAttrs && (popup->HasAttr(kNameSpaceID_None, nsGkAtoms::left) ||
                        popup->HasAttr(kNameSpaceID_None, nsGkAtoms::top)))
   {
     nsAutoString left, top;
     left.AppendInt(aLeft);
     top.AppendInt(aTop);
@@ -1957,17 +1952,17 @@ nsMenuPopupFrame::MoveToAnchor(nsIConten
   NS_ASSERTION(mPopupState == ePopupOpenAndVisible, "popup must be open to move it");
 
   InitializePopup(aAnchorContent, mTriggerContent, aPosition,
                   aXPos, aYPos, aAttributesOverride);
   // InitializePopup changed the state so reset it.
   mPopupState = ePopupOpenAndVisible;
 
   // Pass false here so that flipping and adjusting to fit on the screen happen.
-  SetPopupPosition(nullptr, false);
+  SetPopupPosition(nullptr, false, false);
 }
 
 bool
 nsMenuPopupFrame::GetAutoPosition()
 {
   return mShouldAutoPosition;
 }
 
--- a/layout/xul/nsMenuPopupFrame.h
+++ b/layout/xul/nsMenuPopupFrame.h
@@ -198,26 +198,27 @@ public:
   uint8_t GetShadowStyle();
 
   NS_IMETHOD SetInitialChildList(ChildListID     aListID,
                                  nsFrameList&    aChildList) MOZ_OVERRIDE;
 
   virtual bool IsLeaf() const MOZ_OVERRIDE;
 
   // layout, position and display the popup as needed
-  void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, bool aSizedToPopup);
+  void LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
+                   nsIFrame* aAnchor, bool aSizedToPopup);
 
   nsView* GetRootViewForPopup(nsIFrame* aStartFrame);
 
   // set the position of the popup either relative to the anchor aAnchorFrame
   // (or the frame for mAnchorContent if aAnchorFrame is null) or at a specific
   // point if a screen position (mScreenXPos and mScreenYPos) are set. The popup
   // will be adjusted so that it is on screen. If aIsMove is true, then the popup
   // is being moved, and should not be flipped.
-  nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove);
+  nsresult SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aSizedToPopup);
 
   bool HasGeneratedChildren() { return mGeneratedChildren; }
   void SetGeneratedChildren() { mGeneratedChildren = true; }
 
   // called when the Enter key is pressed while the popup is open. This will
   // just pass the call down to the current menu, if any. If a current menu
   // should be opened as a result, this method should return the frame for
   // that menu, or null if no menu should be opened. Also, calling Enter will
--- a/layout/xul/nsPopupSetFrame.cpp
+++ b/layout/xul/nsPopupSetFrame.cpp
@@ -125,17 +125,17 @@ NS_IMETHODIMP
 nsPopupSetFrame::DoLayout(nsBoxLayoutState& aState)
 {
   // lay us out
   nsresult rv = nsBoxFrame::DoLayout(aState);
 
   // lay out all of our currently open popups.
   for (nsFrameList::Enumerator e(mPopupList); !e.AtEnd(); e.Next()) {
     nsMenuPopupFrame* popupChild = static_cast<nsMenuPopupFrame*>(e.get());
-    popupChild->LayoutPopup(aState, nullptr, false);
+    popupChild->LayoutPopup(aState, nullptr, nullptr, false);
   }
 
   return rv;
 }
 
 void
 nsPopupSetFrame::RemovePopupFrame(nsIFrame* aPopup)
 {
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -337,17 +337,17 @@ nsXULPopupManager::AdjustPopupsOnWindowC
       nsIContent* popup = frame->GetContent();
       if (popup) {
         nsIDocument* document = popup->GetCurrentDoc();
         if (document) {
           nsPIDOMWindow* window = document->GetWindow();
           if (window) {
             window = window->GetPrivateRoot();
             if (window == aWindow) {
-              frame->SetPopupPosition(nullptr, true);
+              frame->SetPopupPosition(nullptr, true, false);
             }
           }
         }
       }
     }
 
     item = item->GetParent();
   }
@@ -390,17 +390,17 @@ nsXULPopupManager::PopupMoved(nsIFrame* 
   if ((aPnt.x != currentPnt.x || aPnt.y != currentPnt.y) || (widget &&
       widget->GetClientOffset() != menuPopupFrame->GetLastClientOffset())) {
     // Update the popup's position using SetPopupPosition if the popup is
     // anchored and at the parent level as these maintain their position
     // relative to the parent window. Otherwise, just update the popup to
     // the specified screen coordinates.
     if (menuPopupFrame->IsAnchored() &&
         menuPopupFrame->PopupLevel() == ePopupLevelParent) {
-      menuPopupFrame->SetPopupPosition(nullptr, true);
+      menuPopupFrame->SetPopupPosition(nullptr, true, false);
     }
     else {
       menuPopupFrame->MoveTo(aPnt.x, aPnt.y, false);
     }
   }
 }
 
 void
@@ -600,17 +600,17 @@ nsXULPopupManager::ShowMenu(nsIContent *
   nsAutoString position;
   if (onMenuBar || !onmenu)
     position.AssignLiteral("after_start");
   else
     position.AssignLiteral("end_before");
 
   // there is no trigger event for menus
   InitTriggerEvent(nullptr, nullptr, nullptr);
-  popupFrame->InitializePopup(aMenu, nullptr, position, 0, 0, true);
+  popupFrame->InitializePopup(menuFrame->GetAnchor(), nullptr, position, 0, 0, true);
 
   if (aAsynchronous) {
     nsCOMPtr<nsIRunnable> event =
       new nsXULPopupShowingEvent(popupFrame->GetContent(),
                                  parentIsContextMenu, aSelectFirstItem);
     NS_DispatchToCurrentThread(event);
   }
   else {
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -53,16 +53,44 @@ Cu.import("resource://gre/modules/Promis
 
 // The implementation of communications
 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
 
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
 
+/**
+ * Constructors for decoding standard exceptions
+ * received from the worker.
+ */
+const EXCEPTION_CONSTRUCTORS = {
+  EvalError: function(error) {
+    return new EvalError(error.message, error.fileName, error.lineNumber);
+  },
+  InternalError: function(error) {
+    return new InternalError(error.message, error.fileName, error.lineNumber);
+  },
+  RangeError: function(error) {
+    return new RangeError(error.message, error.fileName, error.lineNumber);
+  },
+  ReferenceError: function(error) {
+    return new ReferenceError(error.message, error.fileName, error.lineNumber);
+  },
+  SyntaxError: function(error) {
+    return new SyntaxError(error.message, error.fileName, error.lineNumber);
+  },
+  TypeError: function(error) {
+    return new TypeError(error.message, error.fileName, error.lineNumber);
+  },
+  URIError: function(error) {
+    return new URIError(error.message, error.fileName, error.lineNumber);
+  }
+};
+
 // It's possible for osfile.jsm to get imported before the profile is
 // set up. In this case, some path constants aren't yet available.
 // Here, we make them lazy loaders.
 
 function lazyPathGetter(constProp, dirKey) {
   return function () {
     let path;
     try {
@@ -211,29 +239,34 @@ let Scheduler = {
           Scheduler._updateTelemetry();
         }
 
         // Don't restart the timer when reseting the worker, since that will
         // lead to an endless "resetWorker()" loop.
         if (method != "Meta_reset") {
           Scheduler.restartTimer();
         }
-
+        // Check and throw EvalError | InternalError | RangeError
+        // | ReferenceError | SyntaxError | TypeError | URIError
+        if (error.data && error.data.exn in EXCEPTION_CONSTRUCTORS) {
+          throw EXCEPTION_CONSTRUCTORS[error.data.exn](error.data);
+        }
         // Decode any serialized error
         if (error instanceof PromiseWorker.WorkerError) {
           throw OS.File.Error.fromMsg(error.data);
         }
         // Extract something meaningful from ErrorEvent
         if (error instanceof ErrorEvent) {
           let message = error.message;
           if (message == "uncaught exception: [object StopIteration]") {
             throw StopIteration;
           }
           throw new Error(message, error.filename, error.lineno);
         }
+
         throw error;
       }
     );
   },
 
   /**
    * Post Telemetry statistics.
    *
--- a/toolkit/components/osfile/modules/osfile_async_worker.js
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -4,16 +4,27 @@ if (this.Components) {
 
 /* 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/. */
 
 
 // Worker thread for osfile asynchronous front-end
 
+// Exception names to be posted from the worker thread to main thread
+const EXCEPTION_NAMES = {
+  EvalError: "EvalError",
+  InternalError: "InternalError",
+  RangeError: "RangeError",
+  ReferenceError: "ReferenceError",
+  SyntaxError: "SyntaxError",
+  TypeError: "TypeError",
+  URIError: "URIError"
+};
+
 (function(exports) {
   "use strict";
 
   // Timestamps, for use in Telemetry.
   // The object is set to |null| once it has been sent
   // to the main thread.
   let timeStamps = {
     entered: Date.now(),
@@ -105,16 +116,21 @@ if (this.Components) {
      LOG("Sending back StopIteration");
      post({StopIteration: true, id: id, durationMs: durationMs});
    } else if (exn instanceof exports.OS.File.Error) {
      LOG("Sending back OS.File error", exn, "id is", id);
      // Instances of OS.File.Error know how to serialize themselves
      // (deserialization ensures that we end up with OS-specific
      // instances of |OS.File.Error|)
      post({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs});
+   } else if (exn.constructor.name in EXCEPTION_NAMES) {
+     LOG("Sending back exception", exn.constructor.name);
+     post({fail: {exn: exn.constructor.name, message: exn.message,
+                  fileName: exn.fileName, lineNumber: exn.lineNumber},
+           id: id, durationMs: durationMs});
    } else {
      // Other exceptions do not, and should be propagated through DOM's
      // built-in mechanism for uncaught errors, although this mechanism
      // may lose interesting information.
      LOG("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id);
 
      throw exn;
    }
--- a/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
@@ -36,16 +36,17 @@ if (typeof Components != "undefined") {
 }
 
 let LOG = SharedAll.LOG.bind(SharedAll, "Unix", "allthreads");
 let Const = SharedAll.Constants.libc;
 
 // Open libc
 let libc;
 let libc_candidates =  [ "libc.so",
+                         "libSystem.B.dylib",
                          "a.out" ];
 for (let i = 0; i < libc_candidates.length; ++i) {
   try {
     libc = ctypes.open(libc_candidates[i]);
     break;
   } catch (x) {
     LOG("Could not open libc ", libc_candidates[i]);
   }
--- a/toolkit/components/osfile/tests/xpcshell/test_exception.js
+++ b/toolkit/components/osfile/tests/xpcshell/test_exception.js
@@ -14,16 +14,14 @@ add_task(function test_typeerror() {
   let exn;
   try {
     let fd = yield OS.File.open("/tmp", {no_such_key: 1});
     do_print("Fd: " + fd);
   } catch (ex) {
     exn = ex;
   }
   do_print("Exception: " + exn);
-  do_check_true(typeof exn == "object");
-  do_check_true("name" in exn);
-  do_check_true(exn.message.indexOf("TypeError") != -1);
+  do_check_true(exn.constructor.name == "TypeError");
 });
 
 add_task(function() {
   do_test_finished();
 });
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -92,16 +92,17 @@ skip-if = os == "win" # Intermittent fai
 [test_findbar.xul]
 [test_findbar_events.xul]
 [test_focus_anons.xul]
 [test_hiddenitems.xul]
 [test_hiddenpaging.xul]
 [test_keys.xul]
 [test_largemenu.xul]
 [test_menu.xul]
+[test_menu_anchored.xul]
 [test_menu_hide.xul]
 [test_menuchecks.xul]
 [test_menuitem_blink.xul]
 [test_menuitem_commands.xul]
 [test_menulist.xul]
 [test_menulist_keynav.xul]
 [test_menulist_null_value.xul]
 [test_mousecapture.xul]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/test_menu_anchored.xul
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+  Test for menus with the anchor attribute set
+  -->
+<window title="Anchored Menus Test"
+        align="start"
+        onload="setTimeout(runTest, 0,'tb1');"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="xul_selectcontrol.js"/>
+
+<hbox>
+
+<toolbarbutton id="tb1" type="menu-button" label="Open" anchor="dropmarker">
+  <menupopup id="popup1"
+             onpopupshown="checkPopup(this, document.getAnonymousElementByAttribute(this.parentNode, 'anonid', 'dropmarker'))"
+             onpopuphidden="runTest('tb2')">
+    <menuitem label="Item"/>
+  </menupopup>
+</toolbarbutton>
+
+<toolbarbutton id="tb2" type="menu-button" label="Open" anchor="someanchor">
+  <menupopup id="popup2" onpopupshown="checkPopup(this, $('someanchor'))" onpopuphidden="runTest('tb3')">
+    <menuitem label="Item"/>
+  </menupopup>
+</toolbarbutton>
+
+<toolbarbutton id="tb3" type="menu-button" label="Open" anchor="noexist">
+  <menupopup id="popup3" onpopupshown="checkPopup(this, this.parentNode)" onpopuphidden="SimpleTest.finish()">
+    <menuitem label="Item"/>
+  </menupopup>
+</toolbarbutton>
+
+</hbox>
+
+<hbox pack="end" width="180">
+  <button id="someanchor" label="Anchor"/>
+</hbox>
+
+<!-- test results are displayed in the html:body -->
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script type="application/javascript"><![CDATA[
+
+function runTest(menuid)
+{
+  let menu = $(menuid);
+  let dropmarker = document.getAnonymousElementByAttribute(menu, "anonid", "dropmarker");
+
+  synthesizeMouseAtCenter(dropmarker, { });
+}
+
+function isWithinHalfPixel(a, b)
+{
+  return Math.abs(a - b) <= 0.5;
+}
+
+function checkPopup(popup, anchor)
+{
+  let popupRect = popup.getBoundingClientRect();
+  let anchorRect = anchor.getBoundingClientRect();
+
+  ok(isWithinHalfPixel(popupRect.left, anchorRect.left), popup.id + " left");
+  ok(isWithinHalfPixel(popupRect.top, anchorRect.bottom), popup.id + " top");
+
+  popup.hidePopup();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+]]>
+</script>
+
+</window>
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -309,26 +309,26 @@
           }
         }
       ]]></handler>
     </handlers>
   </binding>
 
   <binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
     <content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
-      <xul:box anonid="container" class="panel-arrowcontainer" flex="1"
+      <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
                xbl:inherits="side,panelopen">
         <xul:box anonid="arrowbox" class="panel-arrowbox">
           <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
         </xul:box>
         <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
           <children/>
           <xul:box class="panel-inner-arrowcontentfooter" xbl:inherits="footertype" hidden="true"/>
         </xul:box>
-      </xul:box>
+      </xul:vbox>
     </content>
     <implementation>
       <field name="_fadeTimer">null</field>
       <method name="sizeTo">
         <parameter name="aWidth"/>
         <parameter name="aHeight"/>
         <body>
         <![CDATA[
@@ -377,17 +377,17 @@
         var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
         var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
 
         var position = this.alignmentPosition;
         var offset = this.alignmentOffset;
         // if this panel has a "sliding" arrow, we may have previously set margins...
         arrowbox.style.removeProperty("transform");
         if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
-          container.orient = "";
+          container.orient = "horizontal";
           arrowbox.orient = "vertical";
           if (position.indexOf("_after") > 0) {
             arrowbox.pack = "end";
           } else {
             arrowbox.pack = "start";
           }
           arrowbox.style.transform = "translate(0, " + -offset + "px)";
 
@@ -399,17 +399,17 @@
             this.setAttribute("side", isRTL ? "left" : "right");
           }
           else {
             container.dir = "";
             this.setAttribute("side", isRTL ? "right" : "left");
           }
         }
         else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
-          container.orient = "vertical";
+          container.orient = "";
           arrowbox.orient = "";
           if (position.indexOf("_end") > 0) {
             arrowbox.pack = "end";
           } else {
             arrowbox.pack = "start";
           }
           arrowbox.style.transform = "translate(" + -offset + "px, 0)";
 
--- a/toolkit/content/widgets/toolbarbutton.xml
+++ b/toolkit/content/widgets/toolbarbutton.xml
@@ -29,33 +29,35 @@
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,dragover-top"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
                  xbl:inherits="xbl:text=label,accesskey"/>
-      <xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
+      <xul:dropmarker anonid="dropmarker" type="menu"
+                      class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
     </content>
   </binding>
   
   <binding id="menu-vertical" display="xul:menu"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:hbox flex="1" align="center">
         <xul:vbox flex="1" align="center">
           <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
           <xul:label class="toolbarbutton-text" crop="right" flex="1"
                      xbl:inherits="value=label,accesskey,crop,dragover-top"/>
           <xul:label class="toolbarbutton-multiline-text" flex="1"
                      xbl:inherits="xbl:text=label,accesskey"/>
         </xul:vbox>
-        <xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
+        <xul:dropmarker anonid="dropmarker" type="menu"
+                        class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
       </xul:hbox>
     </content>
   </binding>
   
   <binding id="menu-button" display="xul:menu" 
            extends="chrome://global/content/bindings/button.xml#menu-button-base">
     <resources>
       <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
@@ -63,17 +65,17 @@
 
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:toolbarbutton class="box-inherit toolbarbutton-menubutton-button"
                          anonid="button" flex="1" allowevents="true"
                          xbl:inherits="disabled,crop,image,label,accesskey,command,wrap,
                                        align,dir,pack,orient,tooltiptext=buttontooltiptext"/>
       <xul:dropmarker type="menu-button" class="toolbarbutton-menubutton-dropmarker"
-                      xbl:inherits="align,dir,pack,orient,disabled,label,open"/>
+                      anonid="dropmarker" xbl:inherits="align,dir,pack,orient,disabled,label,open"/>
     </content>
   </binding>
 
   <binding id="toolbarbutton-image"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
       <xul:image class="toolbarbutton-icon" xbl:inherits="src=image"/>
     </content>
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -24,17 +24,17 @@ this.safeErrorString = function safeErro
           let stack = aError.stack.toString();
           if (typeof stack == "string") {
             errorString += "\nStack: " + stack;
           }
         }
       } catch (ee) { }
 
       if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") {
-        errorString += ", line: " + aError.lineNumber + ", column: " + aError.columnNumber;
+        errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber;
       }
 
       return errorString;
     }
   } catch (ee) { }
 
   return "<failed trying to find error description>";
 }
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -60,16 +60,48 @@ const object = require("sdk/util/object"
 const events = require("sdk/event/core");
 const {Unknown} = require("sdk/platform/xpcom");
 const {Class} = require("sdk/core/heritage");
 const {PageStyleActor} = require("devtools/server/actors/styles");
 const {HighlighterActor} = require("devtools/server/actors/highlighter");
 
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
+// The possible completions to a ':' with added score to give certain values
+// some preference.
+const PSEUDO_SELECTORS = [
+  [":active", 1],
+  [":hover", 1],
+  [":focus", 1],
+  [":visited", 0],
+  [":link", 0],
+  [":first-letter", 0],
+  [":first-child", 2],
+  [":before", 2],
+  [":after", 2],
+  [":lang(", 0],
+  [":not(", 3],
+  [":first-of-type", 0],
+  [":last-of-type", 0],
+  [":only-of-type", 0],
+  [":only-child", 2],
+  [":nth-child(", 3],
+  [":nth-last-child(", 0],
+  [":nth-of-type(", 0],
+  [":nth-last-of-type(", 0],
+  [":last-child", 2],
+  [":root", 0],
+  [":empty", 0],
+  [":target", 0],
+  [":enabled", 0],
+  [":disabled", 0],
+  [":checked", 1],
+  ["::selection", 0]
+];
+
 
 let HELPER_SHEET = ".__fx-devtools-hide-shortcut__ { visibility: hidden !important } ";
 HELPER_SHEET += ":-moz-devtools-highlighted { outline: 2px dashed #F06!important; outline-offset: -2px!important } ";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
 
 loader.lazyGetter(this, "DOMParser", function() {
@@ -1398,16 +1430,146 @@ var WalkerActor = protocol.ActorClass({
       selector: Arg(1)
     },
     response: {
       list: RetVal("domnodelist")
     }
   }),
 
   /**
+   * Returns a list of matching results for CSS selector autocompletion.
+   *
+   * @param string query
+   *        The selector query being completed
+   * @param string completing
+   *        The exact token being completed out of the query
+   * @param string selectorState
+   *        One of "pseudo", "id", "tag", "class", "null"
+   */
+  getSuggestionsForQuery: method(function(query, completing, selectorState) {
+    let sugs = {
+      classes: new Map,
+      tags: new Map
+    };
+    let result = [];
+    let nodes = null;
+    // Filtering and sorting the results so that protocol transfer is miminal.
+    switch (selectorState) {
+      case "pseudo":
+        result = PSEUDO_SELECTORS.filter(item => {
+          return item[0].startsWith(":" + completing);
+        });
+        break;
+
+      case "class":
+        if (!query) {
+          nodes = this.rootDoc.querySelectorAll("[class]");
+        }
+        else {
+          nodes = this.rootDoc.querySelectorAll(query);
+        }
+        for (let node of nodes) {
+          for (let className of node.className.split(" ")) {
+            sugs.classes.set(className, (sugs.classes.get(className)|0) + 1);
+          }
+        }
+        sugs.classes.delete("");
+        // Editing the style editor may make the stylesheet have errors and
+        // thus the page's elements' styles start changing with a transition.
+        // That transition comes from the `moz-styleeditor-transitioning` class.
+        sugs.classes.delete("moz-styleeditor-transitioning");
+        sugs.classes.delete(HIDDEN_CLASS);
+        for (let [className, count] of sugs.classes) {
+          if (className.startsWith(completing)) {
+            result.push(["." + className, count]);
+          }
+        }
+        break;
+
+      case "id":
+        if (!query) {
+          nodes = this.rootDoc.querySelectorAll("[id]");
+        }
+        else {
+          nodes = this.rootDoc.querySelectorAll(query);
+        }
+        for (let node of nodes) {
+          if (node.id.startsWith(completing)) {
+            result.push(["#" + node.id, 1]);
+          }
+        }
+        break;
+
+      case "tag":
+        if (!query) {
+          nodes = this.rootDoc.getElementsByTagName("*");
+        }
+        else {
+          nodes = this.rootDoc.querySelectorAll(query);
+        }
+        for (let node of nodes) {
+          let tag = node.tagName.toLowerCase();
+          sugs.tags.set(tag, (sugs.tags.get(tag)|0) + 1);
+        }
+        for (let [tag, count] of sugs.tags) {
+          if ((new RegExp("^" + completing + ".*", "i")).test(tag)) {
+            result.push([tag, count]);
+          }
+        }
+        break;
+
+      case "null":
+        nodes = this.rootDoc.querySelectorAll(query);
+        for (let node of nodes) {
+          node.id && result.push(["#" + node.id, 1]);
+          let tag = node.tagName.toLowerCase();
+          sugs.tags.set(tag, (sugs.tags.get(tag)|0) + 1);
+          for (let className of node.className.split(" ")) {
+            sugs.classes.set(className, (sugs.classes.get(className)|0) + 1);
+          }
+        }
+        for (let [tag, count] of sugs.tags) {
+          tag && result.push([tag, count]);
+        }
+        sugs.classes.delete("");
+        // Editing the style editor may make the stylesheet have errors and
+        // thus the page's elements' styles start changing with a transition.
+        // That transition comes from the `moz-styleeditor-transitioning` class.
+        sugs.classes.delete("moz-styleeditor-transitioning");
+        sugs.classes.delete(HIDDEN_CLASS);
+        for (let [className, count] of sugs.classes) {
+          className && result.push(["." + className, count]);
+        }
+    }
+
+    // Sort alphabetically in increaseing order.
+    result = result.sort();
+    // Sort based on count in decreasing order.
+    result = result.sort(function(a, b) {
+      return b[1] - a[1];
+    });
+
+    result.slice(0, 25);
+
+    return {
+      query: query,
+      suggestions: result
+    };
+  }, {
+    request: {
+      query: Arg(0),
+      completing: Arg(1),
+      selectorState: Arg(2)
+    },
+    response: {
+      list: RetVal("array:array:string")
+    }
+  }),
+
+  /**
    * Add a pseudo-class lock to a node.
    *
    * @param NodeActor node
    * @param string pseudo
    *    A pseudoclass: ':hover', ':active', ':focus'
    * @param options
    *    Options object:
    *    `parents`: True if the pseudo-class should be added
--- a/toolkit/devtools/tests/unit/test_safeErrorString.js
+++ b/toolkit/devtools/tests/unit/test_safeErrorString.js
@@ -15,17 +15,17 @@ function run_test() {
 function test_with_error() {
   let s = DevToolsUtils.safeErrorString(new Error("foo bar"));
   // Got the message.
   do_check_true(s.contains("foo bar"));
   // Got the stack.
   do_check_true(s.contains("test_with_error"))
   do_check_true(s.contains("test_safeErrorString.js"));
   // Got the lineNumber and columnNumber.
-  do_check_true(s.contains("line"));
+  do_check_true(s.contains("Line"));
   do_check_true(s.contains("column"));
 }
 
 function test_with_tricky_error() {
   let e = new Error("batman");
   e.stack = { toString: Object.create(null) };
   let s = DevToolsUtils.safeErrorString(e);
   // Still got the message, despite a bad stack property.