Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 26 May 2016 11:22:27 -0400
changeset 338200 49beae1792077eba5b790baa7d3c1a37ca3eb609
parent 338199 1bb60897b5e14dbe9586b77fe55c3aef3a8af4cf (current diff)
parent 338093 b0096c5c727749ad3e79cbdf20d2e96bd179c213 (diff)
child 338201 aa1d245e0af392ca48e991661c0c38a84ba2e360
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound. a=merge
devtools/client/shared/css-parsing-utils.js
devtools/shared/gcli/commands/cookie.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -106,16 +106,17 @@ devtools/client/webconsole/**
 !devtools/client/webconsole/panel.js
 !devtools/client/webconsole/jsterm.js
 devtools/client/webide/**
 devtools/server/**
 !devtools/server/actors/webbrowser.js
 !devtools/server/actors/styles.js
 devtools/shared/*.js
 !devtools/shared/css-lexer.js
+!devtools/shared/promise_defer.js
 !devtools/shared/task.js
 devtools/shared/*.jsm
 devtools/shared/apps/**
 devtools/shared/client/**
 devtools/shared/discovery/**
 devtools/shared/gcli/**
 !devtools/shared/gcli/templater.js
 devtools/shared/heapsnapshot/**
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -112,16 +112,17 @@
       function showCertificateErrorReporting() {
         // Display error reporting UI
         document.getElementById("certificateErrorReporting").style.display = "block";
       }
 
       function showPrefChangeContainer() {
         const panel = document.getElementById("prefChangeContainer");
         panel.style.display = "block";
+        document.getElementById("netErrorButtonContainer").style.display = "none";
         document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
           const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
           document.dispatchEvent(event);
         });
       }
 
       function showAdvancedButton(allowOverride) {
         // Get the hostname and add it to the panel
@@ -576,23 +577,23 @@
 
         <div id="wrongSystemTimePanel" style="display: none;">
           &certerror.wrongSystemTime;
         </div>
 
         <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
         <div id="errorLongDesc" />
 
-        <div id="prefChangeContainer">
-          <p>&prefReset.longDesc;</p>
-          <button id="prefResetButton" autocomplete="off">&prefReset.label;</button>
+        <div id="learnMoreContainer">
+          <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
         </div>
 
-        <div id="learnMoreContainer">
-          <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
+        <div id="prefChangeContainer" class="button-container">
+          <p>&prefReset.longDesc;</p>
+          <button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
         </div>
 
         <div id="certErrorButtonContainer" class="button-container">
           <button id="returnButton" class="primary" autocomplete="off" autofocus="true">&returnToPreviousPage.label;</button>
           <div class="button-spacer"></div>
           <button id="advancedButton" autocomplete="off" autofocus="true">&advanced.label;</button>
         </div>
       </div>
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1447,22 +1447,16 @@ var BookmarkingUI = {
 
     let options = PlacesUtils.history.getNewQueryOptions();
     options.excludeQueries = true;
     options.queryType = options.QUERY_TYPE_BOOKMARKS;
     options.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
     options.maxResults = kMaxResults;
     let query = PlacesUtils.history.getNewQuery();
 
-    let onItemCommand = function (aEvent) {
-      let item = aEvent.target;
-      openUILink(item.getAttribute("targetURI"), aEvent);
-      CustomizableUI.hidePanelForNode(item);
-    };
-
     let fragment = document.createDocumentFragment();
     let root = PlacesUtils.history.executeQuery(query, options).root;
     root.containerOpen = true;
     for (let i = 0; i < root.childCount; i++) {
       let node = root.getChild(i);
       let uri = node.uri;
       let title = node.title;
       let icon = node.icon;
@@ -1470,17 +1464,16 @@ var BookmarkingUI = {
       let item =
         document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                  "menuitem");
       item.setAttribute("label", title || uri);
       item.setAttribute("targetURI", uri);
       item.setAttribute("simulated-places-node", true);
       item.setAttribute("class", "menuitem-iconic menuitem-with-favicon bookmark-item " +
                                  aExtraCSSClass);
-      item.addEventListener("command", onItemCommand);
       if (icon) {
         item.setAttribute("image", icon);
       }
       item._placesNode = node;
       fragment.appendChild(item);
     }
     root.containerOpen = false;
     aHeaderItem.parentNode.insertBefore(fragment, aHeaderItem.nextSibling);
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -464,17 +464,17 @@ nsContextMenu.prototype = {
 
   initMediaPlayerItems: function() {
     var onMedia = (this.onVideo || this.onAudio);
     // Several mutually exclusive items... play/pause, mute/unmute, show/hide
     this.showItem("context-media-play",  onMedia && (this.target.paused || this.target.ended));
     this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
     this.showItem("context-media-mute",   onMedia && !this.target.muted);
     this.showItem("context-media-unmute", onMedia && this.target.muted);
-    this.showItem("context-media-playbackrate", onMedia);
+    this.showItem("context-media-playbackrate", onMedia && this.target.duration != Number.POSITIVE_INFINITY);
     this.showItem("context-media-loop", onMedia);
     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
     this.showItem("context-media-hidecontrols", this.target.controls && (this.onVideo || (this.onAudio && !this.inSyntheticDoc)));
     this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.fullscreenElement == null);
     this.showItem("context-media-eme-learnmore", this.onDRMMedia);
     this.showItem("context-media-eme-separator", this.onDRMMedia);
 
     // Disable them when there isn't a valid media source loaded.
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -201,32 +201,38 @@ const CustomizableWidgets = [
       let staticButtons = items.parentNode.getElementsByTagNameNS(kNSXUL, "toolbarbutton");
       for (let i = 0, l = staticButtons.length; i < l; ++i)
         CustomizableUI.addShortcut(staticButtons[i]);
 
       PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                          .asyncExecuteLegacyQueries([query], 1, options, {
         handleResult: function (aResultSet) {
           let onItemCommand = function (aEvent) {
+            // Only handle the click event for middle clicks, we're using the command
+            // event otherwise.
+            if (aEvent.type == "click" && aEvent.button != 1) {
+              return;
+            }
             let item = aEvent.target;
             win.openUILink(item.getAttribute("targetURI"), aEvent);
             CustomizableUI.hidePanelForNode(item);
           };
           let fragment = doc.createDocumentFragment();
           let row;
           while ((row = aResultSet.getNextRow())) {
             let uri = row.getResultByIndex(1);
             let title = row.getResultByIndex(2);
             let icon = row.getResultByIndex(6);
 
             let item = doc.createElementNS(kNSXUL, "toolbarbutton");
             item.setAttribute("label", title || uri);
             item.setAttribute("targetURI", uri);
             item.setAttribute("class", "subviewbutton");
             item.addEventListener("command", onItemCommand);
+            item.addEventListener("click", onItemCommand);
             if (icon) {
               let iconURL = "moz-anno:favicon:" + icon;
               item.setAttribute("image", iconURL);
             }
             fragment.appendChild(item);
           }
           items.appendChild(fragment);
         },
--- a/devtools/.eslintrc
+++ b/devtools/.eslintrc
@@ -13,16 +13,17 @@
     "exports": true,
     "isWorker": true,
     "loader": true,
     "module": true,
     "require": true,
     "setInterval": true,
     "setTimeout": true,
     "XMLHttpRequest": true,
+    "URL": true,
     "_Iterator": true,
   },
   "rules": {
     // These are the rules that have been configured so far to match the
     // devtools coding style.
 
     // Rules from the mozilla plugin
     "mozilla/mark-test-function-used": 1,
@@ -130,17 +131,17 @@
     // Enforces spacing between keys and values in object literal properties.
     "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
     // Allow mixed 'LF' and 'CRLF' as linebreaks.
     "linebreak-style": 0,
     // Don't enforce the maximum depth that blocks can be nested. The complexity
     // rule is a better rule to check this.
     "max-depth": 0,
     // Maximum length of a line.
-    "max-len": [2, 80, 2, {"ignoreUrls": true, "ignorePattern": "data:image\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}],
+    "max-len": [2, 90, 2, {"ignoreUrls": true, "ignorePattern": "data:image\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}],
     // Maximum depth callbacks can be nested.
     "max-nested-callbacks": [2, 3],
     // Don't limit the number of parameters that can be used in a function.
     "max-params": 0,
     // Don't limit the maximum number of statement allowed in a function. We
     // already have the complexity rule that's a better measurement.
     "max-statements": 0,
     // Require a capital letter for constructors, only check if all new
--- a/devtools/client/commandline/test/browser_cmd_inject.js
+++ b/devtools/client/commandline/test/browser_cmd_inject.js
@@ -49,17 +49,17 @@ function test() {
           markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
           hints:                                                                                           "",
           status: "VALID",
           args: {
             library: {
               value: function (library) {
                 is(library.type, "url", "inject type name");
                 is(library.url.origin, "http://example.com", "inject url hostname");
-                ok(library.url.path.indexOf("_inject.js") != -1, "inject url path");
+                ok(library.url.pathname.indexOf("_inject.js") != -1, "inject url path");
               },
               status: "VALID"
             }
           }
         },
         exec: {
           output: [ /http:\/\/example.com\/browser\/devtools\/client\/commandline\/test\/browser_cmd_inject.js loaded/ ]
         }
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -17,29 +17,34 @@ const COLLAPSE_DATA_URL_LENGTH = 60;
 const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
 const DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE = 50;
 const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 5;
 const DRAG_DROP_MAX_AUTOSCROLL_SPEED = 15;
 const DRAG_DROP_MIN_INITIAL_DISTANCE = 10;
 const AUTOCOMPLETE_POPUP_PANEL_ID = "markupview_autoCompletePopup";
 const ATTR_COLLAPSE_ENABLED_PREF = "devtools.markup.collapseAttributes";
 const ATTR_COLLAPSE_LENGTH_PREF = "devtools.markup.collapseAttributeLength";
+const PREVIEW_MAX_DIM_PREF = "devtools.inspector.imagePreviewTooltipSize";
 
 // Contains only void (without end tag) HTML elements
 const HTML_VOID_ELEMENTS = [ "area", "base", "br", "col", "command", "embed",
   "hr", "img", "input", "keygen", "link", "meta", "param", "source",
   "track", "wbr" ];
 
 const {UndoStack} = require("devtools/client/shared/undo");
 const {editableField, InplaceEditor} =
       require("devtools/client/shared/inplace-editor");
 const {HTMLEditor} = require("devtools/client/inspector/markup/html-editor");
 const promise = require("promise");
 const Services = require("Services");
 const {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
+const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
+const {setImageTooltip, setBrokenImageTooltip} =
+      require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
+
 const EventEmitter = require("devtools/shared/event-emitter");
 const Heritage = require("sdk/core/heritage");
 const {parseAttribute} =
       require("devtools/client/shared/node-attribute-parser");
 const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis",
       Ci.nsIPrefLocalizedString).data;
 const {Task} = require("devtools/shared/task");
 const {scrollIntoViewIfNeeded} = require("devtools/shared/layout/utils");
@@ -126,16 +131,17 @@ function MarkupView(inspector, frame, co
   this._onNewSelection = this._onNewSelection.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onFocus = this._onFocus.bind(this);
   this._onMouseMove = this._onMouseMove.bind(this);
   this._onMouseLeave = this._onMouseLeave.bind(this);
   this._onToolboxPickerHover = this._onToolboxPickerHover.bind(this);
   this._onCollapseAttributesPrefChange =
     this._onCollapseAttributesPrefChange.bind(this);
+  this._isImagePreviewTarget = this._isImagePreviewTarget.bind(this);
   this._onBlur = this._onBlur.bind(this);
 
   EventEmitter.decorate(this);
 
   // Listening to various events.
   this._elt.addEventListener("click", this._onMouseClick, false);
   this._elt.addEventListener("mousemove", this._onMouseMove, false);
   this._elt.addEventListener("mouseleave", this._onMouseLeave, false);
@@ -164,27 +170,29 @@ MarkupView.prototype = {
   /**
    * How long does a node flash when it mutates (in ms).
    */
   CONTAINER_FLASHING_DURATION: 500,
 
   _selectedContainer: null,
 
   _initTooltips: function () {
-    this.tooltip = new Tooltip(this._inspector.panelDoc);
-    this._makeTooltipPersistent(false);
+    this.eventDetailsTooltip = new Tooltip(this._inspector.panelDoc);
+    this.imagePreviewTooltip = new HTMLTooltip(this._inspector.toolbox,
+      {type: "arrow"});
+    this._enableImagePreviewTooltip();
   },
 
-  _makeTooltipPersistent: function (state) {
-    if (state) {
-      this.tooltip.stopTogglingOnHover();
-    } else {
-      this.tooltip.startTogglingOnHover(this._elt,
-        this._isImagePreviewTarget.bind(this));
-    }
+  _enableImagePreviewTooltip: function () {
+    this.imagePreviewTooltip.startTogglingOnHover(this._elt,
+      this._isImagePreviewTarget);
+  },
+
+  _disableImagePreviewTooltip: function () {
+    this.imagePreviewTooltip.stopTogglingOnHover();
   },
 
   _onToolboxPickerHover: function (event, nodeFront) {
     this.showNode(nodeFront).then(() => {
       this._showContainerAsHovered(nodeFront);
     }, e => console.error(e));
   },
 
@@ -293,17 +301,18 @@ MarkupView.prototype = {
         break;
       }
       parentNode = parentNode.parentNode;
     }
 
     if (container instanceof MarkupElementContainer) {
       // With the newly found container, delegate the tooltip content creation
       // and decision to show or not the tooltip
-      container._buildEventTooltipContent(event.target, this.tooltip);
+      container._buildEventTooltipContent(event.target,
+        this.eventDetailsTooltip);
     }
   },
 
   _onMouseUp: function () {
     this.indicateDropTarget(null);
     this.indicateDragTarget(null);
     if (this._autoScrollInterval) {
       clearInterval(this._autoScrollInterval);
@@ -470,40 +479,40 @@ MarkupView.prototype = {
    * to decide whether this target should be used to display an image preview
    * tooltip.
    * Delegates the actual decision to the corresponding MarkupContainer instance
    * if one is found.
    *
    * @return {Promise} the promise returned by
    *         MarkupElementContainer._isImagePreviewTarget
    */
-  _isImagePreviewTarget: function (target) {
+  _isImagePreviewTarget: Task.async(function* (target) {
     // From the target passed here, let's find the parent MarkupContainer
     // and ask it if the tooltip should be shown
     if (this.isDragging) {
-      return promise.reject(false);
+      return false;
     }
 
     let parent = target, container;
     while (parent !== this.doc.body) {
       if (parent.container) {
         container = parent.container;
         break;
       }
       parent = parent.parentNode;
     }
 
     if (container instanceof MarkupElementContainer) {
       // With the newly found container, delegate the tooltip content creation
       // and decision to show or not the tooltip
-      return container.isImagePreviewTarget(target, this.tooltip);
+      return container.isImagePreviewTarget(target, this.imagePreviewTooltip);
     }
 
-    return undefined;
-  },
+    return false;
+  }),
 
   /**
    * Given the known reason, should the current selection be briefly highlighted
    * In a few cases, we don't want to highlight the node:
    * - If the reason is null (used to reset the selection),
    * - if it's "inspector-open" (when the inspector opens up, let's not
    * highlight the default node)
    * - if it's "navigateaway" (since the page is being navigated away from)
@@ -961,16 +970,20 @@ MarkupView.prototype = {
       }
 
       let container = this.getContainer(target);
       if (!container) {
         // Container might not exist if this came from a load event for a node
         // we're not viewing.
         continue;
       }
+
+      if (type === "attributes" && mutation.attributeName === "class") {
+        container.updateIsDisplayed();
+      }
       if (type === "attributes" || type === "characterData"
         || type === "events" || type === "pseudoClassLock") {
         container.update();
       } else if (type === "childList" || type === "nativeAnonymousChildList") {
         container.childrenDirty = true;
         // Update the children to take care of changes in the markup view DOM
         // and update container (and its subtree) DOM tree depth level for
         // accessibility where necessary.
@@ -999,17 +1012,17 @@ MarkupView.prototype = {
    *
    * @param  {Array} nodes
    *         An array of nodeFronts
    */
   _onDisplayChange: function (nodes) {
     for (let node of nodes) {
       let container = this.getContainer(node);
       if (container) {
-        container.isDisplayed = node.isDisplayed;
+        container.updateIsDisplayed();
       }
     }
   },
 
   /**
    * Given a list of mutations returned by the mutation observer, flash the
    * corresponding containers to attract attention.
    */
@@ -1709,18 +1722,21 @@ MarkupView.prototype = {
 
     this._elt = null;
 
     for (let [, container] of this._containers) {
       container.destroy();
     }
     this._containers = null;
 
-    this.tooltip.destroy();
-    this.tooltip = null;
+    this.eventDetailsTooltip.destroy();
+    this.eventDetailsTooltip = null;
+
+    this.imagePreviewTooltip.destroy();
+    this.imagePreviewTooltip = null;
 
     this.win = null;
     this.doc = null;
 
     this._lastDropTarget = null;
     this._lastDragTarget = null;
 
     return this._destroyer;
@@ -1874,17 +1890,17 @@ MarkupContainer.prototype = {
     this.win.addEventListener("mouseup", this._onMouseUp, true);
     this.win.addEventListener("mousemove", this._onMouseMove, true);
     this.elt.addEventListener("dblclick", this._onToggle, false);
     if (this.expander) {
       this.expander.addEventListener("click", this._onToggle, false);
     }
 
     // Marking the node as shown or hidden
-    this.isDisplayed = this.node.isDisplayed;
+    this.updateIsDisplayed();
   },
 
   toString: function () {
     return "[MarkupContainer for " + this.node + "]";
   },
 
   isPreviewable: function () {
     if (this.node.tagName && !this.node.isPseudoElement) {
@@ -1895,21 +1911,24 @@ MarkupContainer.prototype = {
 
       return isImage || isCanvas;
     }
 
     return false;
   },
 
   /**
-   * Show the element has displayed or not.
+   * Show whether the element is displayed or not
+   * If an element has the attribute `display: none` or has been hidden with
+   * the H key, it is not displayed (faded in markup view).
+   * Otherwise, it is displayed.
    */
-  set isDisplayed(isDisplayed) {
+  updateIsDisplayed: function () {
     this.elt.classList.remove("not-displayed");
-    if (!isDisplayed) {
+    if (!this.node.isDisplayed || this.node.hidden) {
       this.elt.classList.add("not-displayed");
     }
   },
 
   /**
    * True if the current node has children. The MarkupView
    * will set this attribute for the MarkupContainer.
    */
@@ -2591,19 +2610,21 @@ MarkupElementContainer.prototype = Herit
       tooltip.hide(target);
 
       this.node.getEventListenerInfo().then(listenerInfo => {
         tooltip.setEventContent({
           eventListenerInfos: listenerInfo,
           toolbox: this.markup._inspector.toolbox
         });
 
-        this.markup._makeTooltipPersistent(true);
+        // Disable the image preview tooltip while we display the event details
+        this.markup._disableImagePreviewTooltip();
         tooltip.once("hidden", () => {
-          this.markup._makeTooltipPersistent(false);
+          // Enable the image preview tooltip after closing the event details
+          this.markup._enableImagePreviewTooltip();
         });
         tooltip.show(target);
       });
       return true;
     }
     return undefined;
   },
 
@@ -2625,43 +2646,39 @@ MarkupElementContainer.prototype = Herit
       return promise.reject("_getPreview called on a non-previewable element.");
     }
 
     if (this.tooltipDataPromise) {
       // A preview request is already pending. Re-use that request.
       return this.tooltipDataPromise;
     }
 
-    let maxDim =
-      Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
-
     // Fetch the preview from the server.
     this.tooltipDataPromise = Task.spawn(function* () {
+      let maxDim = Services.prefs.getIntPref(PREVIEW_MAX_DIM_PREF);
       let preview = yield this.node.getImageData(maxDim);
       let data = yield preview.data.string();
 
       // Clear the pending preview request. We can't reuse the results later as
       // the preview contents might have changed.
       this.tooltipDataPromise = null;
-
       return { data, size: preview.size };
     }.bind(this));
 
     return this.tooltipDataPromise;
   },
 
   /**
    * Executed by MarkupView._isImagePreviewTarget which is itself called when
    * the mouse hovers over a target in the markup-view.
    * Checks if the target is indeed something we want to have an image tooltip
    * preview over and, if so, inserts content into the tooltip.
    *
-   * @return {Promise} that resolves when the content has been inserted or
-   *         rejects if no preview is required. This promise is then used by
-   *         Tooltip.js to decide if/when to show the tooltip
+   * @return {Promise} that resolves when the tooltip content is ready. Resolves
+   * true if the tooltip should be displayed, false otherwise.
    */
   isImagePreviewTarget: Task.async(function* (target, tooltip) {
     // Is this Element previewable.
     if (!this.isPreviewable()) {
       return false;
     }
 
     // If the Element has an src attribute, the tooltip is shown when hovering
@@ -2669,24 +2686,29 @@ MarkupElementContainer.prototype = Herit
     // name.
     let src = this.editor.getAttributeElement("src");
     let expectedTarget = src ? src.querySelector(".link") : this.editor.tag;
     if (target !== expectedTarget) {
       return false;
     }
 
     try {
-      let {data, size} = yield this._getPreview();
+      let { data, size } = yield this._getPreview();
       // The preview is ready.
-      tooltip.setImageContent(data, size);
+      let options = {
+        naturalWidth: size.naturalWidth,
+        naturalHeight: size.naturalHeight,
+        maxDim: Services.prefs.getIntPref(PREVIEW_MAX_DIM_PREF)
+      };
+
+      yield setImageTooltip(tooltip, this.markup.doc, data, options);
     } catch (e) {
       // Indicate the failure but show the tooltip anyway.
-      tooltip.setBrokenImageContent();
+      yield setBrokenImageTooltip(tooltip, this.markup.doc);
     }
-
     return true;
   }),
 
   copyImageDataUri: function () {
     // We need to send again a request to gettooltipData even if one was sent
     // for the tooltip, because we want the full-size image
     this.node.getImageData().then(data => {
       data.data.string().then(str => {
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -104,18 +104,19 @@ subsuite = clipboard
 [browser_markup_keybindings_01.js]
 [browser_markup_keybindings_02.js]
 [browser_markup_keybindings_03.js]
 [browser_markup_keybindings_04.js]
 [browser_markup_keybindings_delete_attributes.js]
 [browser_markup_keybindings_scrolltonode.js]
 [browser_markup_mutation_01.js]
 [browser_markup_mutation_02.js]
+[browser_markup_navigation.js]
 [browser_markup_node_names.js]
-[browser_markup_navigation.js]
+[browser_markup_node_names_namespaced.js]
 [browser_markup_node_not_displayed_01.js]
 [browser_markup_node_not_displayed_02.js]
 [browser_markup_pagesize_01.js]
 [browser_markup_pagesize_02.js]
 [browser_markup_remove_xul_attributes.js]
 skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
 [browser_markup_search_01.js]
 [browser_markup_tag_edit_01.js]
--- a/devtools/client/inspector/markup/test/browser_markup_dragdrop_tooltip.js
+++ b/devtools/client/inspector/markup/test/browser_markup_dragdrop_tooltip.js
@@ -11,30 +11,25 @@ add_task(function* () {
   let {inspector} = yield openInspectorForURL(TEST_URL);
   let {markup} = inspector;
 
   info("Get the tooltip target element for the image's src attribute");
   let img = yield getContainerForSelector("img", inspector);
   let target = img.editor.getAttributeElement("src").querySelector(".link");
 
   info("Check that the src attribute of the image is a valid tooltip target");
-  let isValid = yield isHoverTooltipTarget(markup.tooltip, target);
+  let isValid = yield isHoverTooltipTarget(markup.imagePreviewTooltip, target);
   ok(isValid, "The element is a valid tooltip target");
 
   info("Start dragging the test div");
   yield simulateNodeDrag(inspector, "div");
 
   info("Now check that the src attribute of the image isn't a valid target");
-  try {
-    yield isHoverTooltipTarget(markup.tooltip, target);
-    isValid = true;
-  } catch (e) {
-    isValid = false;
-  }
+  isValid = yield isHoverTooltipTarget(markup.imagePreviewTooltip, target);
   ok(!isValid, "The element is not a valid tooltip target");
 
   info("Stop dragging the test div");
   yield simulateNodeDrop(inspector, "div");
 
   info("Check again the src attribute of the image");
-  isValid = yield isHoverTooltipTarget(markup.tooltip, target);
+  isValid = yield isHoverTooltipTarget(markup.imagePreviewTooltip, target);
   ok(isValid, "The element is a valid tooltip target");
 });
--- a/devtools/client/inspector/markup/test/browser_markup_events-overflow.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events-overflow.js
@@ -30,17 +30,17 @@ const TEST_DATA = [
   },
 ];
 
 add_task(function* () {
   let { inspector } = yield openInspectorForURL(TEST_URL);
 
   let markupContainer = yield getContainerForSelector("#events", inspector);
   let evHolder = markupContainer.elt.querySelector(".markupview-events");
-  let tooltip = inspector.markup.tooltip;
+  let tooltip = inspector.markup.eventDetailsTooltip;
 
   info("Clicking to open event tooltip.");
   EventUtils.synthesizeMouseAtCenter(evHolder, {},
     inspector.markup.doc.defaultView);
   yield tooltip.once("shown");
   info("EventTooltip visible.");
 
   let container = tooltip.content;
--- a/devtools/client/inspector/markup/test/browser_markup_image_tooltip.js
+++ b/devtools/client/inspector/markup/test/browser_markup_image_tooltip.js
@@ -38,22 +38,23 @@ function* getImageTooltipTarget({selecto
   if (isImg) {
     target = container.editor.getAttributeElement("src").querySelector(".link");
   }
   return target;
 }
 
 function* assertTooltipShownOn(element, {markup}) {
   info("Is the element a valid hover target");
-  let isValid = yield isHoverTooltipTarget(markup.tooltip, element);
+  let isValid = yield isHoverTooltipTarget(markup.imagePreviewTooltip, element);
   ok(isValid, "The element is a valid hover target for the image tooltip");
 }
 
 function checkImageTooltip({selector, size}, {markup}) {
-  let images = markup.tooltip.panel.getElementsByTagName("image");
+  let panel = markup.imagePreviewTooltip.panel;
+  let images = panel.getElementsByTagName("img");
   is(images.length, 1, "Tooltip for [" + selector + "] contains an image");
 
-  let label = markup.tooltip.panel.querySelector(".devtools-tooltip-caption");
+  let label = panel.querySelector(".devtools-tooltip-caption");
   is(label.textContent, size,
      "Tooltip label for [" + selector + "] displays the right image size");
 
-  markup.tooltip.hide();
+  markup.imagePreviewTooltip.hide();
 }
--- a/devtools/client/inspector/markup/test/browser_markup_image_tooltip_mutations.js
+++ b/devtools/client/inspector/markup/test/browser_markup_image_tooltip_mutations.js
@@ -32,28 +32,28 @@ add_task(function* () {
   let container = getContainerForNodeFront(img, inspector);
   ok(container, "Found markup container for the image.");
 
   let target = container.editor.getAttributeElement("src")
                                .querySelector(".link");
   ok(target, "Found the src attribute in the markup view.");
 
   info("Showing tooltip on the src link.");
-  yield isHoverTooltipTarget(inspector.markup.tooltip, target);
+  yield isHoverTooltipTarget(inspector.markup.imagePreviewTooltip, target);
 
   checkImageTooltip(INITIAL_SRC_SIZE, inspector);
 
   info("Updating the image src.");
   yield updateImageSrc(img, UPDATED_SRC, inspector);
 
   target = container.editor.getAttributeElement("src").querySelector(".link");
   ok(target, "Found the src attribute in the markup view after mutation.");
 
   info("Showing tooltip on the src link.");
-  yield isHoverTooltipTarget(inspector.markup.tooltip, target);
+  yield isHoverTooltipTarget(inspector.markup.imagePreviewTooltip, target);
 
   info("Checking that the new image was shown.");
   checkImageTooltip(UPDATED_SRC_SIZE, inspector);
 });
 
 /**
  * Updates the src attribute of the image. Return a Promise.
  */
@@ -67,16 +67,17 @@ function updateImageSrc(img, newSrc, ins
   return Promise.all([onMutated, onModified]);
 }
 
 /**
  * Checks that the markup view tooltip contains an image element with the given
  * size.
  */
 function checkImageTooltip(size, {markup}) {
-  let images = markup.tooltip.panel.getElementsByTagName("image");
+  let panel = markup.imagePreviewTooltip.panel;
+  let images = panel.getElementsByTagName("img");
   is(images.length, 1, "Tooltip contains an image");
 
-  let label = markup.tooltip.panel.querySelector(".devtools-tooltip-caption");
+  let label = panel.querySelector(".devtools-tooltip-caption");
   is(label.textContent, size, "Tooltip label displays the right image size");
 
-  markup.tooltip.hide();
+  markup.imagePreviewTooltip.hide();
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_node_names_namespaced.js
@@ -0,0 +1,43 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test namespaced element node names in the markupview.
+
+const XHTML = `
+  <!DOCTYPE html>
+  <html xmlns="http://www.w3.org/1999/xhtml"
+        xmlns:svg="http://www.w3.org/2000/svg">
+    <body>
+      <svg:svg width="100" height="100">
+        <svg:clipPath id="clip">
+          <svg:rect id="rectangle" x="0" y="0" width="10" height="5"></svg:rect>
+        </svg:clipPath>
+        <svg:circle cx="0" cy="0" r="5"></svg:circle>
+      </svg:svg>
+    </body>
+  </html>
+`;
+
+const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML);
+
+add_task(function* () {
+  let {inspector} = yield openInspectorForURL(TEST_URI);
+
+  // Get and open the svg element to show its children.
+  let svgNodeFront = yield getNodeFront("svg", inspector);
+  yield inspector.markup.expandNode(svgNodeFront);
+  yield waitForMultipleChildrenUpdates(inspector);
+
+  let clipPathContainer = yield getContainerForSelector("clipPath", inspector);
+  info("Checking the clipPath element");
+  ok(clipPathContainer.editor.tag.textContent === "svg:clipPath",
+     "svg:clipPath node is correctly displayed");
+
+  let circlePathContainer = yield getContainerForSelector("circle", inspector);
+  info("Checking the circle element");
+  ok(circlePathContainer.editor.tag.textContent === "svg:circle",
+     "svg:circle node is correctly displayed");
+});
--- a/devtools/client/inspector/markup/test/browser_markup_node_not_displayed_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_node_not_displayed_01.js
@@ -13,17 +13,18 @@
 // testing children nodes.
 
 const TEST_URL = URL_ROOT + "doc_markup_not_displayed.html";
 const TEST_DATA = [
   {selector: "#normal-div", isDisplayed: true},
   {selector: "head", isDisplayed: false},
   {selector: "#display-none", isDisplayed: false},
   {selector: "#hidden-true", isDisplayed: false},
-  {selector: "#visibility-hidden", isDisplayed: true}
+  {selector: "#visibility-hidden", isDisplayed: true},
+  {selector: "#hidden-via-hide-shortcut", isDisplayed: false},
 ];
 
 add_task(function* () {
   let {inspector} = yield openInspectorForURL(TEST_URL);
 
   for (let {selector, isDisplayed} of TEST_DATA) {
     info("Getting node " + selector);
     let nodeFront = yield getNodeFront(selector, inspector);
--- a/devtools/client/inspector/markup/test/doc_markup_not_displayed.html
+++ b/devtools/client/inspector/markup/test/doc_markup_not_displayed.html
@@ -6,12 +6,13 @@
       display: none;
     }
   </style>
 </head>
 <body>
   <div id="normal-div"></div>
   <div id="display-none" style="display:none;"></div>
   <div id="hidden-true" hidden="true"></div>
+  <div id="hidden-via-hide-shortcut" class="__fx-devtools-hide-shortcut__"></div>
   <div id="visibility-hidden" style="visibility:hidden;"></div>
   <div id="hidden-via-stylesheet"></div>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/devtools/client/inspector/markup/test/helper_events_test_runner.js
+++ b/devtools/client/inspector/markup/test/helper_events_test_runner.js
@@ -53,17 +53,17 @@ function* checkEventsForNode(test, inspe
   let evHolder = container.elt.querySelector(".markupview-events");
 
   if (expected.length === 0) {
     // if no event is expected, simply check that the event bubble is hidden
     is(evHolder.style.display, "none", "event bubble should be hidden");
     return;
   }
 
-  let tooltip = inspector.markup.tooltip;
+  let tooltip = inspector.markup.eventDetailsTooltip;
 
   yield selectNode(selector, inspector);
 
   // Click button to show tooltip
   info("Clicking evHolder");
   EventUtils.synthesizeMouseAtCenter(evHolder, {},
     inspector.markup.doc.defaultView);
   yield tooltip.once("shown");
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -8,17 +8,17 @@
 
 const {Cc, Ci, Cu} = require("chrome");
 const promise = require("promise");
 const {CssLogic} = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
 const {TextProperty} =
       require("devtools/client/inspector/rules/models/text-property");
 const {promiseWarn} = require("devtools/client/inspector/shared/utils");
-const {parseDeclarations} = require("devtools/client/shared/css-parsing-utils");
+const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "osString", function () {
   return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
 });
 
 XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -2,17 +2,17 @@
 /* vim: set 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/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
-const {escapeCSSComment} = require("devtools/client/shared/css-parsing-utils");
+const {escapeCSSComment} = require("devtools/shared/css-parsing-utils");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "domUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 /**
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -47,18 +47,20 @@ support-files =
 [browser_rules_add-property_01.js]
 [browser_rules_add-property_02.js]
 [browser_rules_add-property-svg.js]
 [browser_rules_add-rule_01.js]
 [browser_rules_add-rule_02.js]
 [browser_rules_add-rule_03.js]
 [browser_rules_add-rule_04.js]
 [browser_rules_add-rule_05.js]
+[browser_rules_add-rule_06.js]
 [browser_rules_add-rule_pseudo_class.js]
 [browser_rules_add-rule_iframes.js]
+[browser_rules_add-rule-with-menu.js]
 [browser_rules_authored.js]
 [browser_rules_authored_color.js]
 [browser_rules_authored_override.js]
 [browser_rules_blob_stylesheet.js]
 [browser_rules_colorpicker-and-image-tooltip_01.js]
 [browser_rules_colorpicker-and-image-tooltip_02.js]
 [browser_rules_colorpicker-appears-on-swatch-click.js]
 [browser_rules_colorpicker-commit-on-ENTER.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule-with-menu.js
@@ -0,0 +1,45 @@
+/* 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";
+
+// Tests the a new CSS rule can be added using the context menu.
+
+const TEST_URI = '<div id="testid">Test Node</div>';
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+
+  yield selectNode("#testid", inspector);
+  yield addNewRuleFromContextMenu(inspector, view);
+  yield testNewRule(view);
+});
+
+function* addNewRuleFromContextMenu(inspector, view) {
+  info("Waiting for context menu to be shown");
+  let onPopup = once(view._contextmenu._menupopup, "popupshown");
+  let win = view.styleWindow;
+
+  EventUtils.synthesizeMouseAtCenter(view.element,
+    {button: 2, type: "contextmenu"}, win);
+  yield onPopup;
+
+  ok(!view._contextmenu.menuitemAddRule.hidden, "Add rule is visible");
+
+  info("Adding the new rule and expecting a ruleview-changed event");
+  let onRuleViewChanged = view.once("ruleview-changed");
+  view._contextmenu.menuitemAddRule.click();
+  view._contextmenu._menupopup.hidePopup();
+  yield onRuleViewChanged;
+}
+
+function* testNewRule(view) {
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let editor = ruleEditor.selectorText.ownerDocument.activeElement;
+  is(editor.value, "#testid", "Selector editor value is as expected");
+
+  info("Escaping from the selector field the change");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+}
--- a/devtools/client/inspector/rules/test/browser_rules_add-rule_05.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule_05.js
@@ -29,62 +29,44 @@ const TEST_DATA = [
   { node: ".class3.class4", expected: ".class3.class4" },
   { node: "p", expected: "p" },
   { node: "h1", expected: ".asd\\@\\@\\@\\@a\\!\\!\\!\\!\\:\\:\\:\\@asd" },
   { node: "h2", expected: "#asd\\@\\@\\@a\\!\\!2a" }
 ];
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view, testActor} = yield openRuleView();
+  let {inspector, view} = yield openRuleView();
 
   for (let data of TEST_DATA) {
     let {node, expected} = data;
     yield selectNode(node, inspector);
-    yield addNewRuleFromContextMenu(inspector, view);
-    yield testNewRule(view, expected, 1);
-
-    info("Resetting page content");
-    yield testActor.eval(
-      "content.document.body.innerHTML = `" + TEST_URI + "`;");
+    yield testNewRule(inspector, view, expected);
   }
 });
 
-function* addNewRuleFromContextMenu(inspector, view) {
-  info("Waiting for context menu to be shown");
-  let onPopup = once(view._contextmenu._menupopup, "popupshown");
-  let win = view.styleWindow;
-
-  EventUtils.synthesizeMouseAtCenter(view.element,
-    {button: 2, type: "contextmenu"}, win);
-  yield onPopup;
-
-  ok(!view._contextmenu.menuitemAddRule.hidden, "Add rule is visible");
+function* testNewRule(inspector, view, expected) {
+  info("Adding a new rule and expecting a ruleview-changed event");
+  let onRuleViewChanged = view.once("ruleview-changed");
+  yield addNewRule(inspector, view);
+  yield onRuleViewChanged;
 
-  info("Adding the new rule and expecting a ruleview-changed event");
-  let onRuleViewChanged = view.once("ruleview-changed");
-  view._contextmenu.menuitemAddRule.click();
-  view._contextmenu._menupopup.hidePopup();
-  yield onRuleViewChanged;
-}
-
-function* testNewRule(view, expected, index) {
-  let idRuleEditor = getRuleViewRuleEditor(view, index);
-  let editor = idRuleEditor.selectorText.ownerDocument.activeElement;
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let editor = ruleEditor.selectorText.ownerDocument.activeElement;
   is(editor.value, expected,
-      "Selector editor value is as expected: " + expected);
+     "Selector editor value is as expected: " + expected);
 
   info("Entering the escape key");
   EventUtils.synthesizeKey("VK_ESCAPE", {});
 
-  is(idRuleEditor.selectorText.textContent, expected,
-      "Selector text value is as expected: " + expected);
+  is(ruleEditor.selectorText.textContent, expected,
+     "Selector text value is as expected: " + expected);
 
   info("Adding new properties to new rule: " + expected);
-  let onRuleViewChanged = view.once("ruleview-changed");
-  idRuleEditor.addProperty("font-weight", "bold", "");
+  onRuleViewChanged = view.once("ruleview-changed");
+  ruleEditor.addProperty("font-weight", "bold", "");
   yield onRuleViewChanged;
 
-  let textProps = idRuleEditor.rule.textProps;
+  let textProps = ruleEditor.rule.textProps;
   let lastRule = textProps[textProps.length - 1];
   is(lastRule.name, "font-weight", "Last rule name is font-weight");
   is(lastRule.value, "bold", "Last rule value is bold");
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule_06.js
@@ -0,0 +1,49 @@
+/* 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";
+
+// Tests the behaviour of adding a new rule using the add rule button
+// on namespaced elements.
+
+const XHTML = `
+  <!DOCTYPE html>
+  <html xmlns="http://www.w3.org/1999/xhtml"
+        xmlns:svg="http://www.w3.org/2000/svg">
+    <body>
+      <svg:svg width="100" height="100">
+        <svg:clipPath>
+          <svg:rect x="0" y="0" width="10" height="5"></svg:rect>
+        </svg:clipPath>
+        <svg:circle cx="0" cy="0" r="5"></svg:circle>
+      </svg:svg>
+    </body>
+  </html>
+`;
+const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML);
+
+const TEST_DATA = [
+  { node: "clipPath", expected: "clipPath" },
+  { node: "rect", expected: "rect" },
+  { node: "circle", expected: "circle" }
+];
+
+add_task(function* () {
+  yield addTab(TEST_URI);
+  let {inspector, view} = yield openRuleView();
+
+  for (let data of TEST_DATA) {
+    let {node, expected} = data;
+    yield selectNode(node, inspector);
+    yield addNewRule(inspector, view);
+    yield testNewRule(view, expected, 1);
+  }
+});
+
+function* testNewRule(view, expected, index) {
+  let idRuleEditor = getRuleViewRuleEditor(view, index);
+  let editor = idRuleEditor.selectorText.ownerDocument.activeElement;
+  is(editor.value, expected,
+      "Selector editor value is as expected: " + expected);
+}
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -20,17 +20,17 @@ const {
   promiseWarn
 } = require("devtools/client/inspector/shared/utils");
 const {
   parseDeclarations,
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS
-} = require("devtools/client/shared/css-parsing-utils");
+} = require("devtools/shared/css-parsing-utils");
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 XPCOMUtils.defineLazyGetter(this, "_strings", function () {
   return Services.strings.createBundle(
     "chrome://devtools-shared/locale/styleinspector.properties");
 });
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -1,33 +1,31 @@
 /* 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 {Cc, Ci} = require("chrome");
+const {Ci} = require("chrome");
 const {CssLogic} = require("devtools/shared/inspector/css-logic");
 const {InplaceEditor, editableField} =
       require("devtools/client/shared/inplace-editor");
 const {
   createChild,
   appendText,
   advanceValidate,
   blurOnMultipleProperties,
   throttle
 } = require("devtools/client/inspector/shared/utils");
 const {
   parseDeclarations,
   parseSingleValue,
-} = require("devtools/client/shared/css-parsing-utils");
+} = require("devtools/shared/css-parsing-utils");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
-const IOService = Cc["@mozilla.org/network/io-service;1"]
-                  .getService(Ci.nsIIOService);
 
 /**
  * TextPropertyEditor is responsible for the following:
  *   Owns a TextProperty object.
  *   Manages changes to the TextProperty.
  *   Can be expanded to display computed properties.
  *   Can mark a property disabled or enabled.
  *
@@ -249,48 +247,16 @@ TextPropertyEditor.prototype = {
     let domRule = this.rule.domRule;
     if (domRule) {
       return domRule.href || domRule.nodeHref;
     }
     return undefined;
   },
 
   /**
-   * Get the URI from which to resolve relative requests for
-   * this rule's stylesheet.
-   *
-   * @return {nsIURI} A URI based on the the stylesheet's href.
-   */
-  get sheetURI() {
-    if (this._sheetURI === undefined) {
-      if (this.sheetHref) {
-        this._sheetURI = IOService.newURI(this.sheetHref, null, null);
-      } else {
-        this._sheetURI = null;
-      }
-    }
-
-    return this._sheetURI;
-  },
-
-  /**
-   * Resolve a URI based on the rule stylesheet
-   *
-   * @param {String} relativePath
-   *        the path to resolve
-   * @return {String} the resolved path.
-   */
-  resolveURI: function (relativePath) {
-    if (this.sheetURI) {
-      relativePath = this.sheetURI.resolve(relativePath);
-    }
-    return relativePath;
-  },
-
-  /**
    * Populate the span based on changes to the TextProperty.
    */
   update: function () {
     if (this.ruleView.isDestroyed) {
       return;
     }
 
     if (this.prop.enabled) {
@@ -348,17 +314,17 @@ TextPropertyEditor.prototype = {
       bezierSwatchClass: sharedSwatchClass + bezierSwatchClass,
       bezierClass: "ruleview-bezier",
       filterSwatchClass: sharedSwatchClass + filterSwatchClass,
       filterClass: "ruleview-filter",
       angleSwatchClass: sharedSwatchClass + angleSwatchClass,
       angleClass: "ruleview-angle",
       defaultColorType: !propDirty,
       urlClass: "theme-link",
-      baseURI: this.sheetURI
+      baseURI: this.sheetHref
     };
     let frag = outputParser.parseCssProperty(name, val, parserOptions);
     this.valueSpan.innerHTML = "";
     this.valueSpan.appendChild(frag);
 
     // Attach the color picker tooltip to the color swatches
     this._colorSwatchSpans =
       this.valueSpan.querySelectorAll("." + colorSwatchClass);
@@ -473,17 +439,17 @@ TextPropertyEditor.prototype = {
       });
       appendText(li, ": ");
 
       let outputParser = this.ruleView._outputParser;
       let frag = outputParser.parseCssProperty(
         computed.name, computed.value, {
           colorSwatchClass: "ruleview-swatch ruleview-colorswatch",
           urlClass: "theme-link",
-          baseURI: this.sheetURI
+          baseURI: this.sheetHref
         }
       );
 
       // Store the computed property value that was parsed for output
       computed.parsedValue = frag.textContent;
 
       createChild(li, "span", {
         class: "ruleview-propertyvalue theme-fg-color1",
--- a/devtools/client/inspector/shared/utils.js
+++ b/devtools/client/inspector/shared/utils.js
@@ -2,18 +2,17 @@
 /* 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/. */
 
 "use strict";
 
 const {Ci} = require("chrome");
-const {parseDeclarations} =
-      require("devtools/client/shared/css-parsing-utils");
+const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
 const promise = require("promise");
 const {getCSSLexer} = require("devtools/shared/css-lexer");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * Create a child element with a set of attributes.
  *
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -41,16 +41,17 @@ support-files =
 [browser_inspector_addNode_02.js]
 [browser_inspector_addNode_03.js]
 [browser_inspector_breadcrumbs.js]
 [browser_inspector_breadcrumbs_highlight_hover.js]
 [browser_inspector_breadcrumbs_keybinding.js]
 [browser_inspector_breadcrumbs_keyboard_trap.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_inspector_breadcrumbs_mutations.js]
+[browser_inspector_breadcrumbs_namespaced.js]
 [browser_inspector_delete-selected-node-01.js]
 [browser_inspector_delete-selected-node-02.js]
 [browser_inspector_delete-selected-node-03.js]
 [browser_inspector_destroy-after-navigation.js]
 [browser_inspector_destroy-before-ready.js]
 [browser_inspector_expand-collapse.js]
 [browser_inspector_gcli-inspect-command.js]
 [browser_inspector_highlighter-01.js]
@@ -86,16 +87,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_highlighter-rulers_01.js]
 [browser_inspector_highlighter-rulers_02.js]
 [browser_inspector_highlighter-selector_01.js]
 [browser_inspector_highlighter-selector_02.js]
 [browser_inspector_highlighter-xbl.js]
 [browser_inspector_highlighter-zoom.js]
 [browser_inspector_iframe-navigation.js]
 [browser_inspector_infobar_01.js]
+[browser_inspector_infobar_02.js]
 [browser_inspector_initialization.js]
 skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
 subsuite = clipboard
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu-01-sensitivity.js]
@@ -121,16 +123,17 @@ subsuite = clipboard
 [browser_inspector_remove-iframe-during-load.js]
 [browser_inspector_search-01.js]
 [browser_inspector_search-02.js]
 [browser_inspector_search-03.js]
 [browser_inspector_search-04.js]
 [browser_inspector_search-05.js]
 [browser_inspector_search-06.js]
 [browser_inspector_search-07.js]
+[browser_inspector_search-08.js]
 [browser_inspector_search_keyboard_trap.js]
 [browser_inspector_search-reserved.js]
 [browser_inspector_search-selection.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]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_breadcrumbs_namespaced.js
@@ -0,0 +1,55 @@
+/* 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 the breadcrumbs widget content for namespaced elements is correct.
+
+const XHTML = `
+  <!DOCTYPE html>
+  <html xmlns="http://www.w3.org/1999/xhtml"
+        xmlns:svg="http://www.w3.org/2000/svg">
+    <body>
+      <svg:svg width="100" height="100">
+        <svg:clipPath id="clip">
+          <svg:rect id="rectangle" x="0" y="0" width="10" height="5"></svg:rect>
+        </svg:clipPath>
+        <svg:circle cx="0" cy="0" r="5"></svg:circle>
+      </svg:svg>
+    </body>
+  </html>
+`;
+
+const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML);
+
+const NODES = [
+  {selector: "clipPath", nodes: ["svg:svg", "svg:clipPath"],
+    nodeName: "svg:clipPath", title: "svg:clipPath#clip"},
+  {selector: "circle", nodes: ["svg:svg", "svg:circle"],
+    nodeName: "svg:circle", title: "svg:circle"},
+];
+
+add_task(function* () {
+  let { inspector } = yield openInspectorForURL(TEST_URI);
+  let container = inspector.panelDoc.getElementById("inspector-breadcrumbs");
+
+  for (let node of NODES) {
+    info("Testing node " + node.selector);
+
+    info("Selecting node and waiting for breadcrumbs to update");
+    let breadcrumbsUpdated = inspector.once("breadcrumbs-updated");
+    yield selectNode(node.selector, inspector);
+    yield breadcrumbsUpdated;
+
+    info("Performing checks for node " + node.selector);
+
+    let checkedButton = container.querySelector("button[checked]");
+
+    let labelTag = checkedButton.querySelector(".breadcrumbs-widget-item-tag");
+    is(labelTag.textContent, node.nodeName,
+      "Node " + node.selector + " has the expected tag name");
+
+    is(checkedButton.getAttribute("tooltiptext"), node.title,
+      "Node " + node.selector + " has the expected tooltip");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_infobar_02.js
@@ -0,0 +1,50 @@
+/* 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";
+
+// Check the text content of the highlighter info bar for namespaced elements.
+
+const XHTML = `
+  <!DOCTYPE html>
+  <html xmlns="http://www.w3.org/1999/xhtml"
+        xmlns:svg="http://www.w3.org/2000/svg">
+    <body>
+      <svg:svg width="100" height="100">
+        <svg:circle cx="0" cy="0" r="5"></svg:circle>
+      </svg:svg>
+    </body>
+  </html>
+`;
+
+const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML);
+
+add_task(function* () {
+  let {inspector, testActor} = yield openInspectorForURL(TEST_URI);
+
+  let testData = [
+    {
+      selector: "svg",
+      tag: "svg:svg"
+    },
+    {
+      selector: "circle",
+      tag: "svg:circle"
+    },
+  ];
+
+  for (let currTest of testData) {
+    yield testNode(currTest, inspector, testActor);
+  }
+});
+
+function* testNode(test, inspector, testActor) {
+  info("Testing " + test.selector);
+
+  yield selectAndHighlightNode(test.selector, inspector);
+
+  let tag = yield testActor.getHighlighterNodeTextContent(
+    "box-model-nodeinfobar-tagname");
+  is(tag, test.tag, "node " + test.selector + ": tagName matches.");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_search-08.js
@@ -0,0 +1,64 @@
+/* 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 searching for namespaced elements does work.
+
+const XHTML = `
+  <!DOCTYPE html>
+  <html xmlns="http://www.w3.org/1999/xhtml"
+        xmlns:svg="http://www.w3.org/2000/svg">
+    <body>
+      <svg:svg width="100" height="100">
+        <svg:clipPath>
+          <svg:rect x="0" y="0" width="10" height="5"></svg:rect>
+        </svg:clipPath>
+        <svg:circle cx="0" cy="0" r="5"></svg:circle>
+      </svg:svg>
+    </body>
+  </html>
+`;
+
+const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML);
+
+// An array of (key, suggestions) pairs where key is a key to press and
+// suggestions is an array of suggestions that should be shown in the popup.
+const TEST_DATA = [{
+  key: "c",
+  suggestions: ["circle", "clipPath"]
+}, {
+  key: "VK_BACK_SPACE",
+  suggestions: []
+}, {
+  key: "s",
+  suggestions: ["svg"]
+}];
+
+add_task(function* () {
+  let {inspector} = yield openInspectorForURL(TEST_URI);
+  let {searchBox} = inspector;
+  let popup = inspector.searchSuggestions.searchPopup;
+
+  yield focusSearchBoxUsingShortcut(inspector.panelWin);
+
+  for (let {key, suggestions} of TEST_DATA) {
+    info("Pressing " + key + " to get " + suggestions.join(", "));
+
+    let command = once(searchBox, "command");
+    EventUtils.synthesizeKey(key, {}, inspector.panelWin);
+    yield command;
+
+    info("Waiting for search query to complete and getting the suggestions");
+    yield inspector.searchSuggestions._lastQuery;
+    let actualSuggestions = popup.getItems().reverse();
+
+    is(popup.isOpen ? actualSuggestions.length : 0, suggestions.length,
+       "There are expected number of suggestions.");
+
+    for (let i = 0; i < suggestions.length; i++) {
+      is(actualSuggestions[i].label, suggestions[i],
+         "The suggestion at " + i + "th index is correct.");
+    }
+  }
+});
--- a/devtools/client/responsive.html/components/device-selector.js
+++ b/devtools/client/responsive.html/components/device-selector.js
@@ -59,16 +59,20 @@ module.exports = createClass({
     for (let type of devices.types) {
       for (let device of devices[type]) {
         if (device.displayed) {
           options.push(device);
         }
       }
     }
 
+    options.sort(function (a, b) {
+      return a.name.localeCompare(b.name);
+    });
+
     let selectClass = "viewport-device-selector";
     if (selectedDevice) {
       selectClass += " selected";
     }
 
     return dom.select(
       {
         className: selectClass,
--- a/devtools/client/shared/components/frame.js
+++ b/devtools/client/shared/components/frame.js
@@ -98,17 +98,21 @@ module.exports = createClass({
       // Add `data-line` attribute for testing
       attributes["data-line"] = line;
     }
 
     // If source is not a URL (self-hosted, eval, etc.), don't make
     // it an anchor link, as we can't link to it.
     if (isLinkable) {
       sourceEl = dom.a({
-        onClick,
+        onClick: e => {
+          e.preventDefault();
+          onClick(e);
+        },
+        href: source,
         className: "frame-link-source",
         title: l10n.getFormatStr("frame.viewsourceindebugger", tooltip)
       }, sourceElements);
     } else {
       sourceEl = dom.span({
         className: "frame-link-source",
         title: tooltip,
       }, sourceElements);
--- a/devtools/client/shared/components/reps/url.js
+++ b/devtools/client/shared/components/reps/url.js
@@ -1,14 +1,14 @@
 /* -*- 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 URLSearchParams, URL */
+/* global URLSearchParams */
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   function parseURLParams(url) {
     url = new URL(url);
     return parseURLEncodedText(url.searchParams);
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -136,30 +136,33 @@ var TEST_TREE = {
     O: "N"
   },
   expanded: new Set(),
 };
 
 /**
  * Frame
  */
-function checkFrameString({ frame, file, line, column, shouldLink, tooltip }) {
+function checkFrameString({ frame, file, line, column, source, shouldLink, tooltip }) {
   let el = frame.getDOMNode();
   let $ = selector => el.querySelector(selector);
 
   let $source = $(".frame-link-source");
   let $filename = $(".frame-link-filename");
   let $line = $(".frame-link-line");
   let $column = $(".frame-link-column");
 
   is($filename.textContent, file, "Correct filename");
   is(el.getAttribute("data-line"), line ? `${line}` : null, "Expected `data-line` found");
   is(el.getAttribute("data-column"), column ? `${column}` : null, "Expected `data-column` found");
   is($source.getAttribute("title"), tooltip, "Correct tooltip");
   is($source.tagName, shouldLink ? "A" : "SPAN", "Correct linkable status");
+  if (shouldLink) {
+    is($source.getAttribute("href"), source, "Correct source");
+  }
 
   if (line != null) {
     is(+$line.textContent, +line);
   } else {
     ok(!$line, "Should not have an element for `line`");
   }
 
   if (column != null) {
--- a/devtools/client/shared/components/test/mochitest/test_frame_01.html
+++ b/devtools/client/shared/components/test/mochitest/test_frame_01.html
@@ -142,17 +142,18 @@ window.onload = Task.async(function* () 
     });
 
     function* checkFrameComponent (input, expected) {
       let frame = ReactDOM.render(Frame({
         frame: input,
         onClick: () => {},
       }), window.document.body);
       yield forceRender(frame);
-      checkFrameString(Object.assign({ frame }, expected));
+      let source = input.source;
+      checkFrameString(Object.assign({ frame, source }, expected));
     }
 
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -16,17 +16,16 @@ DIRS += [
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
     'css-angle.js',
     'css-color-db.js',
     'css-color.js',
-    'css-parsing-utils.js',
     'css-reload.js',
     'Curl.jsm',
     'demangle.js',
     'developer-toolbar.js',
     'devices.js',
     'devtools-file-watcher.js',
     'DOMHelpers.jsm',
     'doorhanger.js',
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const {angleUtils} = require("devtools/client/shared/css-angle");
 const {colorUtils} = require("devtools/client/shared/css-color");
 const {getCSSLexer} = require("devtools/shared/css-lexer");
-const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 const BEZIER_KEYWORDS = ["linear", "ease-in-out", "ease-in", "ease-out",
                          "ease"];
 
 // Functions that accept a color argument.
@@ -525,17 +524,17 @@ OutputParser.prototype = {
       // seemed simpler on the whole.
       let [, leader, , body, trailer] =
         /^(url\([ \t\r\n\f]*(["']?))(.*?)(\2[ \t\r\n\f]*\))$/i.exec(match);
 
       this._appendTextNode(leader);
 
       let href = url;
       if (options.baseURI) {
-        href = options.baseURI.resolve(url);
+        href = new URL(url, options.baseURI).href;
       }
 
       this._appendNode("a", {
         target: "_blank",
         class: options.urlClass,
         href: href
       }, body);
 
@@ -643,17 +642,17 @@ OutputParser.prototype = {
    *           - bezierSwatchClass: ""  // The class to use for bezier swatches.
    *           - bezierClass: ""        // The class to use for the bezier value
    *                                    // that follows the swatch.
    *           - angleSwatchClass: ""   // The class to use for angle swatches.
    *           - angleClass: ""         // The class to use for the angle value
    *                                    // that follows the swatch.
    *           - supportsColor: false   // Does the CSS property support colors?
    *           - urlClass: ""           // The class to be used for url() links.
-   *           - baseURI: ""            // A string or nsIURI used to resolve
+   *           - baseURI: undefined     // A string used to resolve
    *                                    // relative links.
    *           - filterSwatch: false    // A special case for parsing a
    *                                    // "filter" property, causing the
    *                                    // parser to skip the call to
    *                                    // _wrapFilter.  Used only for
    *                                    // previewing with the filter swatch.
    * @return {Object}
    *         Overridden options object
@@ -664,24 +663,20 @@ OutputParser.prototype = {
       colorSwatchClass: "",
       colorClass: "",
       bezierSwatchClass: "",
       bezierClass: "",
       angleSwatchClass: "",
       angleClass: "",
       supportsColor: false,
       urlClass: "",
-      baseURI: "",
+      baseURI: undefined,
       filterSwatch: false
     };
 
-    if (typeof overrides.baseURI === "string") {
-      overrides.baseURI = Services.io.newURI(overrides.baseURI, null, null);
-    }
-
     for (let item in overrides) {
       defaults[item] = overrides[item];
     }
     return defaults;
   }
 };
 
 /**
--- a/devtools/client/shared/source-utils.js
+++ b/devtools/client/shared/source-utils.js
@@ -1,14 +1,13 @@
 /* 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 { URL } = require("sdk/url");
 const { LocalizationHelper } = require("devtools/client/shared/l10n");
 
 const l10n = new LocalizationHelper("chrome://devtools/locale/components.properties");
 const UNKNOWN_SOURCE_STRING = l10n.getStr("frame.unknownSource");
 
 // Character codes used in various parsing helper functions.
 const CHAR_CODE_A = "a".charCodeAt(0);
 const CHAR_CODE_C = "c".charCodeAt(0);
@@ -25,67 +24,74 @@ const CHAR_CODE_P = "p".charCodeAt(0);
 const CHAR_CODE_R = "r".charCodeAt(0);
 const CHAR_CODE_S = "s".charCodeAt(0);
 const CHAR_CODE_T = "t".charCodeAt(0);
 const CHAR_CODE_U = "u".charCodeAt(0);
 const CHAR_CODE_COLON = ":".charCodeAt(0);
 const CHAR_CODE_SLASH = "/".charCodeAt(0);
 const CHAR_CODE_CAP_S = "S".charCodeAt(0);
 
-// The cache used in the `nsIURL` function.
+// The cache used in the `parseURL` function.
 const gURLStore = new Map();
 // The cache used in the `getSourceNames` function.
 const gSourceNamesStore = new Map();
 
 /**
  * Takes a string and returns an object containing all the properties
  * available on an URL instance, with additional properties (fileName),
  * Leverages caching.
  *
- * @TODO If loaded through Browser Loader, we can use the web API URL
- * directly, giving us the same interface without needing the SDK --
- * we still need to add `fileName` though.
- *
  * @param {String} location
  * @return {Object?} An object containing most properties available
  *                   in https://developer.mozilla.org/en-US/docs/Web/API/URL
  */
 
 function parseURL(location) {
   let url = gURLStore.get(location);
 
   if (url !== void 0) {
     return url;
   }
 
   try {
     url = new URL(location);
+    // The callers were generally written to expect a URL from
+    // sdk/url, which is subtly different.  So, work around some
+    // important differences here.
+    url = {
+      href: url.href,
+      protocol: url.protocol,
+      host: url.host,
+      hostname: url.hostname,
+      port: url.port || null,
+      pathname: url.pathname,
+      search: url.search,
+      hash: url.hash,
+      username: url.username,
+      password: url.password,
+      origin: url.origin,
+    };
+
     // Definitions:
     // Example: https://foo.com:8888/file.js
     // `hostname`: "foo.com"
     // `host`: "foo.com:8888"
-    //
-    // sdk/url does not match several definitions.: both `host` and `hostname`
-    // are actually the `hostname` (even though this is the `host` property on
-    // the original nsIURL, with `hostPort` representing the actual `host` name,
-    // AH!!!). So normalize all that garbage here.
     let isChrome = isChromeScheme(location);
-    let fileName = url.fileName || "/";
-    let hostname, host;
+
+    url.fileName = url.pathname ?
+      (url.pathname.slice(url.pathname.lastIndexOf("/") + 1) || "/") :
+      "/";
+
     if (isChrome) {
-      hostname = null;
-      host = null;
-    } else {
-      hostname = url.hostname;
-      host = url.port ? `${url.host}:${url.port}` : url.host;
+      url.hostname = null;
+      url.host = null;
     }
 
-    let parsed = Object.assign({}, url, { host, fileName, hostname });
-    gURLStore.set(location, parsed);
-    return parsed;
+    gURLStore.set(location, url);
+    return url;
   } catch (e) {
     gURLStore.set(location, null);
     return null;
   }
 }
 
 /**
  * Parse a source into a short and long name as well as a host name.
--- a/devtools/client/shared/test/browser_filter-editor-01.js
+++ b/devtools/client/shared/test/browser_filter-editor-01.js
@@ -1,19 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that the Filter Editor Widget parses filter values correctly (setCssValue)
 
 const TEST_URI = "chrome://devtools/content/shared/widgets/filter-frame.xhtml";
-const {CSSFilterEditorWidget} =
-      require("devtools/client/shared/widgets/FilterWidget");
-const {cssTokenizer} = require("devtools/client/shared/css-parsing-utils");
+const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
 const DOMUtils =
       Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 
 // Verify that the given string consists of a valid CSS URL token.
 // Return true on success, false on error.
 function verifyURL(string) {
   let lexer = DOMUtils.getCSSLexer(string);
 
--- a/devtools/client/shared/test/unit/test_escapeCSSComment.js
+++ b/devtools/client/shared/test/unit/test_escapeCSSComment.js
@@ -2,18 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 Cu.import("resource://devtools/shared/Loader.jsm");
-const {escapeCSSComment, _unescapeCSSComment} =
-      devtools.require("devtools/client/shared/css-parsing-utils");
+const {escapeCSSComment, _unescapeCSSComment} = devtools.require("devtools/shared/css-parsing-utils");
 
 const TEST_DATA = [
   {
     input: "simple",
     expected: "simple"
   },
   {
     input: "/* comment */",
--- a/devtools/client/shared/test/unit/test_parseDeclarations.js
+++ b/devtools/client/shared/test/unit/test_parseDeclarations.js
@@ -2,18 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {parseDeclarations, _parseCommentDeclarations} =
-  require("devtools/client/shared/css-parsing-utils");
+const {parseDeclarations, _parseCommentDeclarations} = require("devtools/shared/css-parsing-utils");
 
 const TEST_DATA = [
   // Simple test
   {
     input: "p:v;",
     expected: [{name: "p", value: "v", priority: "", offsets: [0, 4]}]
   },
   // Simple test
--- a/devtools/client/shared/test/unit/test_parsePseudoClassesAndAttributes.js
+++ b/devtools/client/shared/test/unit/test_parsePseudoClassesAndAttributes.js
@@ -7,17 +7,17 @@
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {
   parsePseudoClassesAndAttributes,
   SELECTOR_ATTRIBUTE,
   SELECTOR_ELEMENT,
   SELECTOR_PSEUDO_CLASS
-} = require("devtools/client/shared/css-parsing-utils");
+} = require("devtools/shared/css-parsing-utils");
 
 const TEST_DATA = [
   // Test that a null input throws an exception
   {
     input: null,
     throws: true
   },
   // Test that a undefined input throws an exception
--- a/devtools/client/shared/test/unit/test_parseSingleValue.js
+++ b/devtools/client/shared/test/unit/test_parseSingleValue.js
@@ -2,17 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {parseSingleValue} = require("devtools/client/shared/css-parsing-utils");
+const {parseSingleValue} = require("devtools/shared/css-parsing-utils");
 
 const TEST_DATA = [
   {input: null, throws: true},
   {input: undefined, throws: true},
   {input: "", expected: {value: "", priority: ""}},
   {input: "  \t \t \n\n  ", expected: {value: "", priority: ""}},
   {input: "blue", expected: {value: "blue", priority: ""}},
   {input: "blue !important", expected: {value: "blue", priority: "important"}},
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -2,18 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 Cu.import("resource://devtools/shared/Loader.jsm");
-const {parseDeclarations, RuleRewriter} =
-      devtools.require("devtools/client/shared/css-parsing-utils");
+const {RuleRewriter} = devtools.require("devtools/shared/css-parsing-utils");
 
 const TEST_DATA = [
   {
     desc: "simple set",
     input: "p:v;",
     instruction: {type: "set", name: "p", value: "N", priority: "",
                   index: 0},
     expected: "p:N;"
--- a/devtools/client/shared/widgets/FilterWidget.js
+++ b/devtools/client/shared/widgets/FilterWidget.js
@@ -11,17 +11,17 @@
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const { Cc, Ci } = require("chrome");
 
 const { LocalizationHelper } = require("devtools/client/shared/l10n");
 const STRINGS_URI = "chrome://devtools/locale/filterwidget.properties";
 const L10N = new LocalizationHelper(STRINGS_URI);
 
-const {cssTokenizer} = require("devtools/client/shared/css-parsing-utils");
+const {cssTokenizer} = require("devtools/shared/css-parsing-utils");
 
 loader.lazyGetter(this, "asyncStorage",
                   () => require("devtools/shared/async-storage"));
 
 loader.lazyGetter(this, "DOMUtils", () => {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
--- a/devtools/client/shared/widgets/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/HTMLTooltip.js
@@ -2,16 +2,18 @@
 /* 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/. */
 
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
+const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
+
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 const IFRAME_URL = "chrome://devtools/content/shared/widgets/tooltip-frame.xhtml";
 const IFRAME_CONTAINER_ID = "tooltip-iframe-container";
 
 const POSITION = {
   TOP: "top",
@@ -66,16 +68,20 @@ function HTMLTooltip(toolbox,
   this.autofocus = autofocus;
   this.consumeOutsideClicks = consumeOutsideClicks;
 
   // Use the topmost window to listen for click events to close the tooltip
   this.topWindow = this.doc.defaultView.top;
 
   this._onClick = this._onClick.bind(this);
 
+  this._toggle = new TooltipToggle(this);
+  this.startTogglingOnHover = this._toggle.start.bind(this._toggle);
+  this.stopTogglingOnHover = this._toggle.stop.bind(this._toggle);
+
   this.container = this._createContainer();
 
   // Promise that will resolve when the container can be filled with content.
   this.containerReady = new Promise(resolve => {
     if (this._isXUL()) {
       // In XUL context, load a placeholder document in the iframe container.
       let onLoad = () => {
         this.frame.removeEventListener("load", onLoad, true);
@@ -229,17 +235,17 @@ HTMLTooltip.prototype = {
     let container = this.doc.createElementNS(XHTML_NS, "div");
     container.setAttribute("type", this.type);
     container.classList.add("tooltip-container");
 
     let html;
     if (this._isXUL()) {
       html = '<iframe class="devtools-tooltip-iframe tooltip-panel"></iframe>';
     } else {
-      html = '<div class="tooltip-panel theme-body"></div>';
+      html = '<div class="tooltip-panel"></div>';
     }
 
     if (this.type === TYPE.ARROW) {
       html += '<div class="tooltip-arrow"></div>';
     }
     container.innerHTML = html;
     return container;
   },
--- a/devtools/client/shared/widgets/tooltip-frame.xhtml
+++ b/devtools/client/shared/widgets/tooltip-frame.xhtml
@@ -12,14 +12,18 @@
     html, body, #tooltip-iframe-container {
       margin: 0;
       padding: 0;
       width: 100%;
       height: 100%;
       overflow: hidden;
       color: var(--theme-body-color);
     }
+
+    :root[platform="linux"] body {
+      font-size: 80%;
+    }
   </style>
 </head>
-<body role="application" class="theme-body">
+<body role="application">
   <div id="tooltip-iframe-container"></div>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/widgets/tooltip/ImageTooltipHelper.js
@@ -0,0 +1,145 @@
+/* -*- 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/. */
+
+"use strict";
+
+const Services = require("Services");
+loader.lazyGetter(this, "GetStringFromName", () => {
+  let bundle = Services.strings.createBundle(
+    "chrome://devtools/locale/inspector.properties");
+  return key => {
+    return bundle.GetStringFromName(key);
+  };
+});
+
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+// Default image tooltip max dimension
+const MAX_DIMENSION = 200;
+const CONTAINER_MIN_WIDTH = 100;
+const LABEL_HEIGHT = 20;
+const IMAGE_PADDING = 4;
+
+/**
+ * Image preview tooltips should be provided with the naturalHeight and
+ * naturalWidth value for the image to display. This helper loads the provided
+ * image URL in an image object in order to retrieve the image dimensions after
+ * the load.
+ *
+ * @param {Document} doc the document element to use to create the image object
+ * @param {String} imageUrl the url of the image to measure
+ * @return {Promise} returns a promise that will resolve after the iamge load:
+ *         - {Number} naturalWidth natural width of the loaded image
+ *         - {Number} naturalHeight natural height of the loaded image
+ */
+function getImageDimensions(doc, imageUrl) {
+  return new Promise(resolve => {
+    let imgObj = new doc.defaultView.Image();
+    imgObj.onload = () => {
+      imgObj.onload = null;
+      let { naturalWidth, naturalHeight } = imgObj;
+      resolve({ naturalWidth, naturalHeight });
+    };
+    imgObj.src = imageUrl;
+  });
+}
+
+/**
+ * Set the tooltip content of a provided HTMLTooltip instance to display an
+ * image preview matching the provided imageUrl.
+ *
+ * @param {HTMLTooltip} tooltip
+ *        The tooltip instance on which the image preview content should be set
+ * @param {Document} doc
+ *        A document element to create the HTML elements needed for the tooltip
+ * @param {String} imageUrl
+ *        Absolute URL of the image to display in the tooltip
+ * @param {Object} options
+ *        - {Number} naturalWidth mandatory, width of the image to display
+ *        - {Number} naturalHeight mandatory, height of the image to display
+ *        - {Number} maxDim optional, max width/height of the preview
+ *        - {Boolean} hideDimensionLabel optional, pass true to hide the label
+ * @return {Promise} promise that will resolve when the tooltip content has been
+ *         set
+ */
+function setImageTooltip(tooltip, doc, imageUrl, options) {
+  let {naturalWidth, naturalHeight, hideDimensionLabel, maxDim} = options;
+  maxDim = maxDim || MAX_DIMENSION;
+
+  let imgHeight = naturalHeight;
+  let imgWidth = naturalWidth;
+  if (imgHeight > maxDim || imgWidth > maxDim) {
+    let scale = maxDim / Math.max(imgHeight, imgWidth);
+    // Only allow integer values to avoid rounding errors.
+    imgHeight = Math.floor(scale * naturalHeight);
+    imgWidth = Math.ceil(scale * naturalWidth);
+  }
+
+  // Create tooltip content
+  let div = doc.createElementNS(XHTML_NS, "div");
+  div.style.cssText = `
+    height: 100%;
+    min-width: 100px;
+    display: flex;
+    flex-direction: column;
+    text-align: center;`;
+  let html = `
+    <div style="flex: 1;
+                display: flex;
+                padding: ${IMAGE_PADDING}px;
+                align-items: center;
+                justify-content: center;
+                min-height: 1px;">
+      <img style="height: ${imgHeight}px; max-height: 100%;" src="${imageUrl}"/>
+    </div>`;
+
+  if (!hideDimensionLabel) {
+    let label = naturalWidth + " \u00D7 " + naturalHeight;
+    html += `
+      <div style="height: ${LABEL_HEIGHT}px;
+                  text-align: center;">
+        <span class="theme-comment devtools-tooltip-caption">${label}</span>
+      </div>`;
+  }
+  div.innerHTML = html;
+
+  // Calculate tooltip dimensions
+  let height = imgHeight + 2 * IMAGE_PADDING;
+  if (!hideDimensionLabel) {
+    height += LABEL_HEIGHT;
+  }
+  let width = Math.max(CONTAINER_MIN_WIDTH, imgWidth + 2 * IMAGE_PADDING);
+
+  return tooltip.setContent(div, width, height);
+}
+
+/*
+ * Set the tooltip content of a provided HTMLTooltip instance to display a
+ * fallback error message when an image preview tooltip can not be displayed.
+ *
+ * @param {HTMLTooltip} tooltip
+ *        The tooltip instance on which the image preview content should be set
+ * @param {Document} doc
+ *        A document element to create the HTML elements needed for the tooltip
+ * @return {Promise} promise that will resolve when the tooltip content has been
+ *         set
+ */
+function setBrokenImageTooltip(tooltip, doc) {
+  let div = doc.createElementNS(XHTML_NS, "div");
+  div.style.cssText = `
+    box-sizing: border-box;
+    height: 100%;
+    text-align: center;
+    line-height: 30px;`;
+
+  let message = GetStringFromName("previewTooltip.image.brokenImage");
+  div.textContent = message;
+  return tooltip.setContent(div, 150, 30);
+}
+
+module.exports.getImageDimensions = getImageDimensions;
+module.exports.setImageTooltip = setImageTooltip;
+module.exports.setBrokenImageTooltip = setBrokenImageTooltip;
--- a/devtools/client/shared/widgets/tooltip/moz.build
+++ b/devtools/client/shared/widgets/tooltip/moz.build
@@ -1,9 +1,10 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
+    'ImageTooltipHelper.js',
     'TooltipToggle.js',
 )
--- a/devtools/client/sourceeditor/css-autocompleter.js
+++ b/devtools/client/sourceeditor/css-autocompleter.js
@@ -1,17 +1,16 @@
 /* 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 { Cc, Ci } = require("chrome");
-const {cssTokenizer, cssTokenizerWithLineColumn} =
-      require("devtools/client/shared/css-parsing-utils");
+const {cssTokenizer, cssTokenizerWithLineColumn} = require("devtools/shared/css-parsing-utils");
 
 /**
  * Here is what this file (+ css-parsing-utils.js) do.
  *
  * The main objective here is to provide as much suggestions to the user editing
  * a stylesheet in Style Editor. The possible things that can be suggested are:
  *  - CSS property names
  *  - CSS property values
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -1,31 +1,30 @@
 /* -*- 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/. */
 
 "use strict";
 
-const {Cc, Ci, Cu} = require("chrome");
+const {Ci, Cu} = require("chrome");
 
 const Services = require("Services");
 
 loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
 loader.lazyImporter(this, "escapeHTML", "resource://devtools/client/shared/widgets/VariablesView.jsm");
 loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "TableWidget", "devtools/client/shared/widgets/TableWidget", true);
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true);
 
 const { extend } = require("sdk/core/heritage");
-const URI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
 
 const WebConsoleUtils = require("devtools/shared/webconsole/utils").Utils;
 const { getSourceNames } = require("devtools/client/shared/source-utils");
 const {Task} = require("devtools/shared/task");
 const l10n = new WebConsoleUtils.L10n(STRINGS_URI);
@@ -2292,19 +2291,19 @@ Widgets.URLString.prototype = extend(Wid
    *
    * @param string token
    *        The token.
    * @return boolean
    *         Whenther the token is a URL.
    */
   _isURL: function (token) {
     try {
-      let uri = URI.newURI(token, null, null);
-      let url = uri.QueryInterface(Ci.nsIURL);
-      return true;
+      let url = new URL(token);
+      return ["http:", "https:", "ftp:", "data:", "javascript:",
+              "resource:", "chrome:"].includes(url.protocol);
     } catch (e) {
       return false;
     }
   },
 
   /**
    * Renders a string as a URL.
    *
--- a/devtools/client/webconsole/test/browser.ini
+++ b/devtools/client/webconsole/test/browser.ini
@@ -361,16 +361,17 @@ tags = trackingprotection
 [browser_webconsole_output_05.js]
 [browser_webconsole_output_06.js]
 [browser_webconsole_output_dom_elements_01.js]
 [browser_webconsole_output_dom_elements_02.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
 [browser_webconsole_output_dom_elements_03.js]
 [browser_webconsole_output_dom_elements_04.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_output_dom_elements_05.js]
 [browser_webconsole_output_events.js]
 [browser_webconsole_output_regexp.js]
 [browser_webconsole_output_table.js]
 [browser_console_variables_view_highlighter.js]
 [browser_webconsole_start_netmon_first.js]
 [browser_webconsole_console_trace_duplicates.js]
 [browser_webconsole_cd_iframe.js]
 [browser_webconsole_autocomplete_crossdomain_iframe.js]
--- a/devtools/client/webconsole/test/browser_webconsole_bug_1010953_cspro.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_1010953_cspro.js
@@ -33,17 +33,17 @@ const CSP_REPORT_MSG = "Content Security
 add_task(function* () {
   let { browser } = yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
 
   hud.jsterm.clearOutput();
 
   let loaded = loadBrowser(browser);
-  content.location = TEST_VIOLATION;
+  BrowserTestUtils.loadURI(browser, TEST_VIOLATION);
   yield loaded;
 
   yield waitForSuccess({
     name: "Confirmed that CSP and CSP-Report-Only log different messages to " +
           "the console.",
     validator: function () {
       console.log(hud.outputNode.textContent);
       let success = false;
--- a/devtools/client/webconsole/test/browser_webconsole_output_dom_elements_02.js
+++ b/devtools/client/webconsole/test/browser_webconsole_output_dom_elements_02.js
@@ -1,54 +1,54 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* 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/ */
 
 // Test the inspector links in the webconsole output for DOM Nodes actually
-// open the inspector and select the right node
+// open the inspector and select the right node.
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/test-console-output-dom-elements.html";
 
 const TEST_DATA = [
   {
     // The first test shouldn't be returning the body element as this is the
     // default selected node, so re-selecting it won't fire the
     // inspector-updated event
     input: "testNode()",
     output: '<p some-attribute="some-value">',
-    tagName: "P",
+    displayName: "p",
     attrs: [{name: "some-attribute", value: "some-value"}]
   },
   {
     input: "testBodyNode()",
     output: '<body class="body-class" id="body-id">',
-    tagName: "BODY",
+    displayName: "body",
     attrs: [
       {
         name: "class", value: "body-class"
       },
       {
         name: "id", value: "body-id"
       }
     ]
   },
   {
     input: "testNodeInIframe()",
     output: "<p>",
-    tagName: "P",
+    displayName: "p",
     attrs: []
   },
   {
     input: "testDocumentElement()",
     output: '<html dir="ltr" lang="en-US">',
-    tagName: "HTML",
+    displayName: "html",
     attrs: [
       {
         name: "dir",
         value: "ltr"
       },
       {
         name: "lang",
         value: "en-US"
@@ -56,87 +56,11 @@ const TEST_DATA = [
     ]
   }
 ];
 
 function test() {
   Task.spawn(function* () {
     let {tab} = yield loadTab(TEST_URI);
     let hud = yield openConsole(tab);
-    let toolbox = gDevTools.getToolbox(hud.target);
-
-    // Loading the inspector panel at first, to make it possible to listen for
-    // new node selections
-    yield toolbox.selectTool("inspector");
-    let inspector = toolbox.getCurrentPanel();
-    yield toolbox.selectTool("webconsole");
-
-    info("Iterating over the test data");
-    for (let data of TEST_DATA) {
-      let [result] = yield jsEval(data.input, hud, {text: data.output});
-      let {msg} = yield getWidgetAndMessage(result);
-
-      let inspectorIcon = msg.querySelector(".open-inspector");
-      ok(inspectorIcon, "Inspector icon found in the ElementNode widget");
-
-      info("Clicking on the inspector icon and waiting for the " +
-           "inspector to be selected");
-      let onInspectorSelected = toolbox.once("inspector-selected");
-      let onInspectorUpdated = inspector.once("inspector-updated");
-      let onNewNode = toolbox.selection.once("new-node-front");
-      let onNodeHighlight = toolbox.once("node-highlight");
-
-      EventUtils.synthesizeMouseAtCenter(inspectorIcon, {},
-        inspectorIcon.ownerDocument.defaultView);
-      yield onInspectorSelected;
-      yield onInspectorUpdated;
-      yield onNodeHighlight;
-      let nodeFront = yield onNewNode;
-
-      ok(true, "Inspector selected and new node got selected");
-
-      is(nodeFront.tagName, data.tagName, "The correct node was highlighted");
-
-      let attrs = nodeFront.attributes;
-      for (let i in data.attrs) {
-        is(attrs[i].name, data.attrs[i].name,
-           "The correct node was highlighted");
-        is(attrs[i].value, data.attrs[i].value,
-           "The correct node was highlighted");
-      }
-
-      info("Unhighlight the node by moving away from the markup view");
-      let onNodeUnhighlight = toolbox.once("node-unhighlight");
-      let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button");
-      EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"},
-        inspector.toolbox.win);
-      yield onNodeUnhighlight;
-
-      info("Switching back to the console");
-      yield toolbox.selectTool("webconsole");
-    }
+    yield checkDomElementHighlightingForInputs(hud, TEST_DATA);
   }).then(finishTest);
 }
-
-function jsEval(input, hud, message) {
-  info("Executing '" + input + "' in the web console");
-
-  hud.jsterm.clearOutput();
-  hud.jsterm.execute(input);
-
-  return waitForMessages({
-    webconsole: hud,
-    messages: [message]
-  });
-}
-
-function* getWidgetAndMessage(result) {
-  info("Getting the output ElementNode widget");
-
-  let msg = [...result.matched][0];
-  let widget = [...msg._messageObject.widgets][0];
-  ok(widget, "ElementNode widget found in the output");
-
-  info("Waiting for the ElementNode widget to be linked to the inspector");
-  yield widget.linkToInspector();
-
-  return {widget: widget, msg: msg};
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/browser_webconsole_output_dom_elements_05.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 the inspector links in the webconsole output for namespaced elements
+// actually open the inspector and select the right node.
+
+const XHTML = `
+  <!DOCTYPE html>
+  <html xmlns="http://www.w3.org/1999/xhtml"
+        xmlns:svg="http://www.w3.org/2000/svg">
+    <body>
+      <svg:svg width="100" height="100">
+        <svg:clipPath id="clip">
+          <svg:rect id="rectangle" x="0" y="0" width="10" height="5"></svg:rect>
+        </svg:clipPath>
+        <svg:circle cx="0" cy="0" r="5"></svg:circle>
+      </svg:svg>
+    </body>
+  </html>
+`;
+
+const TEST_URI = "data:application/xhtml+xml;charset=utf-8," + encodeURI(XHTML);
+
+const TEST_DATA = [
+  {
+    input: 'document.querySelector("clipPath")',
+    output: '<svg:clipPath id="clip">',
+    displayName: "svg:clipPath"
+  },
+  {
+    input: 'document.querySelector("circle")',
+    output: '<svg:circle cx="0" cy="0" r="5">',
+    displayName: "svg:circle"
+  },
+];
+
+function test() {
+  Task.spawn(function* () {
+    let {tab} = yield loadTab(TEST_URI);
+    let hud = yield openConsole(tab);
+    yield checkDomElementHighlightingForInputs(hud, TEST_DATA);
+  }).then(finishTest);
+}
--- a/devtools/client/webconsole/test/browser_webconsole_show_subresource_security_errors.js
+++ b/devtools/client/webconsole/test/browser_webconsole_show_subresource_security_errors.js
@@ -16,17 +16,17 @@ const SAMPLE_MSG = "specified a header t
 add_task(function* () {
   let { browser } = yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
 
   hud.jsterm.clearOutput();
 
   let loaded = loadBrowser(browser);
-  content.location = TEST_DOC;
+  BrowserTestUtils.loadURI(browser, TEST_DOC);
   yield loaded;
 
   yield waitForSuccess({
     name: "Subresource STS warning displayed successfully",
     validator: function () {
       return hud.outputNode.textContent.indexOf(SAMPLE_MSG) > -1;
     }
   });
--- a/devtools/client/webconsole/test/head.js
+++ b/devtools/client/webconsole/test/head.js
@@ -1579,16 +1579,124 @@ function checkOutputForInputs(hud, input
          "opened tab '" + uri + "', expected tab '" + entry.expectedTab + "'");
       yield closeTab(tab);
     }).then(resolve, reject);
   }
 
   return Task.spawn(runner);
 }
 
+/**
+ * Check the web console DOM element output for the given inputs.
+ * Each input is checked for the expected JS eval result. The JS eval result is
+ * also checked if it opens the inspector with the correct node selected on
+ * inspector icon click
+ *
+ * @param object hud
+ *        The web console instance to work with.
+ * @param array inputTests
+ *        An array of input tests. An input test element is an object. Each
+ *        object has the following properties:
+ *        - input: string, JS input value to execute.
+ *
+ *        - output: string, expected JS eval result.
+ *
+ *        - displayName: string, expected NodeFront's displayName.
+ *
+ *        - attr: Array, expected NodeFront's attributes
+ */
+function checkDomElementHighlightingForInputs(hud, inputs) {
+  function* runner() {
+    let toolbox = gDevTools.getToolbox(hud.target);
+
+    // Loading the inspector panel at first, to make it possible to listen for
+    // new node selections
+    yield toolbox.selectTool("inspector");
+    let inspector = toolbox.getCurrentPanel();
+    yield toolbox.selectTool("webconsole");
+
+    info("Iterating over the test data");
+    for (let data of inputs) {
+      let [result] = yield jsEval(data.input, {text: data.output});
+      let {msg} = yield checkWidgetAndMessage(result);
+      yield checkNodeHighlight(toolbox, inspector, msg, data);
+    }
+  }
+
+  function jsEval(input, message) {
+    info("Executing '" + input + "' in the web console");
+
+    hud.jsterm.clearOutput();
+    hud.jsterm.execute(input);
+
+    return waitForMessages({
+      webconsole: hud,
+      messages: [message]
+    });
+  }
+
+  function* checkWidgetAndMessage(result) {
+    info("Getting the output ElementNode widget");
+
+    let msg = [...result.matched][0];
+    let widget = [...msg._messageObject.widgets][0];
+    ok(widget, "ElementNode widget found in the output");
+
+    info("Waiting for the ElementNode widget to be linked to the inspector");
+    yield widget.linkToInspector();
+
+    return {widget, msg};
+  }
+
+  function* checkNodeHighlight(toolbox, inspector, msg, testData) {
+    let inspectorIcon = msg.querySelector(".open-inspector");
+    ok(inspectorIcon, "Inspector icon found in the ElementNode widget");
+
+    info("Clicking on the inspector icon and waiting for the " +
+         "inspector to be selected");
+    let onInspectorSelected = toolbox.once("inspector-selected");
+    let onInspectorUpdated = inspector.once("inspector-updated");
+    let onNewNode = toolbox.selection.once("new-node-front");
+    let onNodeHighlight = toolbox.once("node-highlight");
+
+    EventUtils.synthesizeMouseAtCenter(inspectorIcon, {},
+      inspectorIcon.ownerDocument.defaultView);
+    yield onInspectorSelected;
+    yield onInspectorUpdated;
+    yield onNodeHighlight;
+    let nodeFront = yield onNewNode;
+
+    ok(true, "Inspector selected and new node got selected");
+
+    is(nodeFront.displayName, testData.displayName,
+      "The correct node was highlighted");
+
+    if (testData.attrs) {
+      let attrs = nodeFront.attributes;
+      for (let i in testData.attrs) {
+        is(attrs[i].name, testData.attrs[i].name,
+           "Expected attribute's name is present");
+        is(attrs[i].value, testData.attrs[i].value,
+           "Expected attribute's value is present");
+      }
+    }
+
+    info("Unhighlight the node by moving away from the markup view");
+    let onNodeUnhighlight = toolbox.once("node-unhighlight");
+    let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button");
+    EventUtils.synthesizeMouseAtCenter(btn, {type: "mousemove"},
+      inspector.toolbox.win);
+    yield onNodeUnhighlight;
+
+    info("Switching back to the console");
+    yield toolbox.selectTool("webconsole");
+  }
+
+  return Task.spawn(runner);
+}
 
 /**
  * Finish the request and resolve with the request object.
  *
  * @param {Function} predicate A predicate function that takes the request
  * object as an argument and returns true if the request was the expected one,
  * false otherwise. The returned promise is resolved ONLY if the predicate
  * matches a request. Defaults to accepting any request.
--- a/devtools/server/actors/styleeditor.js
+++ b/devtools/server/actors/styleeditor.js
@@ -12,53 +12,35 @@ Cu.import("resource://gre/modules/NetUti
 Cu.import("resource://gre/modules/FileUtils.jsm");
 
 const promise = require("promise");
 const events = require("sdk/event/core");
 const protocol = require("devtools/shared/protocol");
 const {Arg, Option, method, RetVal, types} = protocol;
 const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
 const {fetch} = require("devtools/shared/DevToolsUtils");
+const {OldStyleSheetFront} = require("devtools/shared/fronts/styleeditor");
+const {oldStyleSheetSpec} = require("devtools/shared/specs/styleeditor");
 
 loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic);
 
 var TRANSITION_CLASS = "moz-styleeditor-transitioning";
 var TRANSITION_DURATION_MS = 500;
 var TRANSITION_RULE = "\
 :root.moz-styleeditor-transitioning, :root.moz-styleeditor-transitioning * {\
 transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
 transition-delay: 0ms !important;\
 transition-timing-function: ease-out !important;\
 transition-property: all !important;\
 }";
 
 var LOAD_ERROR = "error-load";
 
-/**
- * A StyleSheetActor represents a stylesheet on the server.
- */
-var OldStyleSheetActor = protocol.ActorClass({
-  typeName: "old-stylesheet",
-
-  events: {
-    "property-change" : {
-      type: "propertyChange",
-      property: Arg(0, "string"),
-      value: Arg(1, "json")
-    },
-    "source-load" : {
-      type: "sourceLoad",
-      source: Arg(0, "string")
-    },
-    "style-applied" : {
-      type: "styleApplied"
-    }
-  },
-
-  toString: function () {
+var OldStyleSheetActor = protocol.ActorClassWithSpec(oldStyleSheetSpec, {
+  toString: function() {
     return "[OldStyleSheetActor " + this.actorID + "]";
   },
 
   /**
    * Window of target
    */
   get window() {
     return this._window || this.parentActor.window;
@@ -165,45 +147,43 @@ var OldStyleSheetActor = protocol.ActorC
   },
 
   /**
    * Toggle the disabled property of the style sheet
    *
    * @return {object}
    *         'disabled' - the disabled state after toggling.
    */
-  toggleDisabled: method(function () {
+  toggleDisabled: function () {
     this.rawSheet.disabled = !this.rawSheet.disabled;
     this._notifyPropertyChanged("disabled");
 
     return this.rawSheet.disabled;
-  }, {
-    response: { disabled: RetVal("boolean")}
-  }),
+  },
 
   /**
    * Send an event notifying that a property of the stylesheet
    * has changed.
    *
    * @param  {string} property
    *         Name of the changed property
    */
   _notifyPropertyChanged: function (property) {
     events.emit(this, "property-change", property, this.form()[property]);
   },
 
    /**
     * Fetch the source of the style sheet from its URL. Send a "sourceLoad"
     * event when it's been fetched.
     */
-  fetchSource: method(function () {
+  fetchSource: function () {
     this._getText().then((content) => {
       events.emit(this, "source-load", this.text);
     });
-  }),
+  },
 
   /**
    * Fetch the text for this stylesheet from the cache or network. Return
    * cached text if it's already been fetched.
    *
    * @return {Promise}
    *         Promise that resolves with a string text of the stylesheet.
    */
@@ -285,35 +265,30 @@ var OldStyleSheetActor = protocol.ActorC
 
   /**
    * Update the style sheet in place with new text.
    *
    * @param  {object} request
    *         'text' - new text
    *         'transition' - whether to do CSS transition for change.
    */
-  update: method(function (text, transition) {
+  update: function (text, transition) {
     DOMUtils.parseStyleSheet(this.rawSheet, text);
 
     this.text = text;
 
     this._notifyPropertyChanged("ruleCount");
 
     if (transition) {
       this._insertTransistionRule();
     }
     else {
       this._notifyStyleApplied();
     }
-  }, {
-    request: {
-      text: Arg(0, "string"),
-      transition: Arg(1, "boolean")
-    }
-  }),
+  },
 
   /**
    * Insert a catch-all transition rule into the document. Set a timeout
    * to remove the rule after a certain time.
    */
   _insertTransistionRule: function () {
     // Insert the global transition rule
     // Use a ref count to make sure we do not add it multiple times.. and remove
@@ -342,85 +317,16 @@ var OldStyleSheetActor = protocol.ActorC
       this.rawSheet.deleteRule(this.rawSheet.cssRules.length - 1);
     }
 
     events.emit(this, "style-applied");
   }
 });
 
 /**
- * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
- */
-var OldStyleSheetFront = protocol.FrontClass(OldStyleSheetActor, {
-  initialize: function (conn, form, ctx, detail) {
-    protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
-
-    this._onPropertyChange = this._onPropertyChange.bind(this);
-    events.on(this, "property-change", this._onPropertyChange);
-  },
-
-  destroy: function () {
-    events.off(this, "property-change", this._onPropertyChange);
-
-    protocol.Front.prototype.destroy.call(this);
-  },
-
-  _onPropertyChange: function (property, value) {
-    this._form[property] = value;
-  },
-
-  form: function (form, detail) {
-    if (detail === "actorid") {
-      this.actorID = form;
-      return;
-    }
-    this.actorID = form.actor;
-    this._form = form;
-  },
-
-  getText: function () {
-    let deferred = promise.defer();
-
-    events.once(this, "source-load", (source) => {
-      let longStr = new ShortLongString(source);
-      deferred.resolve(longStr);
-    });
-    this.fetchSource();
-
-    return deferred.promise;
-  },
-
-  getOriginalSources: function () {
-    return promise.resolve([]);
-  },
-
-  get href() {
-    return this._form.href;
-  },
-  get nodeHref() {
-    return this._form.nodeHref;
-  },
-  get disabled() {
-    return !!this._form.disabled;
-  },
-  get title() {
-    return this._form.title;
-  },
-  get isSystem() {
-    return this._form.system;
-  },
-  get styleSheetIndex() {
-    return this._form.styleSheetIndex;
-  },
-  get ruleCount() {
-    return this._form.ruleCount;
-  }
-});
-
-/**
  * Creates a StyleEditorActor. StyleEditorActor provides remote access to the
  * stylesheets of a document.
  */
 var StyleEditorActor = exports.StyleEditorActor = protocol.ActorClass({
   typeName: "styleeditor",
 
   /**
    * The window we work with, taken from the parent actor.
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const promise = require("promise");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {getDefinedGeometryProperties} = require("devtools/server/actors/highlighters/geometry-editor");
-const {parseDeclarations} = require("devtools/client/shared/css-parsing-utils");
+const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
 const {Task} = require("devtools/shared/task");
 const events = require("sdk/event/core");
 
 // This will also add the "stylesheet" actor type for protocol.js to recognize
 const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = require("devtools/server/actors/stylesheets");
 const {pageStyleSpec, styleRuleSpec} = require("devtools/shared/specs/styles");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
--- a/devtools/server/actors/utils/TabSources.js
+++ b/devtools/server/actors/utils/TabSources.js
@@ -6,17 +6,16 @@
 
 const { Ci, Cu } = require("chrome");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, fetch } = DevToolsUtils;
 const EventEmitter = require("devtools/shared/event-emitter");
 const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { resolve } = require("promise");
 const { joinURI } = require("devtools/shared/path");
-const URL = require("URL");
 
 loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/source", true);
 loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/source", true);
 loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
 loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
 
 /**
  * Manages the sources for a thread. Handles source maps, locations in the
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -217,23 +217,16 @@ defineLazyGetter(exports.modules, "index
 
 defineLazyGetter(exports.modules, "CSS", () => {
   let sandbox
     = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
                  {wantGlobalProperties: ["CSS"]});
   return sandbox.CSS;
 });
 
-defineLazyGetter(exports.modules, "URL", () => {
-  let sandbox
-    = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
-                 {wantGlobalProperties: ["URL"]});
-  return sandbox.URL;
-});
-
 // List of all custom globals exposed to devtools modules.
 // Changes here should be mirrored to devtools/.eslintrc.
 const globals = exports.globals = {
   isWorker: false,
   reportError: Cu.reportError,
   atob: atob,
   btoa: btoa,
   _Iterator: Iterator,
@@ -282,8 +275,14 @@ defineLazyGetter(globals, "setTimeout", 
   return Cu.import("resource://gre/modules/Timer.jsm", {}).setTimeout;
 });
 defineLazyGetter(globals, "clearInterval", () => {
   return Cu.import("resource://gre/modules/Timer.jsm", {}).clearInterval;
 });
 defineLazyGetter(globals, "setInterval", () => {
   return Cu.import("resource://gre/modules/Timer.jsm", {}).setInterval;
 });
+defineLazyGetter(globals, "URL", () => {
+  let sandbox
+    = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
+                 {wantGlobalProperties: ["URL"]});
+  return sandbox.URL;
+});
rename from devtools/client/shared/css-parsing-utils.js
rename to devtools/shared/css-parsing-utils.js
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/styleeditor.js
@@ -0,0 +1,81 @@
+/* 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 { ShortLongString } = require("devtools/server/actors/string");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const { oldStyleSheetSpec } = require("devtools/shared/specs/styleeditor");
+const promise = require("promise");
+const events = require("sdk/event/core");
+
+/**
+ * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
+ */
+const OldStyleSheetFront = FrontClassWithSpec(oldStyleSheetSpec, {
+  initialize: function (conn, form, ctx, detail) {
+    Front.prototype.initialize.call(this, conn, form, ctx, detail);
+
+    this._onPropertyChange = this._onPropertyChange.bind(this);
+    events.on(this, "property-change", this._onPropertyChange);
+  },
+
+  destroy: function () {
+    events.off(this, "property-change", this._onPropertyChange);
+
+    Front.prototype.destroy.call(this);
+  },
+
+  _onPropertyChange: function (property, value) {
+    this._form[property] = value;
+  },
+
+  form: function (form, detail) {
+    if (detail === "actorid") {
+      this.actorID = form;
+      return;
+    }
+    this.actorID = form.actor;
+    this._form = form;
+  },
+
+  getText: function () {
+    let deferred = promise.defer();
+
+    events.once(this, "source-load", (source) => {
+      let longStr = new ShortLongString(source);
+      deferred.resolve(longStr);
+    });
+    this.fetchSource();
+
+    return deferred.promise;
+  },
+
+  getOriginalSources: function () {
+    return promise.resolve([]);
+  },
+
+  get href() {
+    return this._form.href;
+  },
+  get nodeHref() {
+    return this._form.nodeHref;
+  },
+  get disabled() {
+    return !!this._form.disabled;
+  },
+  get title() {
+    return this._form.title;
+  },
+  get isSystem() {
+    return this._form.system;
+  },
+  get styleSheetIndex() {
+    return this._form.styleSheetIndex;
+  },
+  get ruleCount() {
+    return this._form.ruleCount;
+  }
+});
+
+exports.OldStyleSheetFront = OldStyleSheetFront;
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -14,17 +14,17 @@ const {
   pageStyleSpec,
   styleRuleSpec
 } = require("devtools/shared/specs/styles");
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 const { Class } = require("sdk/core/heritage");
 
 loader.lazyGetter(this, "RuleRewriter", () => {
-  return require("devtools/client/shared/css-parsing-utils").RuleRewriter;
+  return require("devtools/shared/css-parsing-utils").RuleRewriter;
 });
 
 /**
  * PageStyleFront, the front object for the PageStyleActor
  */
 const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
   initialize: function (conn, form, ctx, detail) {
     Front.prototype.initialize.call(this, conn, form, ctx, detail);
--- a/devtools/shared/gcli/commands/cookie.js
+++ b/devtools/shared/gcli/commands/cookie.js
@@ -17,17 +17,16 @@
  * toolbar (the gcli command bar), and because this toolbar is only available on
  * a local Firefox desktop tab (not in webide or the browser toolbox), we can
  * make the commands run on the client.
  * This way, they'll always run in the parent process.
  */
 
 const { Ci, Cc } = require("chrome");
 const l10n = require("gcli/l10n");
-const URL = require("sdk/url").URL;
 
 XPCOMUtils.defineLazyGetter(this, "cookieMgr", function() {
   return Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
 });
 
 /**
  * Check host value and remove port part as it is not used
  * for storing cookies.
--- a/devtools/shared/gcli/source/lib/gcli/util/host.js
+++ b/devtools/shared/gcli/source/lib/gcli/util/host.js
@@ -13,17 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 'use strict';
 
 var Cc = require('chrome').Cc;
 var Ci = require('chrome').Ci;
-var URL = require('sdk/url').URL;
 
 var { Task } = require("devtools/shared/task");
 
 var util = require('./util');
 
 function Highlighter(document) {
   this._document = document;
   this._nodes = util.createEmptyNodeList(this._document);
@@ -63,17 +62,17 @@ exports.Highlighter = Highlighter;
 exports.exec = function(task) {
   return Task.spawn(task);
 };
 
 /**
  * The URL API is new enough that we need specific platform help
  */
 exports.createUrl = function(uristr, base) {
-  return URL(uristr, base);
+  return new URL(uristr, base);
 };
 
 /**
  * Load some HTML into the given document and return a DOM element.
  * This utility assumes that the html has a single root (other than whitespace)
  */
 exports.toDom = function(document, html) {
   var div = util.createElement(document, 'div');
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -38,21 +38,23 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit
 JAR_MANIFESTS += ['jar.mn']
 
 DevToolsModules(
     'async-storage.js',
     'async-utils.js',
     'builtin-modules.js',
     'content-observer.js',
     'css-lexer.js',
+    'css-parsing-utils.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'event-emitter.js',
     'event-parsers.js',
     'indentation.js',
     'Loader.jsm',
     'Parser.jsm',
     'path.js',
+    'promise_defer.js',
     'protocol.js',
     'system.js',
     'task.js',
     'ThreadSafeDevToolsUtils.js',
 )
--- a/devtools/shared/path.js
+++ b/devtools/shared/path.js
@@ -1,16 +1,14 @@
 /* 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 URL = require("URL");
-
 /*
  * Join all the arguments together and normalize the resulting URI.
  * The initial path must be an full URI with a protocol (i.e. http://).
  */
 exports.joinURI = (initialPath, ...paths) => {
   let url;
 
   try {
new file mode 100644
--- /dev/null
+++ b/devtools/shared/promise_defer.js
@@ -0,0 +1,22 @@
+/* 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";
+
+/**
+ * Returns a deferred object, with a resolve and reject property.
+ * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred
+ */
+module.exports = function defer() {
+  let resolve, reject;
+  let promise = new Promise(function () {
+    resolve = arguments[0];
+    reject = arguments[1];
+  });
+  return {
+    resolve: resolve,
+    reject: reject,
+    promise: promise
+  };
+};
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -5,11 +5,12 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'addons.js',
     'animation.js',
     'highlighters.js',
     'inspector.js',
     'storage.js',
+    'styleeditor.js',
     'styles.js',
     'stylesheets.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/styleeditor.js
@@ -0,0 +1,40 @@
+/* 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 { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const oldStyleSheetSpec = generateActorSpec({
+  typeName: "old-stylesheet",
+
+  events: {
+    "property-change": {
+      type: "propertyChange",
+      property: Arg(0, "string"),
+      value: Arg(1, "json")
+    },
+    "source-load": {
+      type: "sourceLoad",
+      source: Arg(0, "string")
+    },
+    "style-applied": {
+      type: "styleApplied"
+    }
+  },
+
+  methods: {
+    toggleDisabled: {
+      response: { disabled: RetVal("boolean")}
+    },
+    fetchSource: {},
+    update: {
+      request: {
+        text: Arg(0, "string"),
+        transition: Arg(1, "boolean")
+      }
+    }
+  }
+});
+
+exports.oldStyleSheetSpec = oldStyleSheetSpec;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/tests/unit/test_promise_defer.js
@@ -0,0 +1,35 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const defer = require("devtools/shared/promise_defer");
+
+function testResolve() {
+  const deferred = defer();
+  deferred.resolve("success");
+  return deferred.promise;
+}
+
+function testReject() {
+  const deferred = defer();
+  deferred.reject("error");
+  return deferred.promise;
+}
+
+add_task(function* () {
+  const success = yield testResolve();
+  equal(success, "success");
+
+  let error;
+  try {
+    yield testReject();
+  } catch (e) {
+    error = e;
+  }
+
+  equal(error, "error");
+});
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -21,9 +21,10 @@ support-files =
 [test_safeErrorString.js]
 [test_defineLazyPrototypeGetter.js]
 [test_async-utils.js]
 [test_console_filtering.js]
 [test_prettifyCSS.js]
 [test_require_lazy.js]
 [test_require.js]
 [test_stack.js]
+[test_promise_defer.js]
 [test_executeSoon.js]
--- a/devtools/shared/worker/loader.js
+++ b/devtools/shared/worker/loader.js
@@ -485,24 +485,24 @@ var {
 this.worker = new WorkerDebuggerLoader({
   createSandbox: createSandbox,
   globals: {
     "isWorker": true,
     "dump": dump,
     "loader": loader,
     "reportError": reportError,
     "rpc": rpc,
-    "setImmediate": setImmediate
+    "setImmediate": setImmediate,
+    "URL": URL,
   },
   loadSubScript: loadSubScript,
   modules: {
     "Debugger": Debugger,
     "PromiseDebugging": PromiseDebugging,
     "Services": Object.create(null),
-    "URL": URL,
     "chrome": chrome,
     "xpcInspector": xpcInspector
   },
   paths: {
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "": "resource://gre/modules/commonjs/",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "devtools": "resource://devtools",
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
@@ -51,16 +51,25 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+/**
+ * Some tests in this class run client/server multi-threaded code but JUnit assertions triggered
+ * from background threads do not fail the test. If you see unexplained connection-related test failures,
+ * an assertion on the server may have been thrown. Unfortunately, it is non-trivial to get the background
+ * threads to transfer failures back to the test thread so we leave the tests in this state for now.
+ *
+ * One reason the server might throw an assertion is if you have not installed the crypto policies. See
+ * https://wiki.mozilla.org/Mobile/Fennec/Android/Testing#JUnit4_tests for more information.
+ */
 @RunWith(TestRunner.class)
 public class TestClientsEngineStage extends MockSyncClientsEngineStage {
   public final static String LOG_TAG = "TestClientsEngSta";
 
   public TestClientsEngineStage() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException, URISyntaxException {
     super();
     session = initializeSession();
   }
@@ -596,17 +605,17 @@ public class TestClientsEngineStage exte
     // Body and header are the same.
     assertEquals(Utils.decimalSecondsToMilliseconds(uploadBodyTimestamp),
                  session.config.getPersistedServerClientsTimestamp());
     assertEquals(uploadedRecord.lastModified,
                  session.config.getPersistedServerClientRecordTimestamp());
     assertEquals(uploadHeaderTimestamp, session.config.getPersistedServerClientsTimestamp());
   }
 
-  @Test
+  @Test // client/server multi-threaded
   public void testDownloadHasOurRecord() {
     // Make sure no upload occurs after a download so we can
     // test download in isolation.
     stubUpload = true;
 
     // We've uploaded our local record recently.
     long initialTimestamp = setRecentClientRecordTimestamp();
 
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -44,33 +44,36 @@ var snapshotFormatters = {
     let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
     if (data.vendor)
       version += " (" + data.vendor + ")";
     $("version-box").textContent = version;
     $("buildid-box").textContent = data.buildID;
     if (data.updateChannel)
       $("updatechannel-box").textContent = data.updateChannel;
 
-    let statusStrName = ".unknown";
+    let statusText = stringBundle().GetStringFromName("multiProcessStatus.unknown");
 
     // Whitelist of known values with string descriptions:
     switch (data.autoStartStatus) {
       case 0:
       case 1:
       case 2:
       case 4:
-      case 5:
       case 6:
       case 7:
       case 8:
       case 9:
-        statusStrName = "." + data.autoStartStatus;
+        statusText = stringBundle().GetStringFromName("multiProcessStatus." + data.autoStartStatus);
+        break;
+
+      case 10:
+        statusText = (Services.appinfo.OS == "Darwin" ? "OS X 10.6 - 10.8" : "Windows XP");
+        break;
     }
 
-    let statusText = stringBundle().GetStringFromName("multiProcessStatus" + statusStrName);
     $("multiprocess-box").textContent = stringBundle().formatStringFromName("multiProcessWindows",
       [data.numRemoteWindows, data.numTotalWindows, statusText], 3);
 
     $("safemode-box").textContent = data.safeMode;
   },
 
   crashes: function crashes(data) {
     if (!AppConstants.MOZ_CRASHREPORTER)
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -4698,16 +4698,17 @@ enum {
   kE10sDisabledByUser = 2,
   // kE10sDisabledInSafeMode = 3, was removed in bug 1172491.
   kE10sDisabledForAccessibility = 4,
   // kE10sDisabledForMacGfx = 5, was removed in bug 1068674.
   kE10sDisabledForBidi = 6,
   kE10sDisabledForAddons = 7,
   kE10sForceDisabled = 8,
   kE10sDisabledForXPAcceleration = 9,
+  kE10sDisabledForOperatingSystem = 10,
 };
 
 #if defined(XP_WIN) || defined(XP_MACOSX)
 const char* kAccessibilityLastRunDatePref = "accessibility.lastLoadDate";
 const char* kAccessibilityLoadedLastSessionPref = "accessibility.loadedInLastSession";
 
 static inline uint32_t
 PRTimeToSeconds(PRTime t_usec)
@@ -4770,35 +4771,57 @@ MultiprocessBlockPolicy() {
     if (difference > ONE_WEEK_IN_SECONDS || !a11yRunDate) {
       Preferences::ClearUser(kAccessibilityLastRunDatePref);
     } else {
       disabledForA11y = true;
     }
   }
 #endif // XP_WIN || XP_MACOSX
 
+  if (disabledForA11y) {
+    gMultiprocessBlockPolicy = kE10sDisabledForAccessibility;
+    return gMultiprocessBlockPolicy;
+  }
+
+  /**
+   * Avoids enabling e10s for Windows XP users on the release channel.
+   */
+#if defined(XP_WIN)
+  if (Preferences::GetDefaultCString("app.update.channel").EqualsLiteral("release") &&
+      !IsVistaOrLater()) {
+    gMultiprocessBlockPolicy = kE10sDisabledForOperatingSystem;
+    return gMultiprocessBlockPolicy;
+  }
+#endif
+
+  /**
+   * Avoids enabling e10s for OS X 10.6 - 10.8 users (<= Lion) as these
+   * versions will be unsupported soon.
+   */
+#if defined(XP_MACOSX)
+  if (!nsCocoaFeatures::OnMountainLionOrLater()) {
+    gMultiprocessBlockPolicy = kE10sDisabledForOperatingSystem;
+    return gMultiprocessBlockPolicy;
+  }
+#endif
+
 #if defined(XP_WIN)
   /**
    * We block on Windows XP if layers acceleration is requested. This is due to
    * bug 1237769 where D3D9 and e10s behave badly together on XP.
    */
   bool layersAccelerationRequested = !Preferences::GetBool("layers.acceleration.disabled") ||
                                       Preferences::GetBool("layers.acceleration.force-enabled");
 
   if (layersAccelerationRequested && !IsVistaOrLater()) {
     gMultiprocessBlockPolicy = kE10sDisabledForXPAcceleration;
     return gMultiprocessBlockPolicy;
   }
 #endif // XP_WIN
 
-  if (disabledForA11y) {
-    gMultiprocessBlockPolicy = kE10sDisabledForAccessibility;
-    return gMultiprocessBlockPolicy;
-  }
-
   /**
    * Avoids enabling e10s for certain locales that require bidi selection,
    * which currently doesn't work well with e10s.
    */
   bool disabledForBidi = false;
 
   nsCOMPtr<nsIXULChromeRegistry> registry =
    mozilla::services::GetXULChromeRegistryService();
@@ -4806,18 +4829,16 @@ MultiprocessBlockPolicy() {
      registry->IsLocaleRTL(NS_LITERAL_CSTRING("global"), &disabledForBidi);
   }
 
   if (disabledForBidi) {
     gMultiprocessBlockPolicy = kE10sDisabledForBidi;
     return gMultiprocessBlockPolicy;
   }
 
-
-
   /*
    * None of the blocking policies matched, so e10s is allowed to run.
    * Cache the information and return 0, indicating success.
    */
   gMultiprocessBlockPolicy = 0;
   return 0;
 }