Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 26 May 2016 11:21:20 -0400
changeset 338093 b0096c5c727749ad3e79cbdf20d2e96bd179c213
parent 338061 56e5195bef5bf352d37181195dc6043ac3d198ad (current diff)
parent 338092 02517c2c78bd6da0d0433b6b549c09e9a43a9895 (diff)
child 338098 5fdfce654062b471415a82f1a2c07200fc463dcf
child 338200 49beae1792077eba5b790baa7d3c1a37ca3eb609
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
b0096c5c7277 / 49.0a1 / 20160526083305 / files
nightly linux64
b0096c5c7277 / 49.0a1 / 20160526083057 / files
nightly mac
b0096c5c7277 / 49.0a1 / 20160526082506 / files
nightly win32
b0096c5c7277 / 49.0a1 / 20160526082509 / files
nightly win64
b0096c5c7277 / 49.0a1 / 20160526082533 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c. a=merge
devtools/client/shared/css-parsing-utils.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;
 }