merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 30 Sep 2016 12:00:25 +0200
changeset 315814 59db3b0781908d9e2145477ff8dc5a85729e25bf
parent 315813 6f31b67533799bd84fe3b207681bcef2c78dac47 (current diff)
parent 315737 1723fbc9d9d88a5fbf1c57cdd1e226e36f04c522 (diff)
child 315970 5ffed033557e5b6f9694123f1948f867f913ede3
push id20634
push usercbook@mozilla.com
push dateFri, 30 Sep 2016 10:10:13 +0000
treeherderfx-team@afe79b010d13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
merge fx-team to mozilla-central a=merge
devtools/client/inspector/inspector-panel.js
--- a/devtools/client/animationinspector/animation-controller.js
+++ b/devtools/client/animationinspector/animation-controller.js
@@ -137,17 +137,17 @@ var AnimationsController = {
       return;
     }
     this.initialized = promise.defer();
 
     this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this);
     this.onNewNodeFront = this.onNewNodeFront.bind(this);
     this.onAnimationMutations = this.onAnimationMutations.bind(this);
 
-    let target = gToolbox.target;
+    let target = gInspector.target;
     this.animationsFront = new AnimationsFront(target.client, target.form);
 
     // Expose actor capabilities.
     this.traits = yield getServerTraits(target);
 
     if (this.destroyed) {
       console.warn("Could not fully initialize the AnimationsController");
       return;
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const Services = require("Services");
 const osString = Services.appinfo.OS;
 
 // Panels
 loader.lazyGetter(this, "OptionsPanel", () => require("devtools/client/framework/toolbox-options").OptionsPanel);
-loader.lazyGetter(this, "InspectorPanel", () => require("devtools/client/inspector/inspector-panel").InspectorPanel);
+loader.lazyGetter(this, "InspectorPanel", () => require("devtools/client/inspector/panel").InspectorPanel);
 loader.lazyGetter(this, "WebConsolePanel", () => require("devtools/client/webconsole/panel").WebConsolePanel);
 loader.lazyGetter(this, "DebuggerPanel", () => require("devtools/client/debugger/panel").DebuggerPanel);
 loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/client/styleeditor/styleeditor-panel").StyleEditorPanel);
 loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/client/shadereditor/panel").ShaderEditorPanel);
 loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/client/canvasdebugger/panel").CanvasDebuggerPanel);
 loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/client/webaudioeditor/panel").WebAudioEditorPanel);
 loader.lazyGetter(this, "MemoryPanel", () => require("devtools/client/memory/panel").MemoryPanel);
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
--- a/devtools/client/framework/test/browser_toolbox_textbox_context_menu.js
+++ b/devtools/client/framework/test/browser_toolbox_textbox_context_menu.js
@@ -2,17 +2,17 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const URL = "data:text/html;charset=utf8,test for textbox context menu";
 
 add_task(function* () {
   let toolbox = yield openNewTabAndToolbox(URL, "inspector");
-  let textboxContextMenu = toolbox.textboxContextMenuPopup;
+  let textboxContextMenu = toolbox.textBoxContextMenuPopup;
 
   emptyClipboard();
 
   // Make sure the focus is predictable.
   let inspector = toolbox.getPanel("inspector");
   let onFocus = once(inspector.searchBox, "focus");
   inspector.searchBox.focus();
   yield onFocus;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -132,17 +132,17 @@ function Toolbox(target, selectedTool, h
   this.destroy = this.destroy.bind(this);
   this.highlighterUtils = getHighlighterUtils(this);
   this._highlighterReady = this._highlighterReady.bind(this);
   this._highlighterHidden = this._highlighterHidden.bind(this);
   this._prefChanged = this._prefChanged.bind(this);
   this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
   this._onFocus = this._onFocus.bind(this);
   this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
-  this._updateTextboxMenuItems = this._updateTextboxMenuItems.bind(this);
+  this._updateTextBoxMenuItems = this._updateTextBoxMenuItems.bind(this);
   this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
   this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
   this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
   this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
   this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
   this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
   this._onTabbarFocus = this._onTabbarFocus.bind(this);
   this._onTabbarArrowKeypress = this._onTabbarArrowKeypress.bind(this);
@@ -397,20 +397,20 @@ Toolbox.prototype = {
       gDevTools.on("pref-changed", this._prefChanged);
 
       let framesMenu = this.doc.getElementById("command-button-frames");
       framesMenu.addEventListener("click", this.showFramesMenu, false);
 
       let noautohideMenu = this.doc.getElementById("command-button-noautohide");
       noautohideMenu.addEventListener("click", this._toggleAutohide, true);
 
-      this.textboxContextMenuPopup =
+      this.textBoxContextMenuPopup =
         this.doc.getElementById("toolbox-textbox-context-popup");
-      this.textboxContextMenuPopup.addEventListener("popupshowing",
-        this._updateTextboxMenuItems, true);
+      this.textBoxContextMenuPopup.addEventListener("popupshowing",
+        this._updateTextBoxMenuItems, true);
 
       this.shortcuts = new KeyShortcuts({
         window: this.doc.defaultView
       });
       this._buildDockButtons();
       this._buildOptions();
       this._buildTabs();
       this._applyCacheSettings();
@@ -2091,20 +2091,20 @@ Toolbox.prototype = {
       this.webconsolePanel.removeEventListener("resize",
         this._saveSplitConsoleHeight);
       this.webconsolePanel = null;
     }
     if (this.closeButton) {
       this.closeButton.removeEventListener("click", this.destroy, true);
       this.closeButton = null;
     }
-    if (this.textboxContextMenuPopup) {
-      this.textboxContextMenuPopup.removeEventListener("popupshowing",
-        this._updateTextboxMenuItems, true);
-      this.textboxContextMenuPopup = null;
+    if (this.textBoxContextMenuPopup) {
+      this.textBoxContextMenuPopup.removeEventListener("popupshowing",
+        this._updateTextBoxMenuItems, true);
+      this.textBoxContextMenuPopup = null;
     }
     if (this.tabbar) {
       this.tabbar.removeEventListener("focus", this._onTabbarFocus, true);
       this.tabbar.removeEventListener("click", this._onTabbarFocus, true);
       this.tabbar.removeEventListener("keypress", this._onTabbarArrowKeypress);
       this.tabbar = null;
     }
 
@@ -2230,23 +2230,34 @@ Toolbox.prototype = {
       return;
     }
     showDoorhanger({ window: this.win, type: "deveditionpromo" });
   },
 
   /**
    * Enable / disable necessary textbox menu items using globalOverlay.js.
    */
-  _updateTextboxMenuItems: function () {
+  _updateTextBoxMenuItems: function () {
     let window = this.win;
     ["cmd_undo", "cmd_delete", "cmd_cut",
      "cmd_copy", "cmd_paste", "cmd_selectAll"].forEach(window.goUpdateCommand);
   },
 
   /**
+   * Open the textbox context menu at given coordinates.
+   * Panels in the toolbox can call this on contextmenu events with event.screenX/Y
+   * instead of having to implement their own copy/paste/selectAll menu.
+   * @param {Number} x
+   * @param {Number} y
+   */
+  openTextBoxContextMenu: function (x, y) {
+    this.textBoxContextMenuPopup.openPopupAtScreen(x, y, true);
+  },
+
+  /**
    * Connects to the SPS profiler when the developer tools are open. This is
    * necessary because of the WebConsole's `profile` and `profileEnd` methods.
    */
   initPerformance: Task.async(function* () {
     // If target does not have profiler actor (addons), do not
     // even register the shared performance connection.
     if (!this.target.hasActor("profiler")) {
       return promise.resolve();
--- a/devtools/client/inspector/components/box-model.js
+++ b/devtools/client/inspector/components/box-model.js
@@ -336,20 +336,20 @@ BoxModelView.prototype = {
     }
   },
 
   /**
    * Start listening to reflows in the current tab.
    */
   trackReflows: function () {
     if (!this.reflowFront) {
-      let toolbox = this.inspector.toolbox;
-      if (toolbox.target.form.reflowActor) {
-        this.reflowFront = ReflowFront(toolbox.target.client,
-                                       toolbox.target.form);
+      let { target } = this.inspector;
+      if (target.form.reflowActor) {
+        this.reflowFront = ReflowFront(target.client,
+                                       target.form);
       } else {
         return;
       }
     }
 
     this.reflowFront.on("reflows", this.update);
     this.reflowFront.start();
   },
@@ -379,17 +379,16 @@ BoxModelView.prototype = {
       initial: initialValue,
       contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
       property: {
         name: dimension.property
       },
       start: self => {
         self.elt.parentNode.classList.add("boxmodel-editing");
       },
-
       change: value => {
         if (NUMERIC.test(value)) {
           value += "px";
         }
 
         let properties = [
           { name: property, value: value }
         ];
@@ -399,25 +398,25 @@ BoxModelView.prototype = {
           let style = session.getProperty(bprop);
           if (!style || style == "none" || style == "hidden") {
             properties.push({ name: bprop, value: "solid" });
           }
         }
 
         session.setProperties(properties).catch(e => console.error(e));
       },
-
       done: (value, commit) => {
         editor.elt.parentNode.classList.remove("boxmodel-editing");
         if (!commit) {
           session.revert().then(() => {
             session.destroy();
           }, e => console.error(e));
         }
       },
+      contextMenu: this.inspector.onTextBoxContextMenu,
       cssProperties: this._cssProperties
     }, event);
   },
 
   /**
    * Is the BoxModelView visible in the sidebar.
    * @return {Boolean}
    */
@@ -463,17 +462,17 @@ BoxModelView.prototype = {
     if (this.inspector.markup) {
       this.inspector.markup.off("leave", this.onMarkupViewLeave);
       this.inspector.markup.off("node-hover", this.onMarkupViewNodeHover);
     }
 
     this.inspector.sidebar.off("computedview-selected", this.onNewNode);
     this.inspector.selection.off("new-node-front", this.onNewSelection);
     this.inspector.sidebar.off("select", this.onSidebarSelect);
-    this.inspector._target.off("will-navigate", this.onWillNavigate);
+    this.inspector.target.off("will-navigate", this.onWillNavigate);
     this.inspector.off("computed-view-filtered", this.onFilterComputedView);
 
     this.inspector = null;
     this.doc = null;
     this.wrapper = null;
     this.container = null;
     this.expander = null;
     this.sizeLabel = null;
@@ -787,17 +786,17 @@ BoxModelView.prototype = {
         // Hide completely the geometry editor if the picker is clicked
         toolbox.on("picker-started", this.onPickerStarted);
 
         // Temporary hide the geometry editor
         this.inspector.markup.on("leave", this.onMarkupViewLeave);
         this.inspector.markup.on("node-hover", this.onMarkupViewNodeHover);
 
         // Release the actor on will-navigate event
-        this.inspector._target.once("will-navigate", this.onWillNavigate);
+        this.inspector.target.once("will-navigate", this.onWillNavigate);
       });
   },
 
   /**
    * Hide the geometry editor highlighter on the currently selected element
    * @param {Boolean} [updateButton=true]
    *   Indicates if the Geometry Editor's button needs to be unchecked too
    */
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -143,18 +143,16 @@ function CssComputedView(inspector, docu
   // Create bound methods.
   this.focusWindow = this.focusWindow.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._onClick = this._onClick.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onFilterStyles = this._onFilterStyles.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
   this._onIncludeBrowserStyles = this._onIncludeBrowserStyles.bind(this);
-  this._onFilterTextboxContextMenu =
-    this._onFilterTextboxContextMenu.bind(this);
 
   let doc = this.styleDocument;
   this.element = doc.getElementById("propertyContainer");
   this.searchField = doc.getElementById("computedview-searchbox");
   this.searchClearButton = doc.getElementById("computedview-searchinput-clear");
   this.includeBrowserStylesCheckbox =
     doc.getElementById("browser-style-checkbox");
 
@@ -162,18 +160,17 @@ function CssComputedView(inspector, docu
   this._onShortcut = this._onShortcut.bind(this);
   this.shortcuts.on("CmdOrCtrl+F", this._onShortcut);
   this.shortcuts.on("Escape", this._onShortcut);
   this.styleDocument.addEventListener("mousedown", this.focusWindow);
   this.element.addEventListener("click", this._onClick);
   this.element.addEventListener("copy", this._onCopy);
   this.element.addEventListener("contextmenu", this._onContextMenu);
   this.searchField.addEventListener("input", this._onFilterStyles);
-  this.searchField.addEventListener("contextmenu",
-                                    this._onFilterTextboxContextMenu);
+  this.searchField.addEventListener("contextmenu", this.inspector.onTextBoxContextMenu);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
   this.includeBrowserStylesCheckbox.addEventListener("input",
     this._onIncludeBrowserStyles);
 
   this.searchClearButton.hidden = true;
 
   // No results text.
   this.noResults = this.styleDocument.getElementById("computedview-no-results");
@@ -542,29 +539,16 @@ CssComputedView.prototype = {
       }
 
       this.refreshPanel();
       this._filterChangeTimeout = null;
     }, filterTimeout);
   },
 
   /**
-   * Context menu handler for filter style search box.
-   */
-  _onFilterTextboxContextMenu: function (event) {
-    try {
-      this.styleDocument.defaultView.focus();
-      let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
-      contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
-    } catch (e) {
-      console.error(e);
-    }
-  },
-
-  /**
    * Called when the user clicks on the clear button in the filter style search
    * box. Returns true if the search box is cleared and false otherwise.
    */
   _onClearSearch: function () {
     if (this.searchField.value) {
       this.setFilterStyles("");
       return true;
     }
@@ -649,18 +633,17 @@ CssComputedView.prototype = {
   get matchedProperties() {
     return this._matchedProperties || new Set();
   },
 
   /**
    * Focus the window on mousedown.
    */
   focusWindow: function () {
-    let win = this.styleDocument.defaultView;
-    win.focus();
+    this.styleWindow.focus();
   },
 
   /**
    * Context menu handler.
    */
   _onContextMenu: function (event) {
     this._contextmenu.show(event);
   },
@@ -687,17 +670,17 @@ CssComputedView.prototype = {
     event.preventDefault();
   },
 
   /**
    * Copy the current selection to the clipboard
    */
   copySelection: function () {
     try {
-      let win = this.styleDocument.defaultView;
+      let win = this.styleWindow;
       let text = win.getSelection().toString().trim();
 
       // Tidy up block headings by moving CSS property names and their
       // values onto the same line and inserting a colon between them.
       let textArray = text.split(/[\r\n]+/);
       let result = "";
 
       // Parse text array to output string.
@@ -753,17 +736,17 @@ CssComputedView.prototype = {
 
     // Remove bound listeners
     this.styleDocument.removeEventListener("mousedown", this.focusWindow);
     this.element.removeEventListener("click", this._onClick);
     this.element.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.searchField.removeEventListener("input", this._onFilterStyles);
     this.searchField.removeEventListener("contextmenu",
-                                         this._onFilterTextboxContextMenu);
+      this.inspector.onTextBoxContextMenu);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
     this.includeBrowserStylesCheckbox.removeEventListener("input",
       this._onIncludeBrowserStyles);
 
     // Nodes used in templating
     this.element = null;
     this.panel = null;
     this.searchField = null;
--- a/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js
+++ b/devtools/client/inspector/computed/test/browser_computed_search-filter_context-menu.js
@@ -12,17 +12,17 @@ const TEST_URI = "<h1>test filter contex
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {toolbox, inspector, view} = yield openComputedView();
   yield selectNode("h1", inspector);
 
   let win = view.styleWindow;
   let searchField = view.searchField;
-  let searchContextMenu = toolbox.textboxContextMenuPopup;
+  let searchContextMenu = toolbox.textBoxContextMenuPopup;
   ok(searchContextMenu,
     "The search filter context menu is loaded in the computed view");
 
   let cmdUndo = searchContextMenu.querySelector("[command=cmd_undo]");
   let cmdDelete = searchContextMenu.querySelector("[command=cmd_delete]");
   let cmdSelectAll = searchContextMenu.querySelector("[command=cmd_selectAll]");
   let cmdCut = searchContextMenu.querySelector("[command=cmd_cut]");
   let cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]");
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -27,19 +27,20 @@ FontInspector.prototype = {
     this.onNewNode = this.onNewNode.bind(this);
     this.onThemeChanged = this.onThemeChanged.bind(this);
     this.inspector.selection.on("new-node-front", this.onNewNode);
     this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
     this.showAll = this.showAll.bind(this);
     this.showAllLink = this.chromeDoc.getElementById("font-showall");
     this.showAllLink.addEventListener("click", this.showAll);
     this.previewTextChanged = this.previewTextChanged.bind(this);
-    this.previewInput =
-      this.chromeDoc.getElementById("font-preview-text-input");
+    this.previewInput = this.chromeDoc.getElementById("font-preview-text-input");
     this.previewInput.addEventListener("input", this.previewTextChanged);
+    this.previewInput.addEventListener("contextmenu",
+      this.inspector.onTextBoxContextMenu);
 
     // Listen for theme changes as the color of the previews depend on the theme
     gDevTools.on("theme-switched", this.onThemeChanged);
 
     this.update();
   },
 
   /**
@@ -54,16 +55,18 @@ FontInspector.prototype = {
    * Remove listeners.
    */
   destroy: function () {
     this.chromeDoc = null;
     this.inspector.sidebar.off("fontinspector-selected", this.onNewNode);
     this.inspector.selection.off("new-node-front", this.onNewNode);
     this.showAllLink.removeEventListener("click", this.showAll);
     this.previewInput.removeEventListener("input", this.previewTextChanged);
+    this.previewInput.removeEventListener("contextmenu",
+      this.inspector.onTextBoxContextMenu);
 
     gDevTools.off("theme-switched", this.onThemeChanged);
 
     if (this._previewUpdateTimeout) {
       clearTimeout(this._previewUpdateTimeout);
     }
   },
 
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -37,22 +37,19 @@ function InspectorSearch(inspector, inpu
   this.searchClearButton = clearBtn;
   this._lastSearched = null;
 
   this.searchClearButton.hidden = true;
 
   this._onKeyDown = this._onKeyDown.bind(this);
   this._onInput = this._onInput.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
-  this._onFilterTextboxContextMenu =
-    this._onFilterTextboxContextMenu.bind(this);
   this.searchBox.addEventListener("keydown", this._onKeyDown, true);
   this.searchBox.addEventListener("input", this._onInput, true);
-  this.searchBox.addEventListener("contextmenu",
-    this._onFilterTextboxContextMenu);
+  this.searchBox.addEventListener("contextmenu", this.inspector.onTextBoxContextMenu);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
 
   // For testing, we need to be able to wait for the most recent node request
   // to finish.  Tests can watch this promise for that.
   this._lastQuery = promise.resolve(null);
 
   this.autocompleter = new SelectorAutocompleter(inspector, input);
   EventEmitter.decorate(this);
@@ -64,17 +61,17 @@ InspectorSearch.prototype = {
   get walker() {
     return this.inspector.walker;
   },
 
   destroy: function () {
     this.searchBox.removeEventListener("keydown", this._onKeyDown, true);
     this.searchBox.removeEventListener("input", this._onInput, true);
     this.searchBox.removeEventListener("contextmenu",
-      this._onFilterTextboxContextMenu);
+      this.inspector.onTextBoxContextMenu);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
     this.searchBox = null;
     this.searchClearButton = null;
     this.autocompleter.destroy();
   },
 
   _onSearch: function (reverse = false) {
     this.doFullTextSearch(this.searchBox.value, reverse)
@@ -131,28 +128,16 @@ InspectorSearch.prototype = {
     const modifierKey = Services.appinfo.OS === "Darwin"
                         ? event.metaKey : event.ctrlKey;
     if (event.keyCode === KeyCodes.DOM_VK_G && modifierKey) {
       this._onSearch(event.shiftKey);
       event.preventDefault();
     }
   },
 
-  /**
-   * Context menu handler for filter search box.
-   */
-  _onFilterTextboxContextMenu: function (event) {
-    try {
-      let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
-      contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
-    } catch (e) {
-      console.error(e);
-    }
-  },
-
   _onClearSearch: function () {
     this.searchBox.classList.remove("devtools-style-searchbox-no-match");
     this.searchBox.value = "";
     this.searchClearButton.hidden = true;
     this.emit("search-cleared");
   }
 };
 
rename from devtools/client/inspector/inspector-panel.js
rename to devtools/client/inspector/inspector.js
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector.js
@@ -1,16 +1,20 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 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/. */
 
+/* global window */
+
 "use strict";
 
+var Cu = Components.utils;
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var EventEmitter = require("devtools/shared/event-emitter");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 var {Task} = require("devtools/shared/task");
 const {initCssProperties} = require("devtools/shared/fronts/css-properties");
@@ -76,54 +80,53 @@ const PORTRAIT_MODE_WIDTH = 700;
  * - computed-view-filtered
  *      Fired when the computed rules view is filtered
  * - rule-view-refreshed
  *      Fired when the rule view updates to a new node
  * - rule-view-sourcelinks-updated
  *      Fired when the stylesheet source links have been updated (when switching
  *      to source-mapped files)
  */
-function InspectorPanel(iframeWindow, toolbox) {
+function Inspector(toolbox) {
   this._toolbox = toolbox;
   this._target = toolbox.target;
-  this.panelDoc = iframeWindow.document;
-  this.panelWin = iframeWindow;
+  this.panelDoc = window.document;
+  this.panelWin = window;
   this.panelWin.inspector = this;
 
   this.telemetry = new Telemetry();
 
   this.nodeMenuTriggerInfo = null;
 
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this.onNewRoot = this.onNewRoot.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
+  this.onTextBoxContextMenu = this.onTextBoxContextMenu.bind(this);
   this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
   this.onNewSelection = this.onNewSelection.bind(this);
   this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
   this.onDetached = this.onDetached.bind(this);
   this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
   this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
   this.onPanelWindowResize = this.onPanelWindowResize.bind(this);
   this.onSidebarShown = this.onSidebarShown.bind(this);
   this.onSidebarHidden = this.onSidebarHidden.bind(this);
 
   this._target.on("will-navigate", this._onBeforeNavigate);
   this._detectingActorFeatures = this._detectActorFeatures();
 
   EventEmitter.decorate(this);
 }
 
-exports.InspectorPanel = InspectorPanel;
-
-InspectorPanel.prototype = {
+Inspector.prototype = {
   /**
    * open is effectively an asynchronous constructor
    */
-  open: Task.async(function* () {
+  init: Task.async(function* () {
     // Localize all the nodes containing a data-localization attribute.
     localizeMarkup(this.panelDoc);
 
     this._cssPropertiesLoaded = initCssProperties(this.toolbox);
     yield this._cssPropertiesLoaded;
     yield this.target.makeRemote();
     yield this._getPageStyle();
     let defaultSelection = yield this._getDefaultNodeForSelection();
@@ -141,16 +144,20 @@ InspectorPanel.prototype = {
   get walker() {
     return this._toolbox.walker;
   },
 
   get selection() {
     return this._toolbox.selection;
   },
 
+  get highlighter() {
+    return this._toolbox.highlighter;
+  },
+
   get isOuterHTMLEditable() {
     return this._target.client.traits.editOuterHTML;
   },
 
   get hasUrlToImageDataResolver() {
     return this._target.client.traits.urlToImageDataResolver;
   },
 
@@ -270,17 +277,17 @@ InspectorPanel.prototype = {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
     this.isDirty = false;
     this._pendingSelection = null;
   },
 
   _getPageStyle: function () {
-    return this._toolbox.inspector.getPageStyle().then(pageStyle => {
+    return this.inspector.getPageStyle().then(pageStyle => {
       this.pageStyle = pageStyle;
     }, this._handleRejectionIfNotDestroyed);
   },
 
   /**
    * Return a promise that will resolve to the default node for selection.
    */
   _getDefaultNodeForSelection: function () {
@@ -436,18 +443,16 @@ InspectorPanel.prototype = {
   /**
    * Build Splitter located between the main and side area of
    * the Inspector panel.
    */
   setupSplitter: function () {
     let SplitBox = this.React.createFactory(this.browserRequire(
       "devtools/client/shared/components/splitter/split-box"));
 
-    this.panelWin.addEventListener("resize", this.onPanelWindowResize, true);
-
     let splitter = SplitBox({
       className: "inspector-sidebar-splitter",
       initialWidth: INITIAL_SIDEBAR_SIZE,
       initialHeight: INITIAL_SIDEBAR_SIZE,
       minSize: MIN_SIDEBAR_SIZE,
       splitterSize: 1,
       endPanelControl: true,
       startPanel: this.InspectorTabPanel({
@@ -457,16 +462,18 @@ InspectorPanel.prototype = {
         id: "inspector-sidebar-container"
       }),
       vert: this.useLandscapeMode(),
     });
 
     this._splitter = this.ReactDOM.render(splitter,
       this.panelDoc.getElementById("inspector-splitter-box"));
 
+    this.panelWin.addEventListener("resize", this.onPanelWindowResize, true);
+
     // Persist splitter state in preferences.
     this.sidebar.on("show", this.onSidebarShown);
     this.sidebar.on("hide", this.onSidebarHidden);
     this.sidebar.on("destroy", this.onSidebarHidden);
   },
 
   /**
    * Splitter clean up.
@@ -597,17 +604,17 @@ InspectorPanel.prototype = {
 
     // Setup the add-node button.
     this.addNode = this.addNode.bind(this);
     this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
     this.addNodeButton.addEventListener("click", this.addNode);
 
     // Setup the eye-dropper icon if we're in an HTML document and we have actor support.
     if (this.selection.nodeFront && this.selection.nodeFront.isInHTMLDocument) {
-      this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
+      this.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
         if (!value) {
           return;
         }
 
         this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
         this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
         this.eyeDropperButton = this.panelDoc
                                     .getElementById("inspector-eyedropper-toggle");
@@ -921,16 +928,27 @@ InspectorPanel.prototype = {
     e.preventDefault();
     this._openMenu({
       screenX: e.screenX,
       screenY: e.screenY,
       target: e.target,
     });
   },
 
+  /**
+   * This is meant to be called by all the search, filter, inplace text boxes in the
+   * inspector, and just calls through to the toolbox openTextBoxContextMenu helper.
+   * @param {DOMEvent} e
+   */
+  onTextBoxContextMenu: function (e) {
+    e.stopPropagation();
+    e.preventDefault();
+    this.toolbox.openTextBoxContextMenu(e.screenX, e.screenY);
+  },
+
   _openMenu: function ({ target, screenX = 0, screenY = 0 } = { }) {
     let markupContainer = this.markup.getContainer(this.selection.nodeFront);
 
     this.contextMenuTarget = target;
     this.nodeMenuTriggerInfo = markupContainer &&
       markupContainer.editor.getInfoAtNode(target);
 
     let isSelectionElement = this.selection.isElementNode() &&
@@ -1268,17 +1286,17 @@ InspectorPanel.prototype = {
     let doc = this.panelDoc;
 
     this._markupBox = doc.getElementById("markup-box");
 
     // create tool iframe
     this._markupFrame = doc.createElement("iframe");
     this._markupFrame.setAttribute("flex", "1");
     this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
-    this._markupFrame.addEventListener("contextmenu", this._onContextMenu, true);
+    this._markupFrame.addEventListener("contextmenu", this._onContextMenu);
 
     // This is needed to enable tooltips inside the iframe document.
     this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
 
     this._markupBox.setAttribute("collapsed", true);
     this._markupBox.appendChild(this._markupFrame);
     this._markupFrame.setAttribute("src", "chrome://devtools/content/inspector/markup/markup.xhtml");
     this._markupFrame.setAttribute("aria-label",
@@ -1297,17 +1315,17 @@ InspectorPanel.prototype = {
     this.emit("markuploaded");
   },
 
   _destroyMarkup: function () {
     let destroyPromise;
 
     if (this._markupFrame) {
       this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
-      this._markupFrame.removeEventListener("contextmenu", this._onContextMenu, true);
+      this._markupFrame.removeEventListener("contextmenu", this._onContextMenu);
     }
 
     if (this.markup) {
       destroyPromise = this.markup.destroy();
       this.markup = null;
     } else {
       destroyPromise = promise.resolve();
     }
@@ -1811,8 +1829,95 @@ InspectorPanel.prototype = {
   copyAttributeLink: function (link) {
     // When the inspector menu was setup on click (see _getNodeLinkMenuItems), we
     // already checked that resolveRelativeURL existed.
     this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => {
       clipboardHelper.copyString(url);
     }, console.error);
   }
 };
+
+// URL constructor doesn't support chrome: scheme
+let href = window.location.href.replace(/chrome:/, "http://");
+let url = new window.URL(href);
+
+// Only use this method to attach the toolbox if some query parameters are given
+if (url.search.length > 1) {
+  const { targetFromURL } = require("devtools/client/framework/target-from-url");
+  const { attachThread } = require("devtools/client/framework/attach-thread");
+  const { BrowserLoader } =
+    Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+
+  const { Selection } = require("devtools/client/framework/selection");
+  const { InspectorFront } = require("devtools/shared/fronts/inspector");
+  const { getHighlighterUtils } = require("devtools/client/framework/toolbox-highlighter-utils");
+
+  Task.spawn(function* () {
+    let target = yield targetFromURL(url);
+
+    let notImplemented = function () {
+      throw new Error("Not implemented in a tab");
+    };
+    let fakeToolbox = {
+      target,
+      hostType: "bottom",
+      doc: window.document,
+      win: window,
+      on() {}, emit() {}, off() {},
+      initInspector() {},
+      browserRequire: BrowserLoader({
+        window: window,
+        useOnlyShared: true
+      }).require,
+      get React() {
+        return this.browserRequire("devtools/client/shared/vendor/react");
+      },
+      get ReactDOM() {
+        return this.browserRequire("devtools/client/shared/vendor/react-dom");
+      },
+      isToolRegistered() {
+        return false;
+      },
+      currentToolId: "inspector",
+      getCurrentPanel() {
+        return "inspector";
+      },
+      get textboxContextMenuPopup() {
+        notImplemented();
+      },
+      getPanel: notImplemented,
+      openSplitConsole: notImplemented,
+      viewCssSourceInStyleEditor: notImplemented,
+      viewJsSourceInDebugger: notImplemented,
+      viewSource: notImplemented,
+      viewSourceInDebugger: notImplemented,
+      viewSourceInStyleEditor: notImplemented,
+
+      // For attachThread:
+      highlightTool() {},
+      unhighlightTool() {},
+      selectTool() {},
+      raise() {},
+      getNotificationBox() {}
+    };
+
+    // attachThread also expect a toolbox as argument
+    fakeToolbox.threadClient = yield attachThread(fakeToolbox);
+
+    let inspector = InspectorFront(target.client, target.form);
+    let showAllAnonymousContent =
+      Services.prefs.getBoolPref("devtools.inspector.showAllAnonymousContent");
+    let walker = yield inspector.getWalker({ showAllAnonymousContent });
+    let selection = new Selection(walker);
+    let highlighter = yield inspector.getHighlighter(false);
+
+    fakeToolbox.inspector = inspector;
+    fakeToolbox.walker = walker;
+    fakeToolbox.selection = selection;
+    fakeToolbox.highlighter = highlighter;
+    fakeToolbox.highlighterUtils = getHighlighterUtils(fakeToolbox);
+
+    let inspectorUI = new Inspector(fakeToolbox);
+    inspectorUI.init();
+  }).then(null, e => {
+    window.alert("Unable to start the inspector:" + e.message + "\n" + e.stack);
+  });
+}
--- a/devtools/client/inspector/inspector.xhtml
+++ b/devtools/client/inspector/inspector.xhtml
@@ -20,16 +20,17 @@
 
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"></script>
+  <script type="application/javascript;version=1.8" src="inspector.js" defer="true"></script>
 </head>
 <body class="theme-body" role="application">
   <div class="inspector-responsive-container theme-body inspector">
 
     <!-- Main Panel Content -->
     <div id="inspector-main-content" class="devtools-main-content">
       <div id="inspector-toolbar" class="devtools-toolbar" nowindowdrag="true"
                 data-localization-bundle="devtools/locale/inspector.properties">
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -79,18 +79,18 @@ const INSPECTOR_L10N = new LocalizationH
  * updating based on mutations, and the undo/redo bindings.
  *
  * @param  {Inspector} inspector
  *         The inspector we're watching.
  * @param  {iframe} frame
  *         An iframe in which the caller has kindly loaded markup.xhtml.
  */
 function MarkupView(inspector, frame, controllerWindow) {
-  this._inspector = inspector;
-  this.walker = this._inspector.walker;
+  this.inspector = inspector;
+  this.walker = this.inspector.walker;
   this._frame = frame;
   this.win = this._frame.contentWindow;
   this.doc = this._frame.contentDocument;
   this._elt = this.doc.querySelector("#root");
   this.htmlEditor = new HTMLEditor(this.doc);
 
   try {
     this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
@@ -104,17 +104,17 @@ function MarkupView(inspector, frame, co
     Services.prefs.getIntPref(ATTR_COLLAPSE_LENGTH_PREF);
 
   // Creating the popup to be used to show CSS suggestions.
   let options = {
     autoSelect: true,
     theme: "auto",
   };
 
-  this.popup = new AutocompletePopup(inspector._toolbox, options);
+  this.popup = new AutocompletePopup(inspector.toolbox, options);
 
   this.undo = new UndoStack();
   this.undo.installController(controllerWindow);
 
   this._containers = new Map();
 
   // Binding functions that need to be called in scope.
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
@@ -140,18 +140,18 @@ function MarkupView(inspector, frame, co
   this._elt.addEventListener("mousemove", this._onMouseMove, false);
   this._elt.addEventListener("mouseout", this._onMouseOut, false);
   this._elt.addEventListener("blur", this._onBlur, true);
   this.win.addEventListener("mouseup", this._onMouseUp);
   this.win.addEventListener("copy", this._onCopy);
   this._frame.addEventListener("focus", this._onFocus, false);
   this.walker.on("mutations", this._mutationObserver);
   this.walker.on("display-change", this._onDisplayChange);
-  this._inspector.selection.on("new-node-front", this._onNewSelection);
-  this._inspector.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
+  this.inspector.selection.on("new-node-front", this._onNewSelection);
+  this.toolbox.on("picker-node-hovered", this._onToolboxPickerHover);
 
   this._onNewSelection();
   this._initTooltips();
 
   this._prefObserver = new PrefObserver("devtools.markup");
   this._prefObserver.on(ATTR_COLLAPSE_ENABLED_PREF,
                         this._onCollapseAttributesPrefChange);
   this._prefObserver.on(ATTR_COLLAPSE_LENGTH_PREF,
@@ -163,32 +163,36 @@ function MarkupView(inspector, frame, co
 MarkupView.prototype = {
   /**
    * How long does a node flash when it mutates (in ms).
    */
   CONTAINER_FLASHING_DURATION: 500,
 
   _selectedContainer: null,
 
+  get toolbox() {
+    return this.inspector.toolbox;
+  },
+
   /**
    * Handle promise rejections for various asynchronous actions, and only log errors if
    * the markup view still exists.
    * This is useful to silence useless errors that happen when the markup view is
    * destroyed while still initializing (and making protocol requests).
    */
   _handleRejectionIfNotDestroyed: function (e) {
     if (!this._destroyer) {
       console.error(e);
     }
   },
 
   _initTooltips: function () {
-    this.eventDetailsTooltip = new HTMLTooltip(this._inspector.toolbox,
+    this.eventDetailsTooltip = new HTMLTooltip(this.toolbox,
       {type: "arrow"});
-    this.imagePreviewTooltip = new HTMLTooltip(this._inspector.toolbox,
+    this.imagePreviewTooltip = new HTMLTooltip(this.toolbox,
       {type: "arrow", useXulWrapper: "true"});
     this._enableImagePreviewTooltip();
   },
 
   _enableImagePreviewTooltip: function () {
     this.imagePreviewTooltip.startTogglingOnHover(this._elt,
       this._isImagePreviewTarget);
   },
@@ -418,31 +422,30 @@ MarkupView.prototype = {
    *
    * @param  {NodeFront} nodeFront
    *         The node to show the highlighter for
    * @return {Promise} Resolves when the highlighter for this nodeFront is
    *         shown, taking into account that there could already be highlighter
    *         requests queued up
    */
   _showBoxModel: function (nodeFront) {
-    return this._inspector.toolbox.highlighterUtils
-      .highlightNodeFront(nodeFront);
+    return this.toolbox.highlighterUtils.highlightNodeFront(nodeFront);
   },
 
   /**
    * Hide the box model highlighter on a given node front
    *
    * @param  {Boolean} forceHide
    *         See toolbox-highlighter-utils/unhighlight
    * @return {Promise} Resolves when the highlighter for this nodeFront is
    *         hidden, taking into account that there could already be highlighter
    *         requests queued up
    */
   _hideBoxModel: function (forceHide) {
-    return this._inspector.toolbox.highlighterUtils.unhighlight(forceHide);
+    return this.toolbox.highlighterUtils.unhighlight(forceHide);
   },
 
   _briefBoxModelTimer: null,
 
   _clearBriefBoxModelTimer: function () {
     if (this._briefBoxModelTimer) {
       clearTimeout(this._briefBoxModelTimer);
       this._briefBoxModelPromise.resolve();
@@ -546,47 +549,47 @@ MarkupView.prototype = {
    * - if it's "test" (this is a special case for mochitest. In tests, we often
    * need to select elements but don't necessarily want the highlighter to come
    * and go after a delay as this might break test scenarios)
    * We also do not want to start a brief highlight timeout if the node is
    * already being hovered over, since in that case it will already be
    * highlighted.
    */
   _shouldNewSelectionBeHighlighted: function () {
-    let reason = this._inspector.selection.reason;
+    let reason = this.inspector.selection.reason;
     let unwantedReasons = [
       "inspector-open",
       "navigateaway",
       "nodeselected",
       "test"
     ];
-    let isHighlight = this._hoveredNode === this._inspector.selection.nodeFront;
+    let isHighlight = this._hoveredNode === this.inspector.selection.nodeFront;
     return !isHighlight && reason && unwantedReasons.indexOf(reason) === -1;
   },
 
   /**
    * React to new-node-front selection events.
    * Highlights the node if needed, and make sure it is shown and selected in
    * the view.
    */
   _onNewSelection: function () {
-    let selection = this._inspector.selection;
+    let selection = this.inspector.selection;
 
     this.htmlEditor.hide();
     if (this._hoveredNode && this._hoveredNode !== selection.nodeFront) {
       this.getContainer(this._hoveredNode).hovered = false;
       this._hoveredNode = null;
     }
 
     if (!selection.isNode()) {
       this.unmarkSelectedNode();
       return;
     }
 
-    let done = this._inspector.updating("markup-view");
+    let done = this.inspector.updating("markup-view");
     let onShowBoxModel, onShow;
 
     // Highlight the element briefly if needed.
     if (this._shouldNewSelectionBeHighlighted()) {
       onShowBoxModel = this._brieflyShowBoxModel(selection.nodeFront);
     }
 
     onShow = this.showNode(selection.nodeFront).then(() => {
@@ -606,17 +609,17 @@ MarkupView.prototype = {
     promise.all([onShowBoxModel, onShow]).then(done);
   },
 
   /**
    * Maybe make selected the current node selection's MarkupContainer depending
    * on why the current node got selected.
    */
   maybeNavigateToNewSelection: function () {
-    let {reason, nodeFront} = this._inspector.selection;
+    let {reason, nodeFront} = this.inspector.selection;
 
     // The list of reasons that should lead to navigating to the node.
     let reasonsToNavigate = [
       // If the user picked an element with the element picker.
       "picker-node-picked",
       // If the user selected an element with the browser context menu.
       "browser-context-menu",
       // If the user added a new node by clicking in the inspector toolbar.
@@ -651,19 +654,19 @@ MarkupView.prototype = {
   },
 
   _onCopy: function (evt) {
     // Ignore copy events from editors
     if (this._isInputOrTextarea(evt.target)) {
       return;
     }
 
-    let selection = this._inspector.selection;
+    let selection = this.inspector.selection;
     if (selection.isNode()) {
-      this._inspector.copyOuterHTML();
+      this.inspector.copyOuterHTML();
     }
     evt.stopPropagation();
     evt.preventDefault();
   },
 
   /**
    * Register all key shortcuts.
    */
@@ -708,17 +711,17 @@ MarkupView.prototype = {
         break;
       }
       case "markupView.edit.key": {
         this.beginEditingOuterHTML(this._selectedContainer.node);
         break;
       }
       case "markupView.scrollInto.key": {
         let selection = this._selectedContainer.node;
-        this._inspector.scrollNodeIntoView(selection);
+        this.inspector.scrollNodeIntoView(selection);
         break;
       }
       // Generic keys
       case "Delete": {
         this.deleteNodeOrAttribute();
         break;
       }
       case "Backspace": {
@@ -960,34 +963,34 @@ MarkupView.prototype = {
 
     let container;
     let {nodeType, isPseudoElement} = node;
     if (node === this.walker.rootNode) {
       container = new RootContainer(this, node);
       this._elt.appendChild(container.elt);
       this._rootNode = node;
     } else if (nodeType == nodeConstants.ELEMENT_NODE && !isPseudoElement) {
-      container = new MarkupElementContainer(this, node, this._inspector);
+      container = new MarkupElementContainer(this, node, this.inspector);
     } else if (nodeType == nodeConstants.COMMENT_NODE ||
                nodeType == nodeConstants.TEXT_NODE) {
-      container = new MarkupTextContainer(this, node, this._inspector);
+      container = new MarkupTextContainer(this, node, this.inspector);
     } else {
-      container = new MarkupReadOnlyContainer(this, node, this._inspector);
+      container = new MarkupReadOnlyContainer(this, node, this.inspector);
     }
 
     if (flashNode) {
       container.flashMutation();
     }
 
     this._containers.set(node, container);
     container.childrenDirty = true;
 
     this._updateChildren(container);
 
-    this._inspector.emit("container-created", container);
+    this.inspector.emit("container-created", container);
 
     return container;
   },
 
   /**
    * Mutation observer used for included nodes.
    */
   _mutationObserver: function (mutations) {
@@ -1034,17 +1037,17 @@ MarkupView.prototype = {
 
     this._waitForChildren().then(() => {
       if (this._destroyer) {
         // Could not fully update after markup mutations, the markup-view was destroyed
         // while waiting for children. Bail out silently.
         return;
       }
       this._flashMutatedNodes(mutations);
-      this._inspector.emit("markupmutation", mutations);
+      this.inspector.emit("markupmutation", mutations);
 
       // Since the htmlEditor is absolutely positioned, a mutation may change
       // the location in which it should be shown.
       this.htmlEditor.refresh();
     });
   },
 
   /**
@@ -1272,47 +1275,47 @@ MarkupView.prototype = {
           isNodeRemovalMutation = true;
           break;
         }
       }
       if (!isNodeRemovalMutation) {
         return;
       }
 
-      this._inspector.off("markupmutation", onMutations);
+      this.inspector.off("markupmutation", onMutations);
       this._removedNodeObserver = null;
 
       // Don't select the new node if the user has already changed the current
       // selection.
-      if (this._inspector.selection.nodeFront === parentContainer.node ||
-          (this._inspector.selection.nodeFront === removedNode && isHTMLTag)) {
+      if (this.inspector.selection.nodeFront === parentContainer.node ||
+          (this.inspector.selection.nodeFront === removedNode && isHTMLTag)) {
         let childContainers = parentContainer.getChildContainers();
         if (childContainers && childContainers[childIndex]) {
           this.markNodeAsSelected(childContainers[childIndex].node, reason);
           if (childContainers[childIndex].hasChildren) {
             this.expandNode(childContainers[childIndex].node);
           }
           this.emit("reselectedonremoved");
         }
       }
     };
 
     // Start listening for mutations until we find a childList change that has
     // removedNode removed.
-    this._inspector.on("markupmutation", onMutations);
+    this.inspector.on("markupmutation", onMutations);
   },
 
   /**
    * Make sure to stop listening for node removal markupmutations and not
    * reselect the corresponding node when that happens.
    * Useful when the outerHTML/tagname edition failed.
    */
   cancelReselectOnRemoved: function () {
     if (this._removedNodeObserver) {
-      this._inspector.off("markupmutation", this._removedNodeObserver);
+      this.inspector.off("markupmutation", this._removedNodeObserver);
       this._removedNodeObserver = null;
       this.emit("canceledreselectonremoved");
     }
   },
 
   /**
    * Replace the outerHTML of any node displayed in the inspector with
    * some other HTML code
@@ -1474,18 +1477,18 @@ MarkupView.prototype = {
 
     // Select the new container.
     this._selectedContainer = container;
     if (node) {
       this._selectedContainer.selected = true;
     }
 
     // Change the current selection if needed.
-    if (this._inspector.selection.nodeFront !== node) {
-      this._inspector.selection.setNodeFront(node, reason || "nodeselected");
+    if (this.inspector.selection.nodeFront !== node) {
+      this.inspector.selection.setNodeFront(node, reason || "nodeselected");
     }
 
     return true;
   },
 
   /**
    * Make sure that every ancestor of the selection are updated
    * and included in the list of visible children.
@@ -1521,17 +1524,17 @@ MarkupView.prototype = {
    * Check if the current selection is a descendent of the container.
    * if so, make sure it's among the visible set for the container,
    * and set the dirty flag if needed.
    *
    * @return The node that should be made visible, if any.
    */
   _checkSelectionVisible: function (container) {
     let centered = null;
-    let node = this._inspector.selection.nodeFront;
+    let node = this.inspector.selection.nodeFront;
     while (node) {
       if (node.parentNode() === container.node) {
         centered = node;
         break;
       }
       node = node.parentNode();
     }
 
@@ -1736,18 +1739,18 @@ MarkupView.prototype = {
     this._elt.removeEventListener("mousemove", this._onMouseMove, false);
     this._elt.removeEventListener("mouseout", this._onMouseOut, false);
     this._elt.removeEventListener("blur", this._onBlur, true);
     this.win.removeEventListener("mouseup", this._onMouseUp);
     this.win.removeEventListener("copy", this._onCopy);
     this._frame.removeEventListener("focus", this._onFocus, false);
     this.walker.off("mutations", this._mutationObserver);
     this.walker.off("display-change", this._onDisplayChange);
-    this._inspector.selection.off("new-node-front", this._onNewSelection);
-    this._inspector.toolbox.off("picker-node-hovered",
+    this.inspector.selection.off("new-node-front", this._onNewSelection);
+    this.toolbox.off("picker-node-hovered",
                                 this._onToolboxPickerHover);
 
     this._prefObserver.off(ATTR_COLLAPSE_ENABLED_PREF,
                            this._onCollapseAttributesPrefChange);
     this._prefObserver.off(ATTR_COLLAPSE_LENGTH_PREF,
                            this._onCollapseAttributesPrefChange);
     this._prefObserver.destroy();
 
@@ -2333,17 +2336,17 @@ MarkupContainer.prototype = {
     }
 
     // Follow attribute links if middle or meta click.
     if (isMiddleClick || isMetaClick) {
       let link = target.dataset.link;
       let type = target.dataset.type;
       // Make container tabbable descendants not tabbable (by default).
       this.canFocus = false;
-      this.markup._inspector.followAttributeLink(type, link);
+      this.markup.inspector.followAttributeLink(type, link);
       return;
     }
 
     // Start node drag & drop (if the mouse moved, see _onMouseMove).
     if (isLeftClick && this.isDraggable()) {
       this._isPreDragging = true;
       this._dragStartY = event.pageY;
     }
@@ -2641,17 +2644,17 @@ function MarkupElementContainer(markupVi
 
 MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
   _buildEventTooltipContent: Task.async(function* (target, tooltip) {
     if (target.hasAttribute("data-event")) {
       yield tooltip.hide();
 
       let listenerInfo = yield this.node.getEventListenerInfo();
 
-      let toolbox = this.markup._inspector.toolbox;
+      let toolbox = this.markup.toolbox;
       setEventTooltip(tooltip, listenerInfo, toolbox);
       // Disable the image preview tooltip while we display the event details
       this.markup._disableImagePreviewTooltip();
       tooltip.once("hidden", () => {
         // Enable the image preview tooltip after closing the event details
         this.markup._enableImagePreviewTooltip();
       });
       tooltip.show(target);
@@ -2907,17 +2910,18 @@ function TextEditor(container, node, tem
           this.container.undo.do(() => {
             this.node.setNodeValue(val);
           }, () => {
             this.node.setNodeValue(oldValue);
           });
         });
       });
     },
-    cssProperties: getCssProperties(this.markup._inspector.toolbox)
+    cssProperties: getCssProperties(this.markup.toolbox),
+    contextMenu: this.markup.inspector.onTextBoxContextMenu
   });
 
   this.update();
 }
 
 TextEditor.prototype = {
   get selected() {
     return this._selected;
@@ -2961,17 +2965,17 @@ TextEditor.prototype = {
  *         The node being edited.
  */
 function ElementEditor(container, node) {
   this.container = container;
   this.node = node;
   this.markup = this.container.markup;
   this.template = this.markup.template.bind(this.markup);
   this.doc = this.markup.doc;
-  this._cssProperties = getCssProperties(this.markup._inspector.toolbox);
+  this._cssProperties = getCssProperties(this.markup.toolbox);
 
   this.attrElements = new Map();
   this.animationTimers = {};
 
   // The templates will fill the following properties
   this.elt = null;
   this.tag = null;
   this.closeTag = null;
@@ -2989,16 +2993,17 @@ function ElementEditor(container, node) 
     this.tag.setAttribute("tabindex", "-1");
     editableField({
       element: this.tag,
       multiline: true,
       maxWidth: () => getAutocompleteMaxWidth(this.tag, this.container.elt),
       trigger: "dblclick",
       stopOnReturn: true,
       done: this.onTagEdit.bind(this),
+      contextMenu: this.markup.inspector.onTextBoxContextMenu,
       cssProperties: this._cssProperties
     });
   }
 
   // Make the new attribute space editable.
   this.newAttr.editMode = editableField({
     element: this.newAttr,
     multiline: true,
@@ -3016,16 +3021,17 @@ function ElementEditor(container, node) 
       let undoMods = this._startModifyingAttributes();
       this._applyAttributes(val, null, doMods, undoMods);
       this.container.undo.do(() => {
         doMods.apply();
       }, function () {
         undoMods.apply();
       });
     },
+    contextMenu: this.markup.inspector.onTextBoxContextMenu,
     cssProperties: this._cssProperties
   });
 
   let displayName = this.node.displayName;
   this.tag.textContent = displayName;
   this.closeTag.textContent = displayName;
 
   let isVoidElement = HTML_VOID_ELEMENTS.includes(displayName);
@@ -3257,16 +3263,17 @@ ElementEditor.prototype = {
         doMods.removeAttribute(attribute.name);
         this._applyAttributes(newValue, attr, doMods, undoMods);
         this.container.undo.do(() => {
           doMods.apply();
         }, () => {
           undoMods.apply();
         });
       },
+      contextMenu: this.markup.inspector.onTextBoxContextMenu,
       cssProperties: this._cssProperties
     });
 
     // Figure out where we should place the attribute.
     if (attribute.name == "id") {
       before = this.attrList.firstChild;
     } else if (attribute.name == "class") {
       let idNode = this.attrElements.get("id");
@@ -3357,18 +3364,17 @@ ElementEditor.prototype = {
    * Listen to mutations, and when the attribute list is regenerated
    * try to focus on the attribute after the one that's being edited now.
    * If the attribute order changes, go to the beginning of the attribute list.
    */
   refocusOnEdit: function (attrName, attrNode, direction) {
     // Only allow one refocus on attribute change at a time, so when there's
     // more than 1 request in parallel, the last one wins.
     if (this._editedAttributeObserver) {
-      this.markup._inspector.off("markupmutation",
-        this._editedAttributeObserver);
+      this.markup.inspector.off("markupmutation", this._editedAttributeObserver);
       this._editedAttributeObserver = null;
     }
 
     let container = this.markup.getContainer(this.node);
 
     let activeAttrs = [...this.attrList.childNodes]
       .filter(el => el.style.display != "none");
     let attributeIndex = activeAttrs.indexOf(attrNode);
@@ -3443,17 +3449,17 @@ ElementEditor.prototype = {
         editable.focus();
       }
 
       this.markup.emit("refocusedonedit");
     };
 
     // Start listening for mutations until we find an attributes change
     // that modifies this attribute.
-    this.markup._inspector.once("markupmutation", onMutations);
+    this.markup.inspector.once("markupmutation", onMutations);
   },
 
   /**
    * Called when the tag name editor has is done editing.
    */
   onTagEdit: function (newTagName, isCommit) {
     if (!isCommit ||
         newTagName.toLowerCase() === this.node.tagName.toLowerCase() ||
--- a/devtools/client/inspector/markup/test/browser_markup_update-on-navigtion.js
+++ b/devtools/client/inspector/markup/test/browser_markup_update-on-navigtion.js
@@ -10,17 +10,17 @@ const URL_1 = SCHEMA + "<div id='one' st
 const URL_2 = SCHEMA + "<div id='two' style='color:green;'>TWO</div>";
 
 add_task(function* () {
   let {inspector, testActor} = yield openInspectorForURL(URL_1);
 
   assertMarkupViewIsLoaded();
   yield selectNode("#one", inspector);
 
-  let willNavigate = inspector.toolbox.target.once("will-navigate");
+  let willNavigate = inspector.target.once("will-navigate");
   yield testActor.eval(`content.location = "${URL_2}"`);
 
   info("Waiting for will-navigate");
   yield willNavigate;
 
   info("Navigation to page 2 has started, the inspector should be empty");
   assertMarkupViewIsEmpty();
 
--- a/devtools/client/inspector/moz.build
+++ b/devtools/client/inspector/moz.build
@@ -9,15 +9,15 @@ DIRS += [
     'markup',
     'rules',
     'shared'
 ]
 
 DevToolsModules(
     'breadcrumbs.js',
     'inspector-commands.js',
-    'inspector-panel.js',
     'inspector-search.js',
     'inspector.xhtml',
+    'panel.js',
     'toolsidebar.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/panel.js
@@ -0,0 +1,19 @@
+/* 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/. */
+
+"use strict";
+
+function InspectorPanel(iframeWindow, toolbox) {
+  this._inspector = new iframeWindow.Inspector(toolbox);
+}
+InspectorPanel.prototype = {
+  open() {
+    return this._inspector.init();
+  },
+
+  destroy() {
+    return this._inspector.destroy();
+  }
+};
+exports.InspectorPanel = InspectorPanel;
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -107,18 +107,16 @@ function CssRuleView(inspector, document
 
   this._outputParser = new OutputParser(document, this.cssProperties);
 
   this._onAddRule = this._onAddRule.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onFilterStyles = this._onFilterStyles.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
-  this._onFilterTextboxContextMenu =
-    this._onFilterTextboxContextMenu.bind(this);
   this._onTogglePseudoClassPanel = this._onTogglePseudoClassPanel.bind(this);
   this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
 
   let doc = this.styleDocument;
   this.element = doc.getElementById("ruleview-container-focusable");
   this.addRuleButton = doc.getElementById("ruleview-add-rule-button");
   this.searchField = doc.getElementById("ruleview-searchbox");
   this.searchClearButton = doc.getElementById("ruleview-searchinput-clear");
@@ -135,18 +133,17 @@ function CssRuleView(inspector, document
   this.shortcuts.on("Escape", this._onShortcut);
   this.shortcuts.on("Return", this._onShortcut);
   this.shortcuts.on("Space", this._onShortcut);
   this.shortcuts.on("CmdOrCtrl+F", this._onShortcut);
   this.element.addEventListener("copy", this._onCopy);
   this.element.addEventListener("contextmenu", this._onContextMenu);
   this.addRuleButton.addEventListener("click", this._onAddRule);
   this.searchField.addEventListener("input", this._onFilterStyles);
-  this.searchField.addEventListener("contextmenu",
-                                    this._onFilterTextboxContextMenu);
+  this.searchField.addEventListener("contextmenu", this.inspector.onTextBoxContextMenu);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
   this.pseudoClassToggle.addEventListener("click",
                                           this._onTogglePseudoClassPanel);
   this.hoverCheckbox.addEventListener("click", this._onTogglePseudoClass);
   this.activeCheckbox.addEventListener("click", this._onTogglePseudoClass);
   this.focusCheckbox.addEventListener("click", this._onTogglePseudoClass);
 
   this._handlePrefChange = this._handlePrefChange.bind(this);
@@ -465,17 +462,17 @@ CssRuleView.prototype = {
   },
 
   /**
    * Add a new rule to the current element.
    */
   _onAddRule: function () {
     let elementStyle = this._elementStyle;
     let element = elementStyle.element;
-    let client = this.inspector.toolbox.target.client;
+    let client = this.inspector.target.client;
     let pseudoClasses = element.pseudoClassLocks;
 
     if (!client.traits.addNewRule) {
       return;
     }
 
     if (!this.pageStyle.supportsAuthoredStyles) {
       // We're talking to an old server.
@@ -640,29 +637,16 @@ CssRuleView.prototype = {
 
       this.inspector.emit("ruleview-filtered");
 
       this._filterChangeTimeout = null;
     }, filterTimeout);
   },
 
   /**
-   * Context menu handler for filter style search box.
-   */
-  _onFilterTextboxContextMenu: function (event) {
-    try {
-      this.styleWindow.focus();
-      let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
-      contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
-    } catch (e) {
-      console.error(e);
-    }
-  },
-
-  /**
    * Called when the user clicks on the clear button in the filter style search
    * box. Returns true if the search box is cleared and false otherwise.
    */
   _onClearSearch: function () {
     if (this.searchField.value) {
       this.setFilterStyles("");
       return true;
     }
@@ -694,17 +678,17 @@ CssRuleView.prototype = {
 
     // Remove bound listeners
     this.shortcuts.destroy();
     this.element.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.addRuleButton.removeEventListener("click", this._onAddRule);
     this.searchField.removeEventListener("input", this._onFilterStyles);
     this.searchField.removeEventListener("contextmenu",
-      this._onFilterTextboxContextMenu);
+      this.inspector.onTextBoxContextMenu);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
     this.pseudoClassToggle.removeEventListener("click",
       this._onTogglePseudoClassPanel);
     this.hoverCheckbox.removeEventListener("click", this._onTogglePseudoClass);
     this.activeCheckbox.removeEventListener("click", this._onTogglePseudoClass);
     this.focusCheckbox.removeEventListener("click", this._onTogglePseudoClass);
 
     this.searchField = null;
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_context-menu.js
@@ -11,17 +11,17 @@ const TEST_URI = "<h1>test filter contex
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {toolbox, inspector, view} = yield openRuleView();
   yield selectNode("h1", inspector);
 
   let win = view.styleWindow;
   let searchField = view.searchField;
-  let searchContextMenu = toolbox.textboxContextMenuPopup;
+  let searchContextMenu = toolbox.textBoxContextMenuPopup;
   ok(searchContextMenu,
     "The search filter context menu is loaded in the rule view");
 
   let cmdUndo = searchContextMenu.querySelector("[command=cmd_undo]");
   let cmdDelete = searchContextMenu.querySelector("[command=cmd_delete]");
   let cmdSelectAll = searchContextMenu.querySelector("[command=cmd_selectAll]");
   let cmdCut = searchContextMenu.querySelector("[command=cmd_cut]");
   let cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]");
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -82,17 +82,17 @@ RuleEditor.prototype = {
   destroy: function () {
     this.rule.domRule.off("location-changed");
     this.toolbox.off("tool-registered", this.updateSourceLink);
     this.toolbox.off("tool-unregistered", this.updateSourceLink);
   },
 
   get isSelectorEditable() {
     let trait = this.isEditable &&
-      this.toolbox.target.client.traits.selectorEditable &&
+      this.ruleView.inspector.target.client.traits.selectorEditable &&
       this.rule.domRule.type !== ELEMENT_STYLE &&
       this.rule.domRule.type !== CSSRule.KEYFRAME_RULE;
 
     // Do not allow editing anonymousselectors until we can
     // detect mutations on  pseudo elements in Bug 1034110.
     return trait && !this.rule.elementStyle.element.isAnonymous;
   },
 
@@ -140,17 +140,18 @@ RuleEditor.prototype = {
       this.selectorText.addEventListener("click", event => {
         // Clicks within the selector shouldn't propagate any further.
         event.stopPropagation();
       }, false);
 
       editableField({
         element: this.selectorText,
         done: this._onSelectorDone,
-        cssProperties: this.rule.cssProperties
+        cssProperties: this.rule.cssProperties,
+        contextMenu: this.ruleView.inspector.onTextBoxContextMenu
       });
     }
 
     if (this.rule.domRule.type !== CSSRule.KEYFRAME_RULE) {
       let selector = this.rule.domRule.selectors
                ? this.rule.domRule.selectors.join(", ")
                : this.ruleView.inspector.selectionCssSelector;
 
@@ -443,17 +444,18 @@ RuleEditor.prototype = {
 
     this.editor = new InplaceEditor({
       element: this.newPropSpan,
       done: this._onNewProperty,
       destroy: this._newPropertyDestroy,
       advanceChars: ":",
       contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
       popup: this.ruleView.popup,
-      cssProperties: this.rule.cssProperties
+      cssProperties: this.rule.cssProperties,
+      contextMenu: this.ruleView.inspector.onTextBoxContextMenu
     });
 
     // Auto-close the input if multiple rules get pasted into new property.
     this.editor.input.addEventListener("paste",
       blurOnMultipleProperties(this.rule.cssProperties), false);
   },
 
   /**
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -214,17 +214,18 @@ TextPropertyEditor.prototype = {
       editableField({
         start: this._onStartEditing,
         element: this.nameSpan,
         done: this._onNameDone,
         destroy: this.updatePropertyState,
         advanceChars: ":",
         contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
         popup: this.popup,
-        cssProperties: this.cssProperties
+        cssProperties: this.cssProperties,
+        contextMenu: this.ruleView.inspector.onTextBoxContextMenu
       });
 
       // Auto blur name field on multiple CSS rules get pasted in.
       this.nameContainer.addEventListener("paste",
         blurOnMultipleProperties(this.cssProperties), false);
 
       this.valueContainer.addEventListener("click", (event) => {
         // Clicks within the value shouldn't propagate any further.
@@ -284,17 +285,18 @@ TextPropertyEditor.prototype = {
         destroy: this.update,
         validate: this._onValidate,
         advanceChars: advanceValidate,
         contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
         property: this.prop,
         popup: this.popup,
         multiline: true,
         maxWidth: () => this.container.getBoundingClientRect().width,
-        cssProperties: this.cssProperties
+        cssProperties: this.cssProperties,
+        contextMenu: this.ruleView.inspector.onTextBoxContextMenu
       });
     }
   },
 
   /**
    * Get the path from which to resolve requests for this
    * rule's stylesheet.
    *
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -156,8 +156,9 @@ subsuite = clipboard
 [browser_inspector_search-reserved.js]
 [browser_inspector_search-selection.js]
 [browser_inspector_search-sidebar.js]
 [browser_inspector_select-docshell.js]
 [browser_inspector_select-last-selected.js]
 [browser_inspector_search-navigation.js]
 [browser_inspector_sidebarstate.js]
 [browser_inspector_switch-to-inspector-on-pick.js]
+[browser_inspector_textbox-menu.js]
--- a/devtools/client/inspector/test/browser_inspector_highlighter-03.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-03.js
@@ -29,17 +29,17 @@ const DOCUMENT_SRC = "<style>" +
 
 const TEST_URI = "data:text/html;charset=utf-8," + DOCUMENT_SRC;
 
 add_task(function* () {
   let { inspector, toolbox, testActor } = yield openInspectorForURL(TEST_URI);
 
   info("Waiting for box mode to show.");
   let body = yield getNodeFront("body", inspector);
-  yield toolbox.highlighter.showBoxModel(body);
+  yield inspector.highlighter.showBoxModel(body);
 
   info("Waiting for element picker to become active.");
   yield startPicker(toolbox);
 
   info("Moving mouse over iframe padding.");
   yield moveMouseOver("iframe", 1, 1);
 
   info("Performing checks");
--- a/devtools/client/inspector/test/browser_inspector_highlighter-04.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-04.js
@@ -22,22 +22,22 @@ const ELEMENTS = ["box-model-root",
                   "box-model-infobar-container",
                   "box-model-infobar-tagname",
                   "box-model-infobar-id",
                   "box-model-infobar-classes",
                   "box-model-infobar-pseudo-classes",
                   "box-model-infobar-dimensions"];
 
 add_task(function* () {
-  let {inspector, toolbox, testActor} = yield openInspectorForURL(TEST_URL);
+  let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
 
   info("Show the box-model highlighter");
   let divFront = yield getNodeFront("div", inspector);
-  yield toolbox.highlighter.showBoxModel(divFront);
+  yield inspector.highlighter.showBoxModel(divFront);
 
   for (let id of ELEMENTS) {
     let foundId = yield testActor.getHighlighterNodeAttribute(id, "id");
     is(foundId, id, "Element " + id + " found");
   }
 
   info("Hide the box-model highlighter");
-  yield toolbox.highlighter.hideBoxModel();
+  yield inspector.highlighter.hideBoxModel();
 });
--- a/devtools/client/inspector/test/browser_inspector_highlighter-hover_02.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-hover_02.js
@@ -6,30 +6,30 @@
 
 // Test that when after an element is selected and highlighted on hover, if the
 // mouse leaves the markup-view and comes back again on the same element, that
 // the highlighter is shown again on the node
 
 const TEST_URL = "data:text/html;charset=utf-8,<p>Select me!</p>";
 
 add_task(function* () {
-  let {toolbox, inspector, testActor} = yield openInspectorForURL(TEST_URL);
+  let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
 
   info("hover over the <p> line in the markup-view so that it's the " +
        "currently hovered node");
   yield hoverContainer("p", inspector);
 
   info("select the <p> markup-container line by clicking");
   yield clickContainer("p", inspector);
   let isVisible = yield testActor.isHighlighting();
   ok(isVisible, "the highlighter is shown");
 
   info("listen to the highlighter's hidden event");
   let onHidden = testActor.waitForHighlighterEvent("hidden",
-    toolbox.highlighter);
+    inspector.highlighter);
   info("mouse-leave the markup-view");
   yield mouseLeaveMarkupView(inspector);
   yield onHidden;
   isVisible = yield testActor.isHighlighting();
   ok(!isVisible, "the highlighter is hidden after mouseleave");
 
   info("hover over the <p> line again, which is still selected");
   yield hoverContainer("p", inspector);
--- a/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-keybinding_03.js
@@ -41,17 +41,17 @@ add_task(function* () {
   yield doKeyStop(shortcutOpts);
   is(inspector.selection.nodeFront.id, "another",
      "The #another DIV is still selected. Passed.");
 
   function doKeyPick(args) {
     info("Key pressed. Waiting for element to be picked");
     testActor.synthesizeKey(args);
     return promise.all([
-      toolbox.selection.once("new-node-front"),
+      inspector.selection.once("new-node-front"),
       inspector.once("inspector-updated")
     ]);
   }
 
   function doKeyStop(args) {
     info("Key pressed. Waiting for picker to be canceled");
     testActor.synthesizeKey(args);
     return inspector.toolbox.once("picker-stopped");
--- a/devtools/client/inspector/test/browser_inspector_highlighter-options.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-options.js
@@ -180,25 +180,25 @@ const TEST_DATA = [
           is(faded, "true", "Region " + region + " is faded");
         }
       }
     }
   }
 ];
 
 add_task(function* () {
-  let {inspector, toolbox, testActor} = yield openInspectorForURL(
+  let {inspector, testActor} = yield openInspectorForURL(
     "data:text/html;charset=utf-8," + encodeURI(TEST_URL));
 
   let divFront = yield getNodeFront("div", inspector);
 
   for (let {desc, options, checkHighlighter} of TEST_DATA) {
     info("Running test: " + desc);
 
     info("Show the box-model highlighter with options " + options);
-    yield toolbox.highlighter.showBoxModel(divFront, options);
+    yield inspector.highlighter.showBoxModel(divFront, options);
 
     yield checkHighlighter(testActor);
 
     info("Hide the box-model highlighter");
-    yield toolbox.highlighter.hideBoxModel();
+    yield inspector.highlighter.hideBoxModel();
   }
 });
--- a/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-xbl.js
@@ -17,17 +17,17 @@ add_task(function* () {
   yield doKeyPick({key: "VK_RETURN", options: {}});
   is(inspector.selection.nodeFront.className, "scale-slider",
      "The .scale-slider inside the scale was selected");
 
   function doKeyPick(msg) {
     info("Key pressed. Waiting for element to be picked");
     testActor.synthesizeKey(msg);
     return promise.all([
-      toolbox.selection.once("new-node-front"),
+      inspector.selection.once("new-node-front"),
       inspector.once("inspector-updated")
     ]);
   }
 
   function moveMouseOver(selector) {
     info("Waiting for element " + selector + " to be highlighted");
     testActor.synthesizeMouse({
       options: {type: "mousemove"},
--- a/devtools/client/inspector/test/browser_inspector_invalidate.js
+++ b/devtools/client/inspector/test/browser_inspector_invalidate.js
@@ -10,26 +10,26 @@ const TEST_URI = "data:text/html;charset
   "browser_inspector_invalidate.js\n" +
   "<div style=\"width: 100px; height: 100px; background:yellow;\"></div>";
 
 add_task(function* () {
   let {inspector, testActor} = yield openInspectorForURL(TEST_URI);
   let divFront = yield getNodeFront("div", inspector);
 
   info("Waiting for highlighter to activate");
-  yield inspector.toolbox.highlighter.showBoxModel(divFront);
+  yield inspector.highlighter.showBoxModel(divFront);
 
   let rect = yield testActor.getSimpleBorderRect();
   is(rect.width, 100, "The highlighter has the right width.");
 
   info("Changing the test element's size and waiting for the highlighter " +
        "to update");
   yield testActor.changeHighlightedNodeWaitForUpdate(
     "style",
     "width: 200px; height: 100px; background:yellow;"
   );
 
   rect = yield testActor.getSimpleBorderRect();
   is(rect.width, 200, "The highlighter has the right width after update");
 
   info("Waiting for highlighter to hide");
-  yield inspector.toolbox.highlighter.hideBoxModel();
+  yield inspector.highlighter.hideBoxModel();
 });
--- a/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js
+++ b/devtools/client/inspector/test/browser_inspector_pseudoclass-lock.js
@@ -84,19 +84,18 @@ function* testNavigate(inspector, testAc
   ok(!(yield testActor.hasPseudoClassLock("#div-1", PSEUDO)),
      "pseudo-class lock is removed after inspecting sibling node");
 
   yield selectNode("#div-1", inspector);
   yield togglePseudoClass(inspector);
 }
 
 function* showPickerOn(selector, inspector) {
-  let highlighter = inspector.toolbox.highlighter;
   let nodeFront = yield getNodeFront(selector, inspector);
-  yield highlighter.showBoxModel(nodeFront);
+  yield inspector.highlighter.showBoxModel(nodeFront);
 }
 
 function* assertPseudoAddedToNode(inspector, testActor, ruleview) {
   info("Make sure the pseudoclass lock is applied to #div-1 and its ancestors");
 
   let hasLock = yield testActor.hasPseudoClassLock("#div-1", PSEUDO);
   ok(hasLock, "pseudo-class lock has been applied");
   hasLock = yield testActor.hasPseudoClassLock("#parent-div", PSEUDO);
@@ -114,17 +113,17 @@ function* assertPseudoAddedToNode(inspec
 
   info("Show the highlighter on #div-1");
   yield showPickerOn("#div-1", inspector);
 
   info("Check that the infobar selector contains the pseudo-class");
   let value = yield testActor.getHighlighterNodeTextContent(
     "box-model-infobar-pseudo-classes");
   is(value, PSEUDO, "pseudo-class in infobar selector");
-  yield inspector.toolbox.highlighter.hideBoxModel();
+  yield inspector.highlighter.hideBoxModel();
 }
 
 function* assertPseudoRemovedFromNode(testActor) {
   info("Make sure the pseudoclass lock is removed from #div-1 and its " +
        "ancestors");
 
   let hasLock = yield testActor.hasPseudoClassLock("#div-1", PSEUDO);
   ok(!hasLock, "pseudo-class lock has been removed");
@@ -140,10 +139,10 @@ function* assertPseudoRemovedFromView(in
     ".ruleview-rule.theme-separator");
   is(rules.length, 2, "rule view is showing 2 rules after removing lock");
 
   yield showPickerOn("#div-1", inspector);
 
   let value = yield testActor.getHighlighterNodeTextContent(
     "box-model-infobar-pseudo-classes");
   is(value, "", "pseudo-class removed from infobar selector");
-  yield inspector.toolbox.highlighter.hideBoxModel();
+  yield inspector.highlighter.hideBoxModel();
 }
--- a/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js
+++ b/devtools/client/inspector/test/browser_inspector_search-filter_context-menu.js
@@ -10,17 +10,17 @@ const TEST_URI = "<h1>test filter contex
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {toolbox, inspector} = yield openInspector();
   let {searchBox} = inspector;
   yield selectNode("h1", inspector);
 
   let win = inspector.panelWin;
-  let searchContextMenu = toolbox.textboxContextMenuPopup;
+  let searchContextMenu = toolbox.textBoxContextMenuPopup;
   ok(searchContextMenu,
     "The search filter context menu is loaded in the inspector");
 
   let cmdUndo = searchContextMenu.querySelector("[command=cmd_undo]");
   let cmdDelete = searchContextMenu.querySelector("[command=cmd_delete]");
   let cmdSelectAll = searchContextMenu.querySelector("[command=cmd_selectAll]");
   let cmdCut = searchContextMenu.querySelector("[command=cmd_cut]");
   let cmdCopy = searchContextMenu.querySelector("[command=cmd_copy]");
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_textbox-menu.js
@@ -0,0 +1,90 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that when right-clicking on various text boxes throughout the inspector does use
+// the toolbox's context menu (copy/cut/paste/selectAll/Undo).
+
+add_task(function* () {
+  yield addTab(`data:text/html;charset=utf-8,
+                <style>h1 { color: red; }</style>
+                <h1 id="title">textbox context menu test</h1>`);
+  let {toolbox, inspector} = yield openInspector();
+  yield selectNode("h1", inspector);
+
+  info("Testing the markup-view tagname");
+  let container = yield focusNode("h1", inspector);
+  let tag = container.editor.tag;
+  tag.focus();
+  EventUtils.sendKey("return", inspector.panelWin);
+  yield checkTextBox(inspector.markup.doc.activeElement, toolbox);
+
+  info("Testing the markup-view attribute");
+  EventUtils.sendKey("tab", inspector.panelWin);
+  yield checkTextBox(inspector.markup.doc.activeElement, toolbox);
+
+  info("Testing the markup-view new attribute");
+  // It takes 2 tabs to focus the newAttr field, the first one just moves the cursor to
+  // the end of the field.
+  EventUtils.sendKey("tab", inspector.panelWin);
+  EventUtils.sendKey("tab", inspector.panelWin);
+  yield checkTextBox(inspector.markup.doc.activeElement, toolbox);
+
+  info("Testing the markup-view textcontent");
+  EventUtils.sendKey("tab", inspector.panelWin);
+  yield checkTextBox(inspector.markup.doc.activeElement, toolbox);
+  // Blur this last markup-view field, since we're moving on to the rule-view next.
+  EventUtils.sendKey("escape", inspector.panelWin);
+
+  info("Testing the rule-view selector");
+  let ruleView = inspector.ruleview.view;
+  let cssRuleEditor = getRuleViewRuleEditor(ruleView, 1);
+  EventUtils.synthesizeMouse(cssRuleEditor.selectorText, 0, 0, {}, inspector.panelWin);
+  yield checkTextBox(inspector.panelDoc.activeElement, toolbox);
+
+  info("Testing the rule-view property name");
+  EventUtils.sendKey("tab", inspector.panelWin);
+  yield checkTextBox(inspector.panelDoc.activeElement, toolbox);
+
+  info("Testing the rule-view property value");
+  EventUtils.sendKey("tab", inspector.panelWin);
+  yield checkTextBox(inspector.panelDoc.activeElement, toolbox);
+
+  info("Testing the rule-view new property");
+  // Tabbing out of the value field triggers a ruleview-changed event that we need to wait
+  // for.
+  let onRuleViewChanged = once(ruleView, "ruleview-changed");
+  EventUtils.sendKey("tab", inspector.panelWin);
+  yield onRuleViewChanged;
+  yield checkTextBox(inspector.panelDoc.activeElement, toolbox);
+
+  info("Switching to the computed-view");
+  let onComputedViewReady = inspector.once("boxmodel-view-updated");
+  selectComputedView(inspector);
+  yield onComputedViewReady;
+
+  info("Testing the box-model region");
+  let margin = inspector.panelDoc.querySelector(".boxmodel-margin.boxmodel-top > span");
+  EventUtils.synthesizeMouseAtCenter(margin, {}, inspector.panelWin);
+  yield checkTextBox(inspector.panelDoc.activeElement, toolbox);
+});
+
+function* checkTextBox(textBox, {textBoxContextMenuPopup}) {
+  is(textBoxContextMenuPopup.state, "closed", "The menu is closed");
+
+  info("Simulating context click on the textbox and expecting the menu to open");
+  let onContextMenu = once(textBoxContextMenuPopup, "popupshown");
+  EventUtils.synthesizeMouse(textBox, 2, 2, {type: "contextmenu", button: 2},
+                             textBox.ownerDocument.defaultView);
+  yield onContextMenu;
+
+  is(textBoxContextMenuPopup.state, "open", "The menu is now visible");
+
+  info("Closing the menu");
+  let onContextMenuHidden = once(textBoxContextMenuPopup, "popuphidden");
+  textBoxContextMenuPopup.hidePopup();
+  yield onContextMenuHidden;
+
+  is(textBoxContextMenuPopup.state, "closed", "The menu is closed again");
+}
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -20,16 +20,17 @@ devtools.jar:
     content/webconsole/webconsole.xul (webconsole/webconsole.xul)
 *   content/scratchpad/scratchpad.xul (scratchpad/scratchpad.xul)
     content/scratchpad/scratchpad.js (scratchpad/scratchpad.js)
     content/shared/splitview.css (shared/splitview.css)
     content/shared/theme-switching.js (shared/theme-switching.js)
     content/shared/frame-script-utils.js (shared/frame-script-utils.js)
     content/styleeditor/styleeditor.xul (styleeditor/styleeditor.xul)
     content/storage/storage.xul (storage/storage.xul)
+    content/inspector/inspector.js (inspector/inspector.js)
     content/inspector/fonts/fonts.js (inspector/fonts/fonts.js)
     content/inspector/markup/markup.xhtml (inspector/markup/markup.xhtml)
     content/animationinspector/animation-controller.js (animationinspector/animation-controller.js)
     content/animationinspector/animation-panel.js (animationinspector/animation-panel.js)
     content/animationinspector/animation-inspector.xhtml (animationinspector/animation-inspector.xhtml)
     content/sourceeditor/codemirror/addon/dialog/dialog.css (sourceeditor/codemirror/addon/dialog/dialog.css)
     content/sourceeditor/codemirror/addon/hint/show-hint.js (sourceeditor/codemirror/addon/hint/show-hint.js)
     content/sourceeditor/codemirror/addon/tern/tern.js (sourceeditor/codemirror/addon/tern/tern.js)
--- a/devtools/client/shared/inplace-editor.js
+++ b/devtools/client/shared/inplace-editor.js
@@ -83,16 +83,18 @@ function isKeyIn(key, ...keys) {
  *       Called when input is committed or blurred.  Called with
  *       current value, a boolean telling the caller whether to
  *       commit the change, and the direction of the next element to be
  *       selected. Direction may be one of Services.focus.MOVEFOCUS_FORWARD,
  *       Services.focus.MOVEFOCUS_BACKWARD, or null (no movement).
  *       This function is called before the editor has been torn down.
  *    {Function} destroy:
  *       Called when the editor is destroyed and has been torn down.
+ *    {Function} contextMenu:
+ *       Called when the user triggers a contextmenu event on the input.
  *    {Object} advanceChars:
  *       This can be either a string or a function.
  *       If it is a string, then if any characters in it are typed,
  *       focus will advance to the next element.
  *       Otherwise, if it is a function, then the function will
  *       be called with three arguments: a key code, the current text,
  *       and the insertion point.  If the function returns true,
  *       then the focus advance takes place.  If it returns false,
@@ -217,16 +219,17 @@ exports.getInplaceEditorForSpan = getInp
 function InplaceEditor(options, event) {
   this.elt = options.element;
   let doc = this.elt.ownerDocument;
   this.doc = doc;
   this.elt.inplaceEditor = this;
   this.cssProperties = options.cssProperties;
   this.change = options.change;
   this.done = options.done;
+  this.contextMenu = options.contextMenu;
   this.destroy = options.destroy;
   this.initial = options.initial ? options.initial : this.elt.textContent;
   this.multiline = options.multiline || false;
   this.maxWidth = options.maxWidth;
   if (typeof this.maxWidth == "function") {
     this.maxWidth = this.maxWidth();
   }
 
@@ -244,16 +247,17 @@ function InplaceEditor(options, event) {
                           : !!options.preserveTextStyles;
 
   this._onBlur = this._onBlur.bind(this);
   this._onWindowBlur = this._onWindowBlur.bind(this);
   this._onKeyPress = this._onKeyPress.bind(this);
   this._onInput = this._onInput.bind(this);
   this._onKeyup = this._onKeyup.bind(this);
   this._onAutocompletePopupClick = this._onAutocompletePopupClick.bind(this);
+  this._onContextMenu = this._onContextMenu.bind(this);
 
   this._createInput();
 
   // Hide the provided element and add our editor.
   this.originalDisplay = this.elt.style.display;
   this.elt.style.display = "none";
   this.elt.parentNode.insertBefore(this.input, this.elt);
 
@@ -285,16 +289,17 @@ function InplaceEditor(options, event) {
   }
 
   this.input.addEventListener("blur", this._onBlur, false);
   this.input.addEventListener("keypress", this._onKeyPress, false);
   this.input.addEventListener("input", this._onInput, false);
   this.input.addEventListener("dblclick", this._stopEventPropagation, false);
   this.input.addEventListener("click", this._stopEventPropagation, false);
   this.input.addEventListener("mousedown", this._stopEventPropagation, false);
+  this.input.addEventListener("contextmenu", this._onContextMenu, false);
   this.doc.defaultView.addEventListener("blur", this._onWindowBlur, false);
 
   this.validate = options.validate;
 
   if (this.validate) {
     this.input.addEventListener("keyup", this._onKeyup, false);
   }
 
@@ -344,21 +349,20 @@ InplaceEditor.prototype = {
       // Already cleared.
       return;
     }
 
     this.input.removeEventListener("blur", this._onBlur, false);
     this.input.removeEventListener("keypress", this._onKeyPress, false);
     this.input.removeEventListener("keyup", this._onKeyup, false);
     this.input.removeEventListener("input", this._onInput, false);
-    this.input.removeEventListener("dblclick", this._stopEventPropagation,
-      false);
+    this.input.removeEventListener("dblclick", this._stopEventPropagation, false);
     this.input.removeEventListener("click", this._stopEventPropagation, false);
-    this.input.removeEventListener("mousedown", this._stopEventPropagation,
-      false);
+    this.input.removeEventListener("mousedown", this._stopEventPropagation, false);
+    this.input.removeEventListener("contextmenu", this._onContextMenu, false);
     this.doc.defaultView.removeEventListener("blur", this._onWindowBlur, false);
 
     this._stopAutosize();
 
     this.elt.style.display = this.originalDisplay;
 
     if (this.doc.activeElement == this.input) {
       this.elt.focus();
@@ -1159,16 +1163,22 @@ InplaceEditor.prototype = {
       prevent = !input.value;
     }
 
     if (prevent) {
       event.preventDefault();
     }
   },
 
+  _onContextMenu: function (event) {
+    if (this.contextMenu) {
+      this.contextMenu(event);
+    }
+  },
+
   /**
    * Open the autocomplete popup, adding a custom click handler and classname.
    *
    * @param {Number} offset
    *        X-offset relative to the input starting edge.
    * @param {Number} selectedIndex
    *        The index of the item that should be selected. Use -1 to have no
    *        item selected.
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -736,17 +736,17 @@ Heritage.extend(SwatchBasedEditorTooltip
       this._originalColor = this.currentSwatchColor.textContent;
       let color = this.activeSwatch.style.backgroundColor;
       this.spectrum.off("changed", this._onSpectrumColorChange);
       this.spectrum.rgb = this._colorToRgba(color);
       this.spectrum.on("changed", this._onSpectrumColorChange);
       this.spectrum.updateUI();
     }
 
-    let {target} = this.inspector.toolbox;
+    let {target} = this.inspector;
     target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
       let tooltipDoc = this.tooltip.doc;
       let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
       if (value && this.inspector.selection.nodeFront.isInHTMLDocument) {
         eyeButton.addEventListener("click", this._openEyeDropper);
       } else {
         eyeButton.style.display = "none";
       }
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -6,16 +6,24 @@
 :root {
   --eyedropper-image: url(images/command-eyedropper.svg);
 }
 
 .theme-firebug {
   --eyedropper-image: url(images/firebug/command-eyedropper.svg);
 }
 
+:root.theme-light {
+  --breadcrumbs-border-color: #f3f3f3;
+}
+
+:root.theme-dark {
+  --breadcrumbs-border-color: #454d5d;
+}
+
 /* Make sure to hide scroll bars for the parent window */
 window {
   overflow: hidden;
 }
 
 /* The main Inspector panel container. */
 .inspector-responsive-container {
   height: 100vh;
@@ -112,16 +120,20 @@ window {
 #inspector-sidebar-toggle-box {
   line-height: initial;
 }
 
 #inspector-breadcrumbs-toolbar {
   padding: 0px;
   border-bottom-width: 0px;
   border-top-width: 1px;
+  border-top-color: var(--breadcrumbs-border-color);
+  /* Bug 1262668 - Use the same background as the body so the breadcrumbs toolbar doesn't
+     get mistaken as a splitter */
+  background-color: var(--theme-body-background);
   display: block;
   position: relative;
 }
 
 #inspector-breadcrumbs-toolbar,
 #inspector-breadcrumbs-toolbar * {
   box-sizing: border-box;
 }
--- a/devtools/client/themes/widgets.css
+++ b/devtools/client/themes/widgets.css
@@ -209,17 +209,17 @@
 
 #breadcrumb-separator-before,
 #breadcrumb-separator-after:after {
   background: var(--theme-selection-background);
 }
 
 #breadcrumb-separator-after,
 #breadcrumb-separator-before:after {
-  background: var(--theme-toolbar-background);
+  background: var(--theme-body-background);
 }
 
 /* This chevron arrow cannot be replicated easily in CSS, so we are using
  * a background image for it (still keeping it in a separate element so
  * we can handle RTL support with a CSS transform).
  */
 #breadcrumb-separator-normal {
   background: url(images/breadcrumbs-divider@2x.png) no-repeat center right;