Merge mozilla-inbound to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Sun, 10 Feb 2019 11:43:56 +0200
changeset 458436 952b928f1605ad5676ae4ccfd41612b34523bae5
parent 458435 32b792a2a7a4c1876a2de67e752881ebcb3e08a3 (current diff)
parent 458425 fa161ab82839dd5dd8f50dc74df45615eb0cbe7a (diff)
child 458437 674fa918ee39ddcbe6d506717ce2835d77202129
child 458439 1b147d9934f1b16545afaea6f90b9fc92959b74d
child 458443 dbcbdca85269f32385ce7b54df4ae7df345963ef
push id111827
push userrmaries@mozilla.com
push dateSun, 10 Feb 2019 09:51:32 +0000
treeherdermozilla-inbound@674fa918ee39 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
952b928f1605 / 67.0a1 / 20190210094433 / files
nightly linux64
952b928f1605 / 67.0a1 / 20190210094433 / files
nightly mac
952b928f1605 / 67.0a1 / 20190210094433 / files
nightly win32
952b928f1605 / 67.0a1 / 20190210094433 / files
nightly win64
952b928f1605 / 67.0a1 / 20190210094433 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
--- a/devtools/client/inspector/flexbox/test/head.js
+++ b/devtools/client/inspector/flexbox/test/head.js
@@ -13,27 +13,23 @@ Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
   this);
 
 // Load the shared Redux helpers into this compartment.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/shared-redux-head.js",
   this);
 
-// Make sure the flexbox inspector is enabled before running the tests.
-Services.prefs.setBoolPref("devtools.flexboxinspector.enabled", true);
-
 // Make sure only the flexbox layout accordion is opened, and the others are closed.
 Services.prefs.setBoolPref("devtools.layout.flexbox.opened", true);
 Services.prefs.setBoolPref("devtools.layout.boxmodel.opened", false);
 Services.prefs.setBoolPref("devtools.layout.grid.opened", false);
 
 // Clear all set prefs.
 registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("devtools.flexboxinspector.enabled");
   Services.prefs.clearUserPref("devtools.layout.flexbox.opened");
   Services.prefs.clearUserPref("devtools.layout.boxmodel.opened");
   Services.prefs.clearUserPref("devtools.layout.grid.opened");
 });
 
 /**
  * Toggles ON the flexbox highlighter given the flexbox highlighter button from the
  * layout panel.
--- a/devtools/client/inspector/layout/components/LayoutApp.js
+++ b/devtools/client/inspector/layout/components/LayoutApp.js
@@ -25,17 +25,16 @@ const FlexboxTypes = require("devtools/c
 const GridTypes = require("devtools/client/inspector/grids/types");
 
 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties";
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 const LAYOUT_STRINGS_URI = "devtools/client/locales/layout.properties";
 const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI);
 
-const FLEXBOX_ENABLED_PREF = "devtools.flexboxinspector.enabled";
 const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened";
 const FLEXBOX_OPENED_PREF = "devtools.layout.flexbox.opened";
 const GRID_OPENED_PREF = "devtools.layout.grid.opened";
 
 class LayoutApp extends PureComponent {
   static get propTypes() {
     return {
       boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired,
@@ -84,18 +83,35 @@ class LayoutApp extends PureComponent {
   /**
    * Scrolls to top of the layout container.
    */
   scrollToTop() {
     this.containerRef.current.scrollTop = 0;
   }
 
   render() {
+    const { flexContainer, flexItemContainer } = this.props.flexbox;
+
     const items = [
       {
+        className: `flex-accordion ${flexContainer.flexItemShown ? "item" : "container"}`,
+        component: Flexbox,
+        componentProps: {
+          ...this.props,
+          flexContainer,
+          scrollToTop: this.scrollToTop,
+        },
+        header: this.getFlexboxHeader(flexContainer),
+        opened: Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF),
+        onToggled: () => {
+          Services.prefs.setBoolPref(FLEXBOX_OPENED_PREF,
+            !Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF));
+        },
+      },
+      {
         component: Grid,
         componentProps: this.props,
         header: LAYOUT_L10N.getStr("layout.header"),
         opened: Services.prefs.getBoolPref(GRID_OPENED_PREF),
         onToggled: () => {
           const opened = Services.prefs.getBoolPref(GRID_OPENED_PREF);
           Services.prefs.setBoolPref(GRID_OPENED_PREF, !opened);
         },
@@ -107,63 +123,40 @@ class LayoutApp extends PureComponent {
         opened: Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF),
         onToggled: () => {
           const opened = Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF);
           Services.prefs.setBoolPref(BOXMODEL_OPENED_PREF, !opened);
         },
       },
     ];
 
-    if (Services.prefs.getBoolPref(FLEXBOX_ENABLED_PREF)) {
-      const { flexContainer, flexItemContainer } = this.props.flexbox;
-      const opened = Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF);
-
-      // Since the flexbox panel is hidden behind a pref. We insert the flexbox container
-      // to the first index of the accordion item list.
-      items.splice(0, 0, {
-        className: `flex-accordion ${flexContainer.flexItemShown ? "item" : "container"}`,
+    // If the current selected node is both a flex container and flex item. Render
+    // an extra accordion with another Flexbox component where the node is shown as an
+    // item of its parent flex container.
+    // If the node was selected from the markup-view, then show this accordion after the
+    // container accordion. Otherwise show it first.
+    // The reason is that if the user selects an item-container in the markup view, it
+    // is assumed that they want to primarily see that element as a container, so the
+    // container info should be at the top.
+    if (flexItemContainer && flexItemContainer.actorID) {
+      items.splice(this.props.flexbox.initiatedByMarkupViewSelection ? 1 : 0, 0, {
+        className: "flex-accordion item",
         component: Flexbox,
         componentProps: {
           ...this.props,
-          flexContainer,
+          flexContainer: flexItemContainer,
           scrollToTop: this.scrollToTop,
         },
-        header: this.getFlexboxHeader(flexContainer),
-        opened,
+        header: this.getFlexboxHeader(flexItemContainer),
+        opened: Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF),
         onToggled: () => {
           Services.prefs.setBoolPref(FLEXBOX_OPENED_PREF,
             !Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF));
         },
       });
-
-      // If the current selected node is both a flex container and flex item. Render
-      // an extra accordion with another Flexbox component where the node is shown as an
-      // item of its parent flex container.
-      // If the node was selected from the markup-view, then show this accordion after the
-      // container accordion. Otherwise show it first.
-      // The reason is that if the user selects an item-container in the markup view, it
-      // is assumed that they want to primarily see that element as a container, so the
-      // container info should be at the top.
-      if (flexItemContainer && flexItemContainer.actorID) {
-        items.splice(this.props.flexbox.initiatedByMarkupViewSelection ? 1 : 0, 0, {
-          className: "flex-accordion item",
-          component: Flexbox,
-          componentProps: {
-            ...this.props,
-            flexContainer: flexItemContainer,
-            scrollToTop: this.scrollToTop,
-          },
-          header: this.getFlexboxHeader(flexItemContainer),
-          opened,
-          onToggled: () => {
-            Services.prefs.setBoolPref(FLEXBOX_OPENED_PREF,
-              !Services.prefs.getBoolPref(FLEXBOX_OPENED_PREF));
-          },
-        });
-      }
     }
 
     return (
       dom.div({ className: "layout-container", ref: this.containerRef },
         Accordion({ items })
       )
     );
   }
--- a/devtools/client/inspector/markup/test/browser_markup_display_node_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_display_node_01.js
@@ -28,18 +28,16 @@ const TEST_URI = `
   <div id="flex">Flex</div>
   <div id="block">Block</div>
   <span>HELLO WORLD</span>
 `;
 
 add_task(async function() {
   info("Enable subgrid in order to see the subgrid display type.");
   await pushPref("layout.css.grid-template-subgrid-value.enabled", true);
-  info("Enable the flexbox highlighter to get the interactive flex display badge.");
-  await pushPref("devtools.inspector.flexboxHighlighter.enabled", true);
 
   const {inspector} = await openInspectorForURL("data:text/html;charset=utf-8," +
     encodeURIComponent(TEST_URI));
 
   info("Check the display node is shown and the value of #grid.");
   await selectNode("#grid", inspector);
   const gridContainer = await getContainerForSelector("#grid", inspector);
   const gridDisplayNode = gridContainer.elt.querySelector(
--- a/devtools/client/inspector/markup/test/browser_markup_display_node_02.js
+++ b/devtools/client/inspector/markup/test/browser_markup_display_node_02.js
@@ -93,19 +93,16 @@ const TEST_DATA = [
     after: {
       textContent: "flex",
       visible: true,
     },
   },
 ];
 
 add_task(async function() {
-  info("Enable the flexbox highlighter to get the interactive flex display badge.");
-  await pushPref("devtools.inspector.flexboxHighlighter.enabled", true);
-
   const {inspector, testActor} = await openInspectorForURL(
     "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
 
   for (const data of TEST_DATA) {
     info("Running test case: " + data.desc);
     await runTestData(inspector, testActor, data);
   }
 });
--- a/devtools/client/inspector/markup/test/browser_markup_flex_display_badge.js
+++ b/devtools/client/inspector/markup/test/browser_markup_flex_display_badge.js
@@ -12,18 +12,16 @@ const TEST_URI = `
     }
   </style>
   <div id="flex"></div>
 `;
 
 const HIGHLIGHTER_TYPE = "FlexboxHighlighter";
 
 add_task(async function() {
-  await pushPref("devtools.inspector.flexboxHighlighter.enabled", true);
-  await pushPref("devtools.flexboxinspector.enabled", true);
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   const { inspector } = await openLayoutView();
   const { highlighters, store } = inspector;
 
   info("Check the flex display badge is shown and not active.");
   await selectNode("#flex", inspector);
   const flexContainer = await getContainerForSelector("#flex", inspector);
   const flexDisplayBadge = flexContainer.elt.querySelector(
--- a/devtools/client/inspector/markup/test/browser_markup_flex_display_badge_telemetry.js
+++ b/devtools/client/inspector/markup/test/browser_markup_flex_display_badge_telemetry.js
@@ -11,18 +11,16 @@ const TEST_URI = `
     #flex {
       display: flex;
     }
   </style>
   <div id="flex"></div>
 `;
 
 add_task(async function() {
-  await pushPref("devtools.inspector.flexboxHighlighter.enabled", true);
-  await pushPref("devtools.flexboxinspector.enabled", true);
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   startTelemetry();
   const { inspector } = await openLayoutView();
   const { highlighters } = inspector;
 
   await selectNode("#flex", inspector);
   const flexContainer = await getContainerForSelector("#flex", inspector);
   const flexDisplayBadge = flexContainer.elt.querySelector(
--- a/devtools/client/inspector/markup/views/element-editor.js
+++ b/devtools/client/inspector/markup/views/element-editor.js
@@ -357,18 +357,17 @@ ElementEditor.prototype = {
     this._displayBadge.textContent = displayType;
     this._displayBadge.dataset.display = displayType;
     this._displayBadge.title = DISPLAY_TYPES[displayType];
     this._displayBadge.classList.toggle("active",
       this.highlighters.flexboxHighlighterShown === this.node ||
       this.highlighters.gridHighlighters.has(this.node));
 
     if (displayType === "flex" || displayType === "inline-flex") {
-      this._displayBadge.classList.toggle("interactive",
-        Services.prefs.getBoolPref("devtools.inspector.flexboxHighlighter.enabled"));
+      this._displayBadge.classList.toggle("interactive", true);
     } else if (displayType === "grid" || displayType === "inline-grid") {
       this._displayBadge.classList.toggle("interactive",
         this.highlighters.canGridHighlighterToggle(this.node));
     } else {
       this._displayBadge.classList.remove("interactive");
     }
   },
 
@@ -762,18 +761,17 @@ ElementEditor.prototype = {
    * Called when the display badge is clicked. Toggles on the flex/grid highlighter for
    * the selected node if it is a grid container.
    */
   onDisplayBadgeClick: async function(event) {
     event.stopPropagation();
 
     const target = event.target;
 
-    if (Services.prefs.getBoolPref("devtools.inspector.flexboxHighlighter.enabled") &&
-        (target.dataset.display === "flex" || target.dataset.display === "inline-flex")) {
+    if (target.dataset.display === "flex" || target.dataset.display === "inline-flex") {
       // Stop tracking highlighter events to avoid flickering of the active class.
       this.stopTrackingFlexboxHighlighterEvents();
 
       this._displayBadge.classList.toggle("active");
       await this.highlighters.toggleFlexboxHighlighter(this.node, "markup");
 
       this.startTrackingFlexboxHighlighterEvents();
     }
--- a/devtools/client/inspector/rules/actions/index.js
+++ b/devtools/client/inspector/rules/actions/index.js
@@ -29,9 +29,15 @@ createEnum([
   "UPDATE_CLASS_PANEL_EXPANDED",
 
   // Updates the highlighted selector.
   "UPDATE_HIGHLIGHTED_SELECTOR",
 
   // Updates the rules state with the new list of CSS rules for the selected element.
   "UPDATE_RULES",
 
+  // Updates whether or not the source links are enabled.
+  "UPDATE_SOURCE_LINK_ENABLED",
+
+  // Updates the source link information for a given rule.
+  "UPDATE_SOURCE_LINK",
+
 ], module.exports);
--- a/devtools/client/inspector/rules/actions/rules.js
+++ b/devtools/client/inspector/rules/actions/rules.js
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   UPDATE_ADD_RULE_ENABLED,
   UPDATE_HIGHLIGHTED_SELECTOR,
   UPDATE_RULES,
+  UPDATE_SOURCE_LINK_ENABLED,
+  UPDATE_SOURCE_LINK,
 } = require("./index");
 
 module.exports = {
 
   /**
    * Updates whether or not the add new rule button should be enabled.
    *
    * @param  {Boolean} enabled
@@ -46,9 +48,38 @@ module.exports = {
    */
   updateRules(rules) {
     return {
       type: UPDATE_RULES,
       rules,
     };
   },
 
+  /**
+   * Updates whether or not the source links are enabled.
+   *
+   * @param  {Boolean} enabled
+   *         Whether or not the source links are enabled.
+   */
+  updateSourceLinkEnabled(enabled) {
+    return {
+      type: UPDATE_SOURCE_LINK_ENABLED,
+      enabled,
+    };
+  },
+
+  /**
+   * Updates the source link information for a given rule.
+   *
+   * @param  {String} ruleId
+   *         The Rule id of the target rule.
+   * @param  {Object} sourceLink
+   *         New source link data.
+   */
+  updateSourceLink(ruleId, sourceLink) {
+    return {
+      type: UPDATE_SOURCE_LINK,
+      ruleId,
+      sourceLink,
+    };
+  },
+
 };
--- a/devtools/client/inspector/rules/components/Rule.js
+++ b/devtools/client/inspector/rules/components/Rule.js
@@ -14,16 +14,17 @@ const Selector = createFactory(require("
 const SelectorHighlighter = createFactory(require("./SelectorHighlighter"));
 const SourceLink = createFactory(require("./SourceLink"));
 
 const Types = require("../types");
 
 class Rule extends PureComponent {
   static get propTypes() {
     return {
+      onOpenSourceLink: PropTypes.func.isRequired,
       onToggleDeclaration: PropTypes.func.isRequired,
       onToggleSelectorHighlighter: PropTypes.func.isRequired,
       rule: PropTypes.shape(Types.rule).isRequired,
       showDeclarationNameEditor: PropTypes.func.isRequired,
       showDeclarationValueEditor: PropTypes.func.isRequired,
       showNewDeclarationEditor: PropTypes.func.isRequired,
       showSelectorEditor: PropTypes.func.isRequired,
     };
@@ -58,16 +59,17 @@ class Rule extends PureComponent {
   }
 
   onEditorBlur() {
     this.setState({ isNewDeclarationEditorVisible: false });
   }
 
   render() {
     const {
+      onOpenSourceLink,
       onToggleDeclaration,
       onToggleSelectorHighlighter,
       rule,
       showDeclarationNameEditor,
       showDeclarationValueEditor,
       showSelectorEditor,
     } = this.props;
     const {
@@ -82,17 +84,23 @@ class Rule extends PureComponent {
 
     return (
       dom.div(
         {
           className: "ruleview-rule devtools-monospace" +
                      (isUnmatched ? " unmatched" : "") +
                      (isUserAgentStyle ? " uneditable" : ""),
         },
-        SourceLink({ sourceLink }),
+        SourceLink({
+          id,
+          isUserAgentStyle,
+          onOpenSourceLink,
+          sourceLink,
+          type,
+        }),
         dom.div({ className: "ruleview-code" },
           dom.div({},
             Selector({
               id,
               isUserAgentStyle,
               selector,
               showSelectorEditor,
               type,
--- a/devtools/client/inspector/rules/components/Rules.js
+++ b/devtools/client/inspector/rules/components/Rules.js
@@ -9,40 +9,43 @@ const PropTypes = require("devtools/clie
 
 const Rule = createFactory(require("./Rule"));
 
 const Types = require("../types");
 
 class Rules extends PureComponent {
   static get propTypes() {
     return {
+      onOpenSourceLink: PropTypes.func.isRequired,
       onToggleDeclaration: PropTypes.func.isRequired,
       onToggleSelectorHighlighter: PropTypes.func.isRequired,
       rules: PropTypes.arrayOf(PropTypes.shape(Types.rule)).isRequired,
       showDeclarationNameEditor: PropTypes.func.isRequired,
       showDeclarationValueEditor: PropTypes.func.isRequired,
       showNewDeclarationEditor: PropTypes.func.isRequired,
       showSelectorEditor: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
+      onOpenSourceLink,
       onToggleDeclaration,
       onToggleSelectorHighlighter,
       rules,
       showDeclarationNameEditor,
       showDeclarationValueEditor,
       showNewDeclarationEditor,
       showSelectorEditor,
     } = this.props;
 
     return rules.map(rule => {
       return Rule({
         key: rule.id,
+        onOpenSourceLink,
         onToggleDeclaration,
         onToggleSelectorHighlighter,
         rule,
         showDeclarationNameEditor,
         showDeclarationValueEditor,
         showNewDeclarationEditor,
         showSelectorEditor,
       });
--- a/devtools/client/inspector/rules/components/RulesApp.js
+++ b/devtools/client/inspector/rules/components/RulesApp.js
@@ -25,31 +25,33 @@ const Types = require("../types");
 
 const SHOW_PSEUDO_ELEMENTS_PREF = "devtools.inspector.show_pseudo_elements";
 
 class RulesApp extends PureComponent {
   static get propTypes() {
     return {
       onAddClass: PropTypes.func.isRequired,
       onAddRule: PropTypes.func.isRequired,
+      onOpenSourceLink: PropTypes.func.isRequired,
       onSetClassState: PropTypes.func.isRequired,
       onToggleClassPanelExpanded: PropTypes.func.isRequired,
       onToggleDeclaration: PropTypes.func.isRequired,
       onTogglePseudoClass: PropTypes.func.isRequired,
       onToggleSelectorHighlighter: PropTypes.func.isRequired,
       rules: PropTypes.arrayOf(PropTypes.shape(Types.rule)).isRequired,
       showDeclarationNameEditor: PropTypes.func.isRequired,
       showDeclarationValueEditor: PropTypes.func.isRequired,
       showNewDeclarationEditor: PropTypes.func.isRequired,
       showSelectorEditor: PropTypes.func.isRequired,
     };
   }
 
   getRuleProps() {
     return {
+      onOpenSourceLink: this.props.onOpenSourceLink,
       onToggleDeclaration: this.props.onToggleDeclaration,
       onToggleSelectorHighlighter: this.props.onToggleSelectorHighlighter,
       showDeclarationNameEditor: this.props.showDeclarationNameEditor,
       showDeclarationValueEditor: this.props.showDeclarationValueEditor,
       showNewDeclarationEditor: this.props.showNewDeclarationEditor,
       showSelectorEditor: this.props.showSelectorEditor,
     };
   }
--- a/devtools/client/inspector/rules/components/SourceLink.js
+++ b/devtools/client/inspector/rules/components/SourceLink.js
@@ -2,32 +2,77 @@
  * 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";
 
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { ELEMENT_STYLE } = require("devtools/client/inspector/rules/constants");
 
 const Types = require("../types");
 
 class SourceLink extends PureComponent {
   static get propTypes() {
     return {
+      id: PropTypes.string.isRequired,
+      isSourceLinkEnabled: PropTypes.bool.isRequired,
+      isUserAgentStyle: PropTypes.bool.isRequired,
+      onOpenSourceLink: PropTypes.func.isRequired,
       sourceLink: PropTypes.shape(Types.sourceLink).isRequired,
+      type: PropTypes.number.isRequired,
     };
   }
 
+  constructor(props) {
+    super(props);
+    this.onSourceClick = this.onSourceClick.bind(this);
+  }
+
+  /**
+   * Whether or not the source link is enabled. The source link is enabled when the
+   * Style Editor is enabled and the rule is not a user agent or element style.
+   */
+  get isSourceLinkEnabled() {
+    return this.props.isSourceLinkEnabled &&
+           !this.props.isUserAgentStyle &&
+           this.props.type !== ELEMENT_STYLE;
+  }
+
+  onSourceClick(event) {
+    event.stopPropagation();
+
+    if (this.isSourceLinkEnabled) {
+      this.props.onOpenSourceLink(this.props.id);
+    }
+  }
+
   render() {
-    const { sourceLink } = this.props;
+    const { label, title } = this.props.sourceLink;
 
     return (
-      dom.div({ className: "ruleview-rule-source theme-link" },
-        dom.span({ className: "ruleview-rule-source-label" },
-          sourceLink.title
+      dom.div(
+        {
+          className: "ruleview-rule-source theme-link" +
+                     (!this.isSourceLinkEnabled ? " disabled" : ""),
+          onClick: this.onSourceClick,
+        },
+        dom.span(
+          {
+            className: "ruleview-rule-source-label",
+            title,
+          },
+          label
         )
       )
     );
   }
 }
 
-module.exports = SourceLink;
+const mapStateToProps = state => {
+  return {
+    isSourceLinkEnabled: state.rules.isSourceLinkEnabled,
+  };
+};
+
+module.exports = connect(mapStateToProps)(SourceLink);
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -69,16 +69,18 @@ class ElementStyle {
     }
 
     this.destroyed = true;
 
     for (const rule of this.rules) {
       if (rule.editor) {
         rule.editor.destroy();
       }
+
+      rule.destroy();
     }
 
     if (this.ruleView.isNewRulesView) {
       this.pageStyle.off("stylesheet-updated", this.onStyleSheetUpdated);
     }
   }
 
   /**
@@ -118,16 +120,20 @@ class ElementStyle {
         this._maybeAddRule(entry, existingRules);
       }
 
       // Mark overridden computed styles.
       this.markOverriddenAll();
 
       this._sortRulesForPseudoElement();
 
+      if (this.ruleView.isNewRulesView) {
+        this.subscribeRulesToLocationChange();
+      }
+
       // We're done with the previous list of rules.
       for (const r of existingRules) {
         if (r && r.editor) {
           r.editor.destroy();
         }
       }
 
       return undefined;
@@ -344,20 +350,20 @@ class ElementStyle {
         textProp.updateEditor();
       }
     }
   }
 
   /**
    * Adds a new declaration to the rule.
    *
-   * @param {String} ruleId
-   *        The id of the Rule to be modified.
-   * @param {String} value
-   *        The new declaration value.
+   * @param  {String} ruleId
+   *         The id of the Rule to be modified.
+   * @param  {String} value
+   *         The new declaration value.
    */
   addNewDeclaration(ruleId, value) {
     const rule = this.getRule(ruleId);
     if (!rule) {
       return;
     }
 
     const declarationsToAdd = parseNamedDeclarations(this.cssProperties.isKnown,
@@ -520,20 +526,20 @@ class ElementStyle {
     }
 
     this._addMultipleDeclarations(rule, declarationsToAdd, declaration);
   }
 
   /**
    * Modifies the existing rule's selector to the new given value.
    *
-   * @param {String} ruleId
-   *        The id of the Rule to be modified.
-   * @param {String} selector
-   *        The new selector value.
+   * @param  {String} ruleId
+   *         The id of the Rule to be modified.
+   * @param  {String} selector
+   *         The new selector value.
    */
   async modifySelector(ruleId, selector) {
     try {
       const rule = this.getRule(ruleId);
       if (!rule) {
         return;
       }
 
@@ -585,22 +591,31 @@ class ElementStyle {
 
       this._changed();
     } catch (e) {
       console.error(e);
     }
   }
 
   /**
+   * Subscribes all the rules to location changes.
+   */
+  subscribeRulesToLocationChange() {
+    for (const rule of this.rules) {
+      rule.subscribeToLocationChange();
+    }
+  }
+
+  /**
    * Toggles the enabled state of the given CSS declaration.
    *
-   * @param {String} ruleId
-   *        The Rule id of the given CSS declaration.
-   * @param {String} declarationId
-   *        The TextProperty id for the CSS declaration.
+   * @param  {String} ruleId
+   *         The Rule id of the given CSS declaration.
+   * @param  {String} declarationId
+   *         The TextProperty id for the CSS declaration.
    */
   toggleDeclaration(ruleId, declarationId) {
     const rule = this.getRule(ruleId);
     if (!rule) {
       return;
     }
 
     const declaration = rule.getDeclaration(declarationId);
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -7,16 +7,17 @@
 "use strict";
 
 const promise = require("promise");
 const CssLogic = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const TextProperty = require("devtools/client/inspector/rules/models/text-property");
 const Services = require("Services");
 
+loader.lazyRequireGetter(this, "updateSourceLink", "devtools/client/inspector/rules/actions/rules", true);
 loader.lazyRequireGetter(this, "promiseWarn", "devtools/client/inspector/shared/utils", true);
 loader.lazyRequireGetter(this, "parseNamedDeclarations", "devtools/shared/css/parsing-utils", true);
 
 const STYLE_INSPECTOR_PROPERTIES = "devtools/shared/locales/styleinspector.properties";
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const STYLE_INSPECTOR_L10N = new LocalizationHelper(STYLE_INSPECTOR_PROPERTIES);
 
 /**
@@ -45,23 +46,35 @@ class Rule {
     this.pseudoElement = options.pseudoElement || "";
     this.isSystem = options.isSystem;
     this.isUnmatched = options.isUnmatched || false;
     this.inherited = options.inherited || null;
     this.keyframes = options.keyframes || null;
 
     this.mediaText = this.domRule && this.domRule.mediaText ? this.domRule.mediaText : "";
     this.cssProperties = this.elementStyle.ruleView.cssProperties;
+    this.inspector = this.elementStyle.ruleView.inspector;
+    this.store = this.elementStyle.ruleView.store;
 
     // Populate the text properties with the style's current authoredText
     // value, and add in any disabled properties from the store.
     this.textProps = this._getTextProperties();
     this.textProps = this.textProps.concat(this._getDisabledProperties());
 
     this.getUniqueSelector = this.getUniqueSelector.bind(this);
+    this.onLocationChanged = this.onLocationChanged.bind(this);
+    this.updateSourceLocation = this.updateSourceLocation.bind(this);
+  }
+
+  destroy() {
+    if (this.unsubscribeSourceMap) {
+      this.unsubscribeSourceMap();
+    }
+
+    this.domRule.off("location-changed", this.onLocationChanged);
   }
 
   get declarations() {
     return this.textProps;
   }
 
   get inheritance() {
     if (!this.inherited) {
@@ -80,23 +93,41 @@ class Rule {
       matchedSelectors: this.matchedSelectors,
       selectors: this.domRule.selectors,
       selectorText: this.keyframes ? this.domRule.keyText : this.selectorText,
     };
   }
 
   get sourceLink() {
     return {
-      column: this.ruleColumn,
-      line: this.ruleLine,
-      mediaText: this.mediaText,
-      title: this.title,
+      label: this.getSourceText(CssLogic.shortSource({ href: this.sourceLocation.url })),
+      title: this.getSourceText(this.sourceLocation.url),
     };
   }
 
+  get sourceMapURLService() {
+    return this.inspector.toolbox.sourceMapURLService;
+  }
+
+  /**
+   * Returns the original source location which includes the original URL, line and
+   * column numbers.
+   */
+  get sourceLocation() {
+    if (!this._sourceLocation) {
+      this._sourceLocation = {
+        column: this.ruleColumn,
+        line: this.ruleLine,
+        url: this.sheet ? this.sheet.href || this.sheet.nodeHref : null,
+      };
+    }
+
+    return this._sourceLocation;
+  }
+
   get title() {
     let title = CssLogic.shortSource(this.sheet);
     if (this.domRule.type !== ELEMENT_STYLE && this.ruleLine > 0) {
       title += ":" + this.ruleLine;
     }
 
     return title + (this.mediaText ? " @media " + this.mediaText : "");
   }
@@ -174,31 +205,56 @@ class Rule {
    * @return {TextProperty|undefined} with the given id in the current Rule or undefined
    * if it cannot be found.
    */
   getDeclaration(id) {
     return this.textProps.find(textProp => textProp.id === id);
   }
 
   /**
+   * Returns a formatted source text of the given stylesheet URL with its source line
+   * and @media text.
+   *
+   * @param  {String} url
+   *         The stylesheet URL.
+   */
+  getSourceText(url) {
+    if (this.isSystem) {
+      return `${STYLE_INSPECTOR_L10N.getStr("rule.userAgentStyles")} ${this.title}`;
+    }
+
+    let sourceText = url;
+
+    if (this.sourceLocation.line > 0) {
+      sourceText += ":" + this.sourceLocation.line;
+    }
+
+    if (this.mediaText) {
+      sourceText += " @media " + this.mediaText;
+    }
+
+    return sourceText;
+  }
+
+  /**
    * Returns an unique selector for the CSS rule.
    */
   async getUniqueSelector() {
     let selector = "";
 
     if (this.domRule.selectors) {
       // This is a style rule with a selector.
       selector = this.domRule.selectors.join(", ");
     } else if (this.inherited) {
       // This is an inline style from an inherited rule. Need to resolve the unique
       // selector from the node which rule this is inherited from.
       selector = await this.inherited.getUniqueSelector();
     } else {
       // This is an inline style from the current node.
-      selector = this.elementStyle.ruleView.inspector.selectionCssSelector;
+      selector = this.inspector.selectionCssSelector;
     }
 
     return selector;
   }
 
   /**
    * Returns true if the rule matches the creation options
    * specified.
@@ -739,11 +795,62 @@ class Rule {
   hasAnyVisibleProperties() {
     for (const prop of this.textProps) {
       if (!prop.invisible) {
         return true;
       }
     }
     return false;
   }
+
+  /**
+   * Handler for "location-changed" events fired from the StyleRuleActor. This could
+   * occur by adding a new declaration to the rule. Updates the source location of the
+   * rule. This will overwrite the source map location.
+   */
+  onLocationChanged() {
+    const url = this.sheet ? this.sheet.href || this.sheet.nodeHref : null;
+    this.updateSourceLocation(url, this.ruleLine, this.ruleColumn);
+  }
+
+  /**
+   * Subscribes the rule to the source map service to map the the original source
+   * location.
+   */
+  subscribeToLocationChange() {
+    const { url, line, column } = this.sourceLocation;
+
+    if (url && !this.isSystem && this.domRule.type !== ELEMENT_STYLE) {
+      // Subscribe returns an unsubscribe function that can be called on destroy.
+      this.unsubscribeSourceMap = this.sourceMapURLService.subscribe(url, line, column,
+        (enabled, sourceUrl, sourceLine, sourceColumn) => {
+          if (enabled) {
+            // Only update the source location if source map is in use.
+            this.updateSourceLocation(sourceUrl, sourceLine, sourceColumn);
+          }
+        });
+    }
+
+    this.domRule.on("location-changed", this.onLocationChanged);
+  }
+
+  /**
+   * Handler for any location changes called from the SourceMapURLService and can also be
+   * called from onLocationChanged(). Updates the source location for the rule.
+   *
+   * @param  {String} url
+   *         The original URL.
+   * @param  {Number} line
+   *         The original line number.
+   * @param  {number} column
+   *         The original column number.
+   */
+  updateSourceLocation(url, line, column) {
+    this._sourceLocation = {
+      column,
+      line,
+      url,
+    };
+    this.store.dispatch(updateSourceLink(this.domRule.actorID, this.sourceLink));
+  }
 }
 
 module.exports = Rule;
--- a/devtools/client/inspector/rules/new-rules.js
+++ b/devtools/client/inspector/rules/new-rules.js
@@ -18,24 +18,27 @@ const {
   disableAllPseudoClasses,
   setPseudoClassLocks,
   togglePseudoClass,
 } = require("./actions/pseudo-classes");
 const {
   updateAddRuleEnabled,
   updateHighlightedSelector,
   updateRules,
+  updateSourceLinkEnabled,
 } = require("./actions/rules");
 
 const RulesApp = createFactory(require("./components/RulesApp"));
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
+loader.lazyRequireGetter(this, "Tools", "devtools/client/definitions", true);
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "ClassList", "devtools/client/inspector/rules/models/class-list");
 loader.lazyRequireGetter(this, "advanceValidate", "devtools/client/inspector/shared/utils", true);
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
 loader.lazyRequireGetter(this, "InplaceEditor", "devtools/client/shared/inplace-editor", true);
 
 const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
 
 class RulesView {
@@ -49,46 +52,51 @@ class RulesView {
     this.telemetry = inspector.telemetry;
     this.toolbox = inspector.toolbox;
     this.isNewRulesView = true;
 
     this.showUserAgentStyles = Services.prefs.getBoolPref(PREF_UA_STYLES);
 
     this.onAddClass = this.onAddClass.bind(this);
     this.onAddRule = this.onAddRule.bind(this);
+    this.onOpenSourceLink = this.onOpenSourceLink.bind(this);
     this.onSelection = this.onSelection.bind(this);
     this.onSetClassState = this.onSetClassState.bind(this);
     this.onToggleClassPanelExpanded = this.onToggleClassPanelExpanded.bind(this);
     this.onToggleDeclaration = this.onToggleDeclaration.bind(this);
     this.onTogglePseudoClass = this.onTogglePseudoClass.bind(this);
+    this.onToolChanged = this.onToolChanged.bind(this);
     this.onToggleSelectorHighlighter = this.onToggleSelectorHighlighter.bind(this);
     this.showDeclarationNameEditor = this.showDeclarationNameEditor.bind(this);
     this.showDeclarationValueEditor = this.showDeclarationValueEditor.bind(this);
     this.showNewDeclarationEditor = this.showNewDeclarationEditor.bind(this);
     this.showSelectorEditor = this.showSelectorEditor.bind(this);
     this.updateClassList = this.updateClassList.bind(this);
     this.updateRules = this.updateRules.bind(this);
 
     this.inspector.sidebar.on("select", this.onSelection);
     this.selection.on("detached-front", this.onSelection);
     this.selection.on("new-node-front", this.onSelection);
+    this.toolbox.on("tool-registered", this.onToolChanged);
+    this.toolbox.on("tool-unregistered", this.onToolChanged);
 
     this.init();
 
     EventEmitter.decorate(this);
   }
 
   init() {
     if (!this.inspector) {
       return;
     }
 
     const rulesApp = RulesApp({
       onAddClass: this.onAddClass,
       onAddRule: this.onAddRule,
+      onOpenSourceLink: this.onOpenSourceLink,
       onSetClassState: this.onSetClassState,
       onToggleClassPanelExpanded: this.onToggleClassPanelExpanded,
       onToggleDeclaration: this.onToggleDeclaration,
       onTogglePseudoClass: this.onTogglePseudoClass,
       onToggleSelectorHighlighter: this.onToggleSelectorHighlighter,
       showDeclarationNameEditor: this.showDeclarationNameEditor,
       showDeclarationValueEditor: this.showDeclarationValueEditor,
       showNewDeclarationEditor: this.showNewDeclarationEditor,
@@ -105,16 +113,18 @@ class RulesView {
     // Exposes the provider to let inspector.js use it in setupSidebar.
     this.provider = provider;
   }
 
   destroy() {
     this.inspector.sidebar.off("select", this.onSelection);
     this.selection.off("detached-front", this.onSelection);
     this.selection.off("new-node-front", this.onSelection);
+    this.toolbox.off("tool-registered", this.onToolChanged);
+    this.toolbox.off("tool-unregistered", this.onToolChanged);
 
     if (this._autocompletePopup) {
       this._autocompletePopup.destroy();
       this._autocompletePopup = null;
     }
 
     if (this._classList) {
       this._classList.off("current-node-class-changed", this.refreshClassList);
@@ -278,16 +288,38 @@ class RulesView {
   /**
    * Handler for adding a new CSS rule.
    */
   async onAddRule() {
     await this.elementStyle.addNewRule();
   }
 
   /**
+   * Handler for opening the source link of the given rule in the Style Editor.
+   *
+   * @param  {String} ruleId
+   *         The id of the Rule for opening the source link.
+   */
+  async onOpenSourceLink(ruleId) {
+    const rule = this.elementStyle.getRule(ruleId);
+    if (!rule || !Tools.styleEditor.isTargetSupported(this.inspector.target)) {
+      return;
+    }
+
+    const toolbox = await gDevTools.showToolbox(this.inspector.target, "styleeditor");
+    const styleEditor = toolbox.getCurrentPanel();
+    if (!styleEditor) {
+      return;
+    }
+
+    const { url, line, column } = rule.sourceLocation;
+    styleEditor.selectStyleSheet(url, line, column);
+  }
+
+  /**
    * Handler for selection events "detached-front" and "new-node-front" and inspector
    * sidbar "select" event. Updates the rules view with the selected node if the panel
    * is visible.
    */
   onSelection() {
     if (!this.isPanelVisible()) {
       return;
     }
@@ -393,16 +425,30 @@ class RulesView {
       this.highlighters.selectorHighlighterShown = null;
       this.store.dispatch(updateHighlightedSelector(""));
       // This event is emitted for testing purposes.
       this.emit("ruleview-selectorhighlighter-toggled", false);
     }
   }
 
   /**
+   * Handler for when the toolbox's tools are registered or unregistered.
+   * The source links in the rules view should be enabled only while the
+   * Style Editor is registered because that's where source links point to.
+   */
+  onToolChanged() {
+    const prevIsSourceLinkEnabled = this.store.getState().rules.isSourceLinkEnabled;
+    const isSourceLinkEnabled = this.toolbox.isToolRegistered("styleeditor");
+
+    if (prevIsSourceLinkEnabled !== isSourceLinkEnabled) {
+      this.store.dispatch(updateSourceLinkEnabled(isSourceLinkEnabled));
+    }
+  }
+
+  /**
    * Handler for showing the inplace editor when an editable property name is clicked in
    * the rules view.
    *
    * @param  {DOMNode} element
    *         The declaration name span element to be edited.
    * @param  {String} ruleId
    *         The id of the Rule object to be edited.
    * @param  {String} declarationId
@@ -545,16 +591,20 @@ class RulesView {
    * Updates the rules view by dispatching the new rules data of the newly selected
    * element. This is called when the rules view becomes visible or upon new node
    * selection.
    *
    * @param  {NodeFront|null} element
    *         The NodeFront of the current selected element.
    */
   async update(element) {
+    if (this.elementStyle) {
+      this.elementStyle.destroy();
+    }
+
     if (!element) {
       this.store.dispatch(disableAllPseudoClasses());
       this.store.dispatch(updateAddRuleEnabled(false));
       this.store.dispatch(updateClasses([]));
       this.store.dispatch(updateRules([]));
       return;
     }
 
--- a/devtools/client/inspector/rules/reducers/rules.js
+++ b/devtools/client/inspector/rules/reducers/rules.js
@@ -1,25 +1,32 @@
 /* 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";
 
+const Services = require("Services");
+
 const {
   UPDATE_ADD_RULE_ENABLED,
+  UPDATE_HIGHLIGHTED_SELECTOR,
   UPDATE_RULES,
-  UPDATE_HIGHLIGHTED_SELECTOR,
+  UPDATE_SOURCE_LINK_ENABLED,
+  UPDATE_SOURCE_LINK,
 } = require("../actions/index");
 
 const INITIAL_RULES = {
   // The selector of the node that is highlighted by the selector highlighter.
   highlightedSelector: "",
   // Whether or not the add new rule button should be enabled.
   isAddRuleEnabled: false,
+  // Whether or not the source links are enabled. This is determined by
+  // whether or not the style editor is registered.
+  isSourceLinkEnabled: Services.prefs.getBoolPref("devtools.styleeditor.enabled"),
   // Array of CSS rules.
   rules: [],
 };
 
 /**
  * Given a rule's TextProperty, returns the properties that are needed to render a
  * CSS declaration.
  *
@@ -108,20 +115,46 @@ const reducers = {
       highlightedSelector,
     };
   },
 
   [UPDATE_RULES](rules, { rules: newRules }) {
     return {
       highlightedSelector: rules.highlightedSelector,
       isAddRuleEnabled: rules.isAddRuleEnabled,
+      isSourceLinkEnabled: rules.isSourceLinkEnabled,
       rules: newRules.map(rule => getRuleState(rule)),
     };
   },
 
+  [UPDATE_SOURCE_LINK_ENABLED](rules, { enabled }) {
+    return {
+      ...rules,
+      isSourceLinkEnabled: enabled,
+    };
+  },
+
+  [UPDATE_SOURCE_LINK](rules, { ruleId, sourceLink }) {
+    return {
+      highlightedSelector: rules.highlightedSelector,
+      isAddRuleEnabled: rules.isAddRuleEnabled,
+      isSourceLinkEnabled: rules.isSourceLinkEnabled,
+      rules: rules.rules.map(rule => {
+        if (rule.id !== ruleId) {
+          return rule;
+        }
+
+        return {
+          ...rule,
+          sourceLink,
+        };
+      }),
+    };
+  },
+
 };
 
 module.exports = function(rules = INITIAL_RULES, action) {
   const reducer = reducers[action.type];
   if (!reducer) {
     return rules;
   }
   return reducer(rules, action);
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -19,22 +19,20 @@ var {getInplaceEditorForSpan: inplaceEdi
   require("devtools/client/shared/inplace-editor");
 
 const ROOT_TEST_DIR = getRootDirectory(gTestPath);
 const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
 
 const STYLE_INSPECTOR_L10N
       = new LocalizationHelper("devtools/shared/locales/styleinspector.properties");
 
-Services.prefs.setBoolPref("devtools.inspector.flexboxHighlighter.enabled", true);
 Services.prefs.setBoolPref("devtools.inspector.shapesHighlighter.enabled", true);
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.defaultColorUnit");
-  Services.prefs.clearUserPref("devtools.inspector.flexboxHighlighter.enabled");
   Services.prefs.clearUserPref("devtools.inspector.shapesHighlighter.enabled");
 });
 
 /**
  * The rule-view tests rely on a frame-script to be injected in the content test
  * page. So override the shared-head's addTab to load the frame script after the
  * tab was added.
  * FIXME: Refactor the rule-view tests to use the testActor instead of a frame
--- a/devtools/client/inspector/rules/types.js
+++ b/devtools/client/inspector/rules/types.js
@@ -113,31 +113,16 @@ const selector = exports.selector = {
   matchedSelectors: PropTypes.arrayOf(PropTypes.string),
   // The CSS rule's selector text content.
   selectorText: PropTypes.string,
   // Array of the CSS rule's selectors.
   selectors: PropTypes.arrayOf(PropTypes.string),
 };
 
 /**
- * A CSS rule's stylesheet source.
- */
-const sourceLink = exports.sourceLink = {
-  // The CSS rule's column number within the stylesheet.
-  column: PropTypes.number,
-  // The CSS rule's line number within the stylesheet.
-  line: PropTypes.number,
-  // The media query text within a @media rule.
-  // Note: Abstract this to support other at-rules in the future.
-  mediaText: PropTypes.string,
-  // The title used for the stylesheet source.
-  title: PropTypes.string,
-};
-
-/**
  * A CSS Rule.
  */
 exports.rule = {
   // Array of CSS declarations.
   declarations: PropTypes.arrayOf(PropTypes.shape(declaration)),
 
   // An unique CSS rule id.
   id: PropTypes.string,
@@ -166,13 +151,18 @@ exports.rule = {
 
   // The pseudo-element keyword used in the rule.
   pseudoElement: PropTypes.string,
 
   // An object containing information about the CSS rule's selector.
   selector: PropTypes.shape(selector),
 
   // An object containing information about the CSS rule's stylesheet source.
-  sourceLink: PropTypes.shape(sourceLink),
+  sourceLink: PropTypes.shape({
+    // The label used for the stylesheet source
+    label: PropTypes.string,
+    // The title used for the stylesheet source.
+    title: PropTypes.string,
+  }),
 
   // The CSS rule type.
   type: PropTypes.number,
 };
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -61,19 +61,17 @@ pref("devtools.inspector.new-rulesview.e
 // Enable the 'scrollable' markup-badges in nightly only for now
 #if defined(NIGHTLY_BUILD)
 pref("devtools.inspector.scrollable-badges.enabled", true);
 #else
 pref("devtools.inspector.scrollable-badges.enabled", false);
 #endif
 
 // Flexbox preferences
-pref("devtools.inspector.flexboxHighlighter.enabled", true);
-pref("devtools.flexboxinspector.enabled", true);
-
+// Whether or not to show the combined flexbox and box model highlighter.
 #if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION)
 pref("devtools.inspector.flexboxHighlighter.combine", true);
 #else
 pref("devtools.inspector.flexboxHighlighter.combine", false);
 #endif
 
 // Grid highlighter preferences
 pref("devtools.gridinspector.gridOutlineMaxColumns", 50);
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -31,17 +31,16 @@ const COLOR_TAKING_FUNCTIONS = ["linear-
                                 "-moz-repeating-linear-gradient", "radial-gradient",
                                 "-moz-radial-gradient", "repeating-radial-gradient",
                                 "-moz-repeating-radial-gradient", "drop-shadow"];
 // Functions that accept a shape argument.
 const BASIC_SHAPE_FUNCTIONS = ["polygon", "circle", "ellipse", "inset"];
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
-const FLEXBOX_HIGHLIGHTER_ENABLED_PREF = "devtools.inspector.flexboxHighlighter.enabled";
 const CSS_SHAPES_ENABLED_PREF = "devtools.inspector.shapesHighlighter.enabled";
 
 /**
  * This module is used to process text for output by developer tools. This means
  * linking JS files with the debugger, CSS files with the style editor, JS
  * functions with the debugger, placing color swatches next to colors and
  * adding doorhanger previews where possible (images, angles, lengths,
  * border radius, cubic-bezier etc.).
@@ -379,18 +378,17 @@ OutputParser.prototype = {
           }
           break;
         }
 
         case "ident":
           if (options.expectCubicBezier &&
               BEZIER_KEYWORDS.includes(token.text)) {
             this._appendCubicBezier(token.text, options);
-          } else if (this._isDisplayFlex(text, token, options) &&
-                     Services.prefs.getBoolPref(FLEXBOX_HIGHLIGHTER_ENABLED_PREF)) {
+          } else if (this._isDisplayFlex(text, token, options)) {
             this._appendHighlighterToggle(token.text, options.flexClass);
           } else if (this._isDisplayGrid(text, token, options)) {
             this._appendHighlighterToggle(token.text, options.gridClass);
           } else if (colorOK() &&
                      colorUtils.isValidCSSColor(token.text, this.cssColor4)) {
             this._appendColor(token.text, options);
           } else if (angleOK(token.text)) {
             this._appendAngle(token.text, options);
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -205,21 +205,22 @@
   cursor: pointer;
 
   /* Create an LTR embed to avoid special characters being shifted to the start due to the
      parent node direction: rtl; */
   direction: ltr;
   unicode-bidi: embed
 }
 
-.ruleview-rule-source[unselectable],
+.ruleview-rule-source.disabled > .ruleview-rule-source-label,
 .ruleview-rule-source[unselectable] > .ruleview-rule-source-label {
   cursor: default;
 }
 
+.ruleview-rule-source:not(.unselectable):hover,
 .ruleview-rule-source:not([unselectable]):hover {
   text-decoration: underline;
 }
 
 .ruleview-header {
   background: var(--rule-header-background-color);
   border-bottom: 1px solid var(--theme-splitter-color);
   color: var(--theme-toolbar-color);
--- a/devtools/server/actors/highlighters/box-model.js
+++ b/devtools/server/actors/highlighters/box-model.js
@@ -29,17 +29,16 @@ loader.lazyRequireGetter(this, "FlexboxH
 // for drawing the BoxModelHighlighter's path elements correctly.
 const BOX_MODEL_REGIONS = ["margin", "border", "padding", "content"];
 const BOX_MODEL_SIDES = ["top", "right", "bottom", "left"];
 // Width of boxmodelhighlighter guides
 const GUIDE_STROKE_WIDTH = 1;
 // FIXME: add ":visited" and ":link" after bug 713106 is fixed
 const PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-within"];
 
-const FLEXBOX_HIGHLIGHTER_ENABLED_PREF = "devtools.inspector.flexboxHighlighter.enabled";
 const FLEXBOX_HIGHLIGHTER_COMBINE_PREF = "devtools.inspector.flexboxHighlighter.combine";
 
 /**
  * The BoxModelHighlighter draws the box model regions on top of a node.
  * If the node is a block box, then each region will be displayed as 1 polygon.
  * If the node is an inline box though, each region may be represented by 1 or
  * more polygons, depending on how many line boxes the inline element has.
  *
@@ -124,17 +123,16 @@ class BoxModelHighlighter extends AutoRe
    * Check whether we should show the combined flexbox highlighter. Because
    * checking for prefs is slow and performance is paramount for the highlighter
    * we cache the result. Because we cache the result the Toolbox needs to be
    * closed and opened again for any pref changes to take affect.
    */
   get showCombinedFlexboxHighlighter() {
     if (typeof this._showCombinedFlexboxHighlighter === "undefined") {
       this._showCombinedFlexboxHighlighter =
-        Services.prefs.getBoolPref(FLEXBOX_HIGHLIGHTER_ENABLED_PREF) &&
         Services.prefs.getBoolPref(FLEXBOX_HIGHLIGHTER_COMBINE_PREF);
     }
 
     return this._showCombinedFlexboxHighlighter;
   }
 
   _buildMarkup() {
     const doc = this.win.document;