Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 09 Jun 2017 13:03:16 +0200
changeset 411321 7457b240847db66b78493ed3a6d03663173bd745
parent 411320 825e71dae9bf360ff0ae51bf0d4bb663189df72c (current diff)
parent 411287 1742b1bdadd13a02df95ca690bea9cc42ff40c91 (diff)
child 411322 eca8d0ea03af1d2424550a037f714f14c0f7b1be
child 411362 24ef2aa69cf3366dc9efec033cba4bf61b6322f6
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
devtools/client/locales/en-US/projecteditor.properties
devtools/client/projecteditor/chrome/content/projecteditor.xul
devtools/client/projecteditor/lib/editors.js
devtools/client/projecteditor/lib/helpers/event.js
devtools/client/projecteditor/lib/helpers/file-picker.js
devtools/client/projecteditor/lib/helpers/l10n.js
devtools/client/projecteditor/lib/helpers/moz.build
devtools/client/projecteditor/lib/helpers/prompts.js
devtools/client/projecteditor/lib/helpers/readdir.js
devtools/client/projecteditor/lib/moz.build
devtools/client/projecteditor/lib/plugins/app-manager/app-project-editor.js
devtools/client/projecteditor/lib/plugins/app-manager/moz.build
devtools/client/projecteditor/lib/plugins/app-manager/plugin.js
devtools/client/projecteditor/lib/plugins/core.js
devtools/client/projecteditor/lib/plugins/delete/delete.js
devtools/client/projecteditor/lib/plugins/delete/moz.build
devtools/client/projecteditor/lib/plugins/dirty/dirty.js
devtools/client/projecteditor/lib/plugins/dirty/moz.build
devtools/client/projecteditor/lib/plugins/image-view/image-editor.js
devtools/client/projecteditor/lib/plugins/image-view/moz.build
devtools/client/projecteditor/lib/plugins/image-view/plugin.js
devtools/client/projecteditor/lib/plugins/logging/logging.js
devtools/client/projecteditor/lib/plugins/logging/moz.build
devtools/client/projecteditor/lib/plugins/moz.build
devtools/client/projecteditor/lib/plugins/new/moz.build
devtools/client/projecteditor/lib/plugins/new/new.js
devtools/client/projecteditor/lib/plugins/rename/moz.build
devtools/client/projecteditor/lib/plugins/rename/rename.js
devtools/client/projecteditor/lib/plugins/save/moz.build
devtools/client/projecteditor/lib/plugins/save/save.js
devtools/client/projecteditor/lib/plugins/status-bar/moz.build
devtools/client/projecteditor/lib/plugins/status-bar/plugin.js
devtools/client/projecteditor/lib/project.js
devtools/client/projecteditor/lib/projecteditor.js
devtools/client/projecteditor/lib/shells.js
devtools/client/projecteditor/lib/stores/base.js
devtools/client/projecteditor/lib/stores/local.js
devtools/client/projecteditor/lib/stores/moz.build
devtools/client/projecteditor/lib/stores/resource.js
devtools/client/projecteditor/lib/tree.js
devtools/client/projecteditor/moz.build
devtools/client/projecteditor/test/.eslintrc.js
devtools/client/projecteditor/test/browser.ini
devtools/client/projecteditor/test/browser_projecteditor_app_options.js
devtools/client/projecteditor/test/browser_projecteditor_confirm_unsaved.js
devtools/client/projecteditor/test/browser_projecteditor_contextmenu_01.js
devtools/client/projecteditor/test/browser_projecteditor_contextmenu_02.js
devtools/client/projecteditor/test/browser_projecteditor_delete_file.js
devtools/client/projecteditor/test/browser_projecteditor_editing_01.js
devtools/client/projecteditor/test/browser_projecteditor_editors_image.js
devtools/client/projecteditor/test/browser_projecteditor_external_change.js
devtools/client/projecteditor/test/browser_projecteditor_immediate_destroy.js
devtools/client/projecteditor/test/browser_projecteditor_init.js
devtools/client/projecteditor/test/browser_projecteditor_menubar_01.js
devtools/client/projecteditor/test/browser_projecteditor_menubar_02.js
devtools/client/projecteditor/test/browser_projecteditor_new_file.js
devtools/client/projecteditor/test/browser_projecteditor_rename_file_01.js
devtools/client/projecteditor/test/browser_projecteditor_rename_file_02.js
devtools/client/projecteditor/test/browser_projecteditor_saveall.js
devtools/client/projecteditor/test/browser_projecteditor_stores.js
devtools/client/projecteditor/test/browser_projecteditor_tree_selection_01.js
devtools/client/projecteditor/test/browser_projecteditor_tree_selection_02.js
devtools/client/projecteditor/test/head.js
devtools/client/projecteditor/test/helper_edits.js
devtools/client/projecteditor/test/helper_homepage.html
devtools/client/projecteditor/test/projecteditor-test.xul
devtools/client/themes/projecteditor/projecteditor.css
layout/reftests/w3c-css/received/css21/reference/ref-filled-green-100px-square.xht
layout/reftests/w3c-css/received/selectors-4/focus-within-001-ref.html
layout/reftests/w3c-css/received/selectors-4/focus-within-001.html
layout/reftests/w3c-css/received/selectors-4/focus-within-002.html
layout/reftests/w3c-css/received/selectors-4/focus-within-003.html
layout/reftests/w3c-css/received/selectors-4/focus-within-004.html
layout/reftests/w3c-css/received/selectors-4/focus-within-005.html
layout/reftests/w3c-css/received/selectors-4/focus-within-006.html
layout/reftests/w3c-css/received/selectors-4/focus-within-shadow-001-ref.html
layout/reftests/w3c-css/received/selectors-4/focus-within-shadow-001.html
layout/reftests/w3c-css/received/selectors-4/focus-within-shadow-002.html
layout/reftests/w3c-css/received/selectors-4/focus-within-shadow-003.html
layout/reftests/w3c-css/received/selectors-4/focus-within-shadow-004.html
layout/reftests/w3c-css/received/selectors-4/focus-within-shadow-005.html
layout/reftests/w3c-css/received/selectors-4/of-type-selectors-ref.xhtml
layout/reftests/w3c-css/received/selectors-4/of-type-selectors.xhtml
layout/reftests/w3c-css/received/selectors-4/selector-required-ref.html
layout/reftests/w3c-css/received/selectors-4/selector-required.html
layout/reftests/w3c-css/received/selectors-4/selectors-dir-selector-ltr-001.html
layout/reftests/w3c-css/received/selectors-4/selectors-dir-selector-ref.html
layout/reftests/w3c-css/received/selectors-4/selectors-dir-selector-rtl-001.html
moz.configure
servo/components/style/values/specified/mod.rs.rej
testing/talos/talos/tests/quantum_pageload/quantum_1.manifest
toolkit/mozapps/installer/windows/nsis/makensis.mk
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1674,8 +1674,19 @@ pref("browser.sessionstore.restore_tabs_
 pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,goog-malware-proto,goog-unwanted-proto,test-malware-simple,test-unwanted-simple");
 pref("urlclassifier.phishTable", "goog-phish-shavar,goog-phish-proto,test-phish-simple");
 #endif
 
 pref("browser.suppress_first_window_animation", true);
 
 // Preferences for Photon onboarding system extension
 pref("browser.onboarding.enabled", true);
+
+// Preferences for the Screenshots feature:
+// Temporarily disable Screenshots in Beta & Release, so that we can gradually
+// roll out the feature using SHIELD pref flipping.
+#ifdef NIGHTLY_BUILD
+pref("extensions.screenshots.system-disabled", false);
+#else
+pref("extensions.screenshots.system-disabled", true);
+#endif
+// Permanent pref that allows individual users to disable Screenshots.
+pref("extensions.screenshots.disabled", false);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4221,29 +4221,40 @@ function updateEditUIVisibility() {
   // is open, or if the toolbar has been customized to include the Cut, Copy,
   // or Paste toolbar buttons.
   gEditUIVisible = editMenuPopupState == "showing" ||
                    editMenuPopupState == "open" ||
                    contextMenuPopupState == "showing" ||
                    contextMenuPopupState == "open" ||
                    placesContextMenuPopupState == "showing" ||
                    placesContextMenuPopupState == "open";
+  const kOpenPopupStates = ["showing", "open"];
   if (!gEditUIVisible) {
     // Now check the edit-controls toolbar buttons.
     let placement = CustomizableUI.getPlacementOfWidget("edit-controls");
     let areaType = placement ? CustomizableUI.getAreaType(placement.area) : "";
     if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
-      let panelUIMenuPopupState = document.getElementById("PanelUI-popup").state;
-      if (panelUIMenuPopupState == "showing" || panelUIMenuPopupState == "open") {
+      let customizablePanel = gPhotonStructure ? PanelUI.overflowPanel : PanelUI.panel;
+      gEditUIVisible = kOpenPopupStates.includes(customizablePanel.state);
+    } else if (areaType == CustomizableUI.TYPE_TOOLBAR && window.toolbar.visible) {
+      // The edit controls are on a toolbar, so they are visible,
+      // unless they're in a panel that isn't visible...
+      if (placement.area == "nav-bar") {
+        let editControls = document.getElementById("edit-controls");
+        gEditUIVisible = !editControls.hasAttribute("overflowedItem") ||
+                          kOpenPopupStates.includes(document.getElementById("widget-overflow").state);
+      } else {
         gEditUIVisible = true;
       }
-    } else if (areaType == CustomizableUI.TYPE_TOOLBAR) {
-      // The edit controls are on a toolbar, so they are visible.
-      gEditUIVisible = true;
-    }
+    }
+  }
+
+  // Now check the main menu panel if we're using photon
+  if (!gEditUIVisible && gPhotonStructure) {
+    gEditUIVisible = kOpenPopupStates.includes(PanelUI.panel.state);
   }
 
   // No need to update commands if the edit UI visibility has not changed.
   if (gEditUIVisible == oldVisible) {
     return;
   }
 
   // If UI is visible, update the edit commands' enabled state to reflect
@@ -7848,16 +7859,17 @@ var gPageActionButton = {
       if (!name) {
         return document.createElement("toolbarseparator");
       }
       let item = document.createElement("toolbarbutton");
       item.classList.add("page-action-sendToDevice-device", "subviewbutton");
       if (clientId) {
         item.classList.add("subviewbutton-iconic");
       }
+      item.setAttribute("tooltiptext", name);
       return item;
     });
 
     if (gSync.remoteClients.length) {
       body.setAttribute("hasdevices", "true");
     } else {
       body.removeAttribute("hasdevices");
     }
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1206,21 +1206,25 @@
 
   <deck id="content-deck" flex="1">
     <hbox flex="1" id="browser">
       <vbox id="browser-border-start" hidden="true" layer="true"/>
       <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
         <sidebarheader id="sidebar-header" align="center">
           <toolbarbutton id="sidebar-switcher-target" flex="1" class="tabbable">
             <image id="sidebar-icon" consumeanchor="sidebar-switcher-target"/>
-            <label id="sidebar-title" persist="value" crop="end" control="sidebar"/>
+            <label id="sidebar-title" persist="value" crop="end" flex="1" control="sidebar"/>
             <image id="sidebar-switcher-arrow"/>
-            <spacer flex="1"/>
           </toolbarbutton>
           <image id="sidebar-throbber"/>
+# To ensure the button label's intrinsic width doesn't expand the sidebar
+# if the label is long, the button needs flex=1.
+# To ensure the button doesn't expand unnecessarily for short labels, the
+# spacer should significantly out-flex the button.
+          <spacer flex="1000"/>
           <toolbarbutton id="sidebar-close" class="tabbable" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="SidebarUI.hide();"/>
         </sidebarheader>
         <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" disablefullscreen="true"
                   style="min-width: 14em; width: 18em; max-width: 36em;" tooltip="aHTMLTooltip"/>
       </vbox>
 
       <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
       <vbox id="appcontent" flex="1">
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -4156,16 +4156,19 @@ OverflowableToolbar.prototype = {
     return new Promise(resolve => {
       let doc = this._panel.ownerDocument;
       this._panel.hidden = false;
       let mainViewId = this._panel.querySelector("panelmultiview").getAttribute("mainViewId");
       let mainView = doc.getElementById(mainViewId);
       let contextMenu = doc.getElementById(mainView.getAttribute("context"));
       gELS.addSystemEventListener(contextMenu, "command", this, true);
       let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon");
+      // Ensure we update the gEditUIVisible flag when opening the popup, in
+      // case the edit controls are in it.
+      this._panel.addEventListener("popupshowing", () => doc.defaultView.updateEditUIVisibility(), {once: true});
       this._panel.openPopup(anchor || this._chevron);
       this._chevron.open = true;
 
       let overflowableToolbarInstance = this;
       this._panel.addEventListener("popupshown", function(aEvent) {
         this.addEventListener("dragover", overflowableToolbarInstance);
         this.addEventListener("dragend", overflowableToolbarInstance);
         resolve();
@@ -4182,16 +4185,17 @@ OverflowableToolbar.prototype = {
     }
   },
 
   _onPanelHiding(aEvent) {
     this._chevron.open = false;
     this._panel.removeEventListener("dragover", this);
     this._panel.removeEventListener("dragend", this);
     let doc = aEvent.target.ownerDocument;
+    doc.defaultView.updateEditUIVisibility();
     let contextMenuId = this._panel.getAttribute("context");
     if (contextMenuId) {
       let contextMenu = doc.getElementById(contextMenuId);
       gELS.removeSystemEventListener(contextMenu, "command", this, true);
     }
   },
 
   onOverflow(aEvent) {
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -870,16 +870,27 @@ const CustomizableWidgets = [
         // put the widgets in there, so they get the right style in the panel.
         onAreaNodeRegistered: (aArea, aContainer) => {
           if (aContainer.ownerDocument == node.ownerDocument &&
               aArea == this.currentArea &&
               aArea == CustomizableUI.AREA_PANEL) {
             updateCombinedWidgetStyle(node, aArea);
           }
         },
+
+        onWidgetOverflow(aWidgetNode) {
+          if (aWidgetNode == node) {
+            node.ownerGlobal.updateEditUIVisibility();
+          }
+        },
+        onWidgetUnderflow(aWidgetNode) {
+          if (aWidgetNode == node) {
+            node.ownerGlobal.updateEditUIVisibility();
+          }
+        },
       };
       CustomizableUI.addListener(listener);
 
       return node;
     }
   },
   {
     id: "feed-button",
--- a/browser/components/customizableui/test/browser_editcontrols_update.js
+++ b/browser/components/customizableui/test/browser_editcontrols_update.js
@@ -89,18 +89,18 @@ add_task(async function test_panelui_ope
   gURLBar.select();
   await overridePromise;
   checkState(true, "Update when edit-controls is on panel, hidden and selection changed");
 });
 
 // Test updating when the edit-controls are moved to the toolbar.
 add_task(async function test_panelui_customize_to_toolbar() {
   await startCustomizing();
-  let navbar = document.getElementById("nav-bar").customizationTarget;
-  simulateItemDrag(document.getElementById("edit-controls"), navbar);
+  let navbar = document.getElementById("nav-bar");
+  simulateItemDrag(document.getElementById("edit-controls"), navbar.customizationTarget);
   await endCustomizing();
 
   // updateEditUIVisibility should be called when customization ends but isn't. See bug 1359790.
   updateEditUIVisibility();
 
   // The URL bar may have been focused to begin with, which means
   // that subsequent calls to focus it won't result in command
   // updates, so we'll make sure to blur it.
@@ -112,16 +112,73 @@ add_task(async function test_panelui_cus
   gURLBar.value = "other";
   await overridePromise;
   checkState(false, "Update when edit-controls on toolbar and focused");
 
   overridePromise = expectCommandUpdate(1);
   gURLBar.select();
   await overridePromise;
   checkState(true, "Update when edit-controls on toolbar and selection changed");
+
+  const kOverflowPanel = document.getElementById("widget-overflow");
+
+  let originalWidth = window.outerWidth;
+  registerCleanupFunction(async function() {
+    kOverflowPanel.removeAttribute("animate");
+    window.resizeTo(originalWidth, window.outerHeight);
+    await waitForCondition(() => !navbar.hasAttribute("overflowing"));
+    CustomizableUI.reset();
+  });
+
+  window.resizeTo(400, window.outerHeight);
+  await waitForCondition(() => navbar.hasAttribute("overflowing"));
+
+  // Mac will update the enabled state even when the buttons are overflowing,
+  // so main menubar shortcuts will work properly.
+  overridePromise = expectCommandUpdate(isMac ? 1 : 0);
+  gURLBar.select();
+  await overridePromise;
+  checkState(true, "Update when edit-controls is on overflow panel, hidden and selection changed");
+
+  // Check that we get an update if we select content while the panel is open.
+  overridePromise = expectCommandUpdate(1);
+  await navbar.overflowable.show();
+  gURLBar.select();
+  await overridePromise;
+
+  // And that we don't (except on mac) when the panel is hidden.
+  kOverflowPanel.hidePopup();
+  overridePromise = expectCommandUpdate(isMac ? 1 : 0);
+  gURLBar.select();
+  await overridePromise;
+
+  window.resizeTo(originalWidth, window.outerHeight);
+  await waitForCondition(() => !navbar.hasAttribute("overflowing"));
+
+  if (gPhotonStructure) {
+    CustomizableUI.addWidgetToArea("edit-controls", CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
+    // updateEditUIVisibility should be called when customization happens but isn't. See bug 1359790.
+    updateEditUIVisibility();
+
+    overridePromise = expectCommandUpdate(isMac ? 1 : 0);
+    gURLBar.select();
+    await overridePromise;
+
+    // Check that we get an update if we select content while the panel is open.
+    overridePromise = expectCommandUpdate(1);
+    await navbar.overflowable.show();
+    gURLBar.select();
+    await overridePromise;
+
+    // And that we don't (except on mac) when the panel is hidden.
+    kOverflowPanel.hidePopup();
+    overridePromise = expectCommandUpdate(isMac ? 1 : 0);
+    gURLBar.select();
+    await overridePromise;
+  }
 });
 
 // Test updating when the edit-controls are moved to the palette.
 add_task(async function test_panelui_customize_to_palette() {
   await startCustomizing();
   let palette = document.getElementById("customization-palette");
   simulateItemDrag(document.getElementById("edit-controls"), palette);
   await endCustomizing();
--- a/browser/components/extensions/.eslintrc.js
+++ b/browser/components/extensions/.eslintrc.js
@@ -1,23 +1,5 @@
 "use strict";
 
 module.exports = {
   "extends": "../../../toolkit/components/extensions/.eslintrc.js",
-
-  "globals": {
-    "EventEmitter": true,
-    "IconDetails": true,
-    "Tab": true,
-    "TabContext": true,
-    "Window": true,
-    "WindowEventManager": true,
-    "browserActionFor": true,
-    "getCookieStoreIdForTab": true,
-    "getDevToolsTargetForContext": true,
-    "getTargetTabIdForToolbox": true,
-    "makeWidgetId": true,
-    "pageActionFor": true,
-    "sidebarActionFor": true,
-    "tabTracker": true,
-    "windowTracker": true,
-  },
 };
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-browserAction.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 
 let listenerCount = 0;
 
 function getTree(rootGuid, onlyChildren) {
   function convert(node, parent) {
     let treenode = {
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -1,10 +1,13 @@
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
+
 XPCOMUtils.defineLazyModuleGetter(global, "EventEmitter",
                                   "resource://gre/modules/EventEmitter.jsm");
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(extension, target, sender) {
   let tabId;
   if ("tabId" in sender) {
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -1,12 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+/* exported browserActionFor, sidebarActionFor, pageActionFor */
+/* global browserActionFor:false, sidebarActionFor:false, pageActionFor:false */
+
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ViewPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
--- a/browser/components/extensions/ext-c-devtools-panels.js
+++ b/browser/components/extensions/ext-c-devtools-panels.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ../../../toolkit/components/extensions/ext-c-toolkit.js */
+
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://gre/modules/EventEmitter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionChildDevToolsUtils",
                                   "resource://gre/modules/ExtensionChildDevToolsUtils.jsm");
 
 var {
--- a/browser/components/extensions/ext-c-omnibox.js
+++ b/browser/components/extensions/ext-c-omnibox.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ../../../toolkit/components/extensions/ext-c-toolkit.js */
+
 this.omnibox = class extends ExtensionAPI {
   getAPI(context) {
     return {
       omnibox: {
         onInputChanged: new SingletonEventManager(context, "omnibox.onInputChanged", fire => {
           let listener = (text, id) => {
             fire.asyncWithoutClone(text, suggestions => {
               context.childManager.callParentFunctionNoReturn("omnibox_internal.addSuggestions", [
--- a/browser/components/extensions/ext-chrome-settings-overrides.js
+++ b/browser/components/extensions/ext-chrome-settings-overrides.js
@@ -2,25 +2,93 @@
  * 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";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
                                   "resource://gre/modules/ExtensionPreferencesManager.jsm");
 
+function searchInitialized() {
+  return new Promise(resolve => {
+    if (Services.search.isInitialized) {
+      resolve();
+    }
+    const SEARCH_SERVICE_TOPIC = "browser-search-service";
+    Services.obs.addObserver(function observer(subject, topic, data) {
+      if (data != "init-complete") {
+        return;
+      }
+
+      Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
+      resolve();
+    }, SEARCH_SERVICE_TOPIC);
+  });
+}
+
 this.chrome_settings_overrides = class extends ExtensionAPI {
-  onManifestEntry(entryName) {
+  async onManifestEntry(entryName) {
     let {extension} = this;
     let {manifest} = extension;
 
     if (manifest.chrome_settings_overrides.homepage) {
       ExtensionPreferencesManager.setSetting(extension, "homepage_override",
                                              manifest.chrome_settings_overrides.homepage);
     }
+    if (manifest.chrome_settings_overrides.search_provider) {
+      await searchInitialized();
+      let searchProvider = manifest.chrome_settings_overrides.search_provider;
+      let isCurrent = false;
+      let index = -1;
+      if (extension.startupReason === "ADDON_UPGRADE") {
+        let engines = Services.search.getEnginesByExtensionID(extension.id);
+        if (engines.length > 0) {
+          // There can be only one engine right now
+          isCurrent = Services.search.currentEngine == engines[0];
+          // Get position of engine and store it
+          index = Services.search.getEngines().indexOf(engines[0]);
+          Services.search.removeEngine(engines[0]);
+        }
+      }
+      try {
+        Services.search.addEngineWithDetails(searchProvider.name.trim(),
+                                             searchProvider.favicon_url,
+                                             searchProvider.keyword, null,
+                                             "GET", searchProvider.search_url,
+                                             extension.id);
+        if (extension.startupReason === "ADDON_UPGRADE") {
+          let engine = Services.search.getEngineByName(searchProvider.name.trim());
+          if (isCurrent) {
+            Services.search.currentEngine = engine;
+          }
+          if (index != -1) {
+            Services.search.moveEngine(engine, index);
+          }
+        }
+      } catch (e) {
+        Components.utils.reportError(e);
+      }
+    }
+  }
+  async onShutdown(reason) {
+    let {extension} = this;
+    if (reason == "ADDON_DISABLE" ||
+        reason == "ADDON_UNINSTALL") {
+      if (extension.manifest.chrome_settings_overrides.search_provider) {
+        await searchInitialized();
+        let engines = Services.search.getEnginesByExtensionID(extension.id);
+        for (let engine of engines) {
+          try {
+            Services.search.removeEngine(engine);
+          } catch (e) {
+            Components.utils.reportError(e);
+          }
+        }
+      }
+    }
   }
 };
 
 ExtensionPreferencesManager.addSetting("homepage_override", {
   prefNames: [
     "browser.startup.homepage",
   ],
   setCallback(value) {
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -1,12 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-browserAction.js */
+/* import-globals-from ext-utils.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
                                   "resource://gre/modules/ExtensionParent.jsm");
 
 var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 this.commands = class extends ExtensionAPI {
   onManifestEntry(entryName) {
     let {extension} = this;
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
+
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 
 var {
   ExtensionError,
 } = ExtensionUtils;
--- a/browser/components/extensions/ext-devtools-network.js
+++ b/browser/components/extensions/ext-devtools-network.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-devtools.js */
+
 this.devtools_network = class extends ExtensionAPI {
   getAPI(context) {
     return {
       devtools: {
         network: {
           onNavigated: new SingletonEventManager(context, "devtools.onNavigated", fire => {
             let listener = (event, data) => {
               fire.async(data.url);
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -1,12 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-devtools.js */
+/* import-globals-from ext-utils.js */
+
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
                                   "resource:///modules/E10SUtils.jsm");
 
 var {
   IconDetails,
   watchExtensionProxyContextLoad,
@@ -77,17 +81,17 @@ class ParentDevToolsPanel {
     const extensionName = this.context.extension.name;
 
     this.toolbox.addAdditionalTool({
       id: this.id,
       url: "about:blank",
       icon: icon,
       label: title,
       tooltip: `DevTools Panel added by "${extensionName}" add-on.`,
-      invertIconForLightTheme: true,
+      invertIconForLightTheme: false,
       visibilityswitch:  `devtools.webext-${this.id}.enabled`,
       isTargetSupported: target => target.isLocalTab,
       build: (window, toolbox) => {
         if (toolbox !== this.toolbox) {
           throw new Error("Unexpected toolbox received on addAdditionalTool build property");
         }
 
         const destroy = this.buildPanel(window, toolbox);
--- a/browser/components/extensions/ext-devtools.js
+++ b/browser/components/extensions/ext-devtools.js
@@ -1,13 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-/* global getTargetTabIdForToolbox */
+/* exported getDevToolsTargetForContext */
+/* global getTargetTabIdForToolbox, getDevToolsTargetForContext */
+
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
 
 /**
  * This module provides helpers used by the other specialized `ext-devtools-*.js` modules
  * and the implementation of the `devtools_page`.
  */
 
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
                                   "resource://devtools/client/framework/gDevTools.jsm");
--- a/browser/components/extensions/ext-geckoProfiler.js
+++ b/browser/components/extensions/ext-geckoProfiler.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ../../../toolkit/components/extensions/ext-toolkit.js */
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.importGlobalProperties(["fetch", "TextEncoder"]);
 
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ParseSymbols", "resource:///modules/ParseSymbols.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Subprocess", "resource://gre/modules/Subprocess.jsm");
 
 const PREF_ASYNC_STACK = "javascript.options.asyncstack";
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-browserAction.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 
 var {
   normalizeTime,
 } = ExtensionUtils;
--- a/browser/components/extensions/ext-omnibox.js
+++ b/browser/components/extensions/ext-omnibox.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ../../../toolkit/components/extensions/ext-toolkit.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSearchHandler",
                                   "resource://gre/modules/ExtensionSearchHandler.jsm");
 
 this.omnibox = class extends ExtensionAPI {
   onManifestEntry(entryName) {
     let {extension} = this;
     let {manifest} = extension;
 
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,12 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-browserAction.js */
+/* import-globals-from ext-utils.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "PanelPopup",
                                   "resource:///modules/ExtensionPopups.jsm");
 
 
 var {
   DefaultWeakMap,
 } = ExtensionUtils;
 
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
+
 var {
   ExtensionError,
   promiseObserved,
 } = ExtensionUtils;
 
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
                                   "resource:///modules/sessionstore/SessionStore.jsm");
 
--- a/browser/components/extensions/ext-sidebarAction.js
+++ b/browser/components/extensions/ext-sidebarAction.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
+
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -1,12 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+/* exported WindowEventManager, makeWidgetId */
+/* global EventEmitter:false, TabContext:false, WindowEventManager:false,
+          makeWidgetId:false, tabTracker:true, windowTracker:true */
+/* import-globals-from ../../../toolkit/components/extensions/ext-toolkit.js */
+
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionTabs.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -1,12 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+// The ext-* files are imported into the same scopes.
+/* import-globals-from ext-utils.js */
+
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
--- a/browser/components/extensions/schemas/chrome_settings_overrides.json
+++ b/browser/components/extensions/schemas/chrome_settings_overrides.json
@@ -10,16 +10,103 @@
             "optional": true,
             "additionalProperties": { "$ref": "UnrecognizedProperty" },
             "properties": {
               "homepage": {
                 "type": "string",
                 "format": "relativeUrl",
                 "optional": true,
                 "preprocess": "localize"
+              },
+             "search_provider": {
+                "type": "object",
+                "optional": true,
+                "additionalProperties": { "$ref": "UnrecognizedProperty" },
+                "properties": {
+                  "name": {
+                    "type": "string",
+                    "preprocess": "localize"
+                  },
+                  "keyword": {
+                    "type": "string",
+                    "optional": true,
+                    "preprocess": "localize"
+                  },
+                  "search_url": {
+                    "type": "string",
+                    "format": "url",
+                    "pattern": "^https://.*$",
+                    "preprocess": "localize"
+                  },
+                  "favicon_url": {
+                    "type": "string",
+                    "optional": true,
+                    "format": "url",
+                    "preprocess": "localize"
+                  },
+                  "suggest_url": {
+                    "type": "string",
+                    "optional": true,
+                    "format": "url",
+                    "preprocess": "localize",
+                    "deprecated": "Unsupported on Firefox at this time."
+                  },
+                  "instant_url": {
+                    "type": "string",
+                    "optional": true,
+                    "format": "url",
+                    "preprocess": "localize",
+                    "deprecated": "Unsupported on Firefox at this time."
+                  },
+                  "image_url": {
+                    "type": "string",
+                    "optional": true,
+                    "format": "url",
+                    "preprocess": "localize",
+                    "deprecated": "Unsupported on Firefox at this time."
+                  },
+                  "search_url_post_params": {
+                    "type": "string",
+                    "optional": true,
+                    "preprocess": "localize",
+                    "deprecated": "Unsupported on Firefox at this time."
+                  },
+                  "instant_url_post_params": {
+                    "type": "string",
+                    "optional": true,
+                    "preprocess": "localize",
+                    "deprecated": "Unsupported on Firefox at this time."
+                  },
+                  "image_url_post_params": {
+                    "type": "string",
+                    "optional": true,
+                    "preprocess": "localize",
+                    "deprecated": "Unsupported on Firefox at this time."
+                  },
+                  "alternate_urls": {
+                    "type": "array",
+                    "items": {
+                      "type": "string",
+                      "format": "url",
+                      "preprocess": "localize"
+                    },
+                    "optional": true,
+                    "deprecated": "Unsupported on Firefox at this time."
+                  },
+                  "prepopulated_id": {
+                    "type": "integer",
+                    "optional": true,
+                    "deprecated": "Unsupported on Firefox."
+                  },
+                  "is_default": {
+                    "type": "boolean",
+                    "optional": true,
+                    "deprecated": "Unsupported on Firefox at this time."
+                  }
+                }
               }
             }
           }
         }
       }
     ]
   }
 ]
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -91,16 +91,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_sessions_forgetClosedTab.js]
 [browser_ext_sessions_forgetClosedWindow.js]
 [browser_ext_sessions_getRecentlyClosed.js]
 [browser_ext_sessions_getRecentlyClosed_private.js]
 [browser_ext_sessions_getRecentlyClosed_tabs.js]
 [browser_ext_sessions_restore.js]
+[browser_ext_settings_overrides_search.js]
 [browser_ext_sidebarAction.js]
 [browser_ext_sidebarAction_browser_style.js]
 [browser_ext_sidebarAction_context.js]
 [browser_ext_sidebarAction_contextMenu.js]
 [browser_ext_sidebarAction_tabs.js]
 [browser_ext_sidebarAction_windows.js]
 [browser_ext_simple.js]
 [browser_ext_tab_runtimeConnect.js]
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
@@ -207,17 +207,21 @@ add_task(async function test_devtools_pa
 
   const toolboxAdditionalTools = toolbox.getAdditionalTools();
 
   is(toolboxAdditionalTools.length, 1,
      "Got the expected number of toolbox specific panel registered.");
 
   await testThemeSwitching(extension);
 
-  const panelId = toolboxAdditionalTools[0].id;
+  const panelDef = toolboxAdditionalTools[0];
+  const panelId = panelDef.id;
+
+  is(panelDef.invertIconForLightTheme, false,
+     "devtools.panel.create set invertIconForLightTheme to false by default");
 
   await gDevTools.showToolbox(target, panelId);
   const {devtoolsPageTabId} = await extension.awaitMessage("devtools_panel_shown");
   const devtoolsPanelTabId = await extension.awaitMessage("devtools_panel_inspectedWindow_tabId");
   is(devtoolsPanelTabId, devtoolsPageTabId,
      "Got the same devtools.inspectedWindow.tabId from devtools page and panel");
   is(await extension.awaitMessage("initial_theme_panel"),
     "light",
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_settings_overrides_search.js
@@ -0,0 +1,114 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+
+"use strict";
+
+add_task(async function test_extension_adding_engine() {
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+             "name": "MozSearch",
+             "keyword": "MozSearch",
+             "search_url": "https://example.com/?q={searchTerms}",
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  let engine = Services.search.getEngineByName("MozSearch");
+  ok(engine, "Engine should exist.");
+
+  await ext1.unload();
+
+  engine = Services.search.getEngineByName("MozSearch");
+  ok(!engine, "Engine should not exist");
+});
+
+add_task(async function test_extension_adding_engine_with_spaces() {
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+             "name": "MozSearch     ",
+             "keyword": "MozSearch",
+             "search_url": "https://example.com/?q={searchTerms}",
+        },
+      },
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  let engine = Services.search.getEngineByName("MozSearch");
+  ok(engine, "Engine should exist.");
+
+  await ext1.unload();
+
+  engine = Services.search.getEngineByName("MozSearch");
+  ok(!engine, "Engine should not exist");
+});
+
+
+add_task(async function test_upgrade_default_position_engine() {
+  let ext1 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+             "name": "MozSearch",
+             "keyword": "MozSearch",
+             "search_url": "https://example.com/?q={searchTerms}",
+        },
+      },
+      "applications": {
+        "gecko": {
+           "id": "testengine@mozilla.com",
+        },
+      },
+      "version": "0.1",
+    },
+    useAddonManager: "temporary",
+  });
+
+  let ext2 = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "chrome_settings_overrides": {
+        "search_provider": {
+             "name": "MozSearch",
+             "keyword": "MozSearch",
+             "search_url": "https://example.com/?q={searchTerms}",
+        },
+      },
+      "applications": {
+        "gecko": {
+           "id": "testengine@mozilla.com",
+        },
+      },
+      "version": "0.2",
+    },
+    useAddonManager: "temporary",
+  });
+
+  await ext1.startup();
+
+  let engine = Services.search.getEngineByName("MozSearch");
+  Services.search.currentEngine = engine;
+  Services.search.moveEngine(engine, 1);
+
+  await ext2.startup();
+
+  engine = Services.search.getEngineByName("MozSearch");
+  is(Services.search.currentEngine, engine, "Default engine should still be MozSearch");
+  is(Services.search.getEngines().indexOf(engine), 1, "Engine is in position 1");
+
+  await ext2.unload();
+  await ext1.unload();
+
+  engine = Services.search.getEngineByName("MozSearch");
+  ok(!engine, "Engine should not exist");
+});
+
--- a/browser/components/extensions/test/xpcshell/.eslintrc.js
+++ b/browser/components/extensions/test/xpcshell/.eslintrc.js
@@ -1,9 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": "plugin:mozilla/xpcshell-test",
 
-  "globals": {
-    "browser": false,
-  },
+  "env": {
+    // The tests in this folder are testing based on WebExtensions, so lets
+    // just define the webextensions environment here.
+    "webextensions": true
+  }
 };
--- a/browser/components/migration/.eslintrc.js
+++ b/browser/components/migration/.eslintrc.js
@@ -1,15 +1,11 @@
 "use strict";
 
 module.exports = {
-  "globals": {
-    "Iterator": true
-  },
-
   "rules": {
     "block-scoped-var": "error",
     "comma-dangle": "off",
     "comma-style": ["error", "last"],
     "complexity": ["error", {"max": 21}],
     "dot-notation": "error",
     "indent": ["error", 2, {"SwitchCase": 1, "ArrayExpression": "first", "ObjectExpression": "first"}],
     "max-nested-callbacks": ["error", 3],
--- a/browser/config/tooltool-manifests/win64/clang.manifest
+++ b/browser/config/tooltool-manifests/win64/clang.manifest
@@ -1,19 +1,19 @@
 [
   {
     "size": 266240,
     "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
     "algorithm": "sha512",
     "filename": "mozmake.exe"
   },
   {
-    "version": "1.18.0-beta.4 (0308c9865 2017-05-27)",
-    "size": 98338423,
-    "digest": "126dd7054f4d2a705e3abf0f916b62904ae5097b70f67c9d048e031e42a19208c41885c0d479a6f3591f2c74725c4e80ed3dfa4452301618cc7ac0f0820d5683",
+    "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
+    "size": 98336380,
+    "digest": "92091d92ce135ee52486c31ae670735dd140ab5b1389f14582c4d9b14cbb393f7180399b9232564a3eb96443b568323070a3c1329deb07b145b28476e8271175",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
     "version": "sccache rev d3aa1116844b50c03015266d2f48235509fa7deb",
     "algorithm": "sha512",
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -1,19 +1,19 @@
 [
   {
     "size": 266240,
     "digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
     "algorithm": "sha512",
     "filename": "mozmake.exe"
   },
   {
-    "version": "1.18.0-beta.4 (0308c9865 2017-05-27)",
-    "size": 98338423,
-    "digest": "126dd7054f4d2a705e3abf0f916b62904ae5097b70f67c9d048e031e42a19208c41885c0d479a6f3591f2c74725c4e80ed3dfa4452301618cc7ac0f0820d5683",
+    "version": "rustc 1.18.0 (03fc9d622 2017-06-06) repack",
+    "size": 98336380,
+    "digest": "92091d92ce135ee52486c31ae670735dd140ab5b1389f14582c4d9b14cbb393f7180399b9232564a3eb96443b568323070a3c1329deb07b145b28476e8271175",
     "algorithm": "sha512",
     "visibility": "public",
     "filename": "rustc.tar.bz2",
     "unpack": true
   },
   {
     "version": "sccache rev d3aa1116844b50c03015266d2f48235509fa7deb",
     "algorithm": "sha512",
--- a/browser/extensions/formautofill/.eslintrc.js
+++ b/browser/extensions/formautofill/.eslintrc.js
@@ -1,19 +1,11 @@
 "use strict";
 
 module.exports = {
-  "globals": {
-    "addMessageListener": false,
-    "removeMessageListener": false,
-    "sendAsyncMessage": false,
-    "TextDecoder": false,
-    "TextEncoder": false,
-  },
-
   "rules": {
     // Rules from the mozilla plugin
     "mozilla/balanced-listeners": "error",
     "mozilla/no-aArgs": "error",
     "mozilla/no-cpows-in-tests": "error",
     "mozilla/var-only-at-top-level": "error",
 
     "valid-jsdoc": ["error", {
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -1,10 +1,11 @@
 // assert is available to chrome scripts loaded via SpecialPowers.loadChromeScript.
 /* global assert */
+/* eslint-env mozilla/frame-script */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 var ParentUtils = {
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -6,26 +6,26 @@
 
 DIRS += [
     'aushelper',
     'e10srollout',
     'followonsearch',
     'pdfjs',
     'pocket',
     'screenshots',
+    'shield-recipe-client',
     'webcompat',
 ]
 
 # Only include the following system add-ons if building Aurora or Nightly
 if not CONFIG['RELEASE_OR_BETA']:
     DIRS += [
         'flyweb',
         'formautofill',
         'presentation',
-        'shield-recipe-client',
         'webcompat-reporter',
     ]
 
 # Only include mortar system add-ons if we locally enable it
 if CONFIG['MOZ_MORTAR']:
     DIRS += [
         'mortar',
     ]
--- a/browser/locales/en-US/chrome/browser/accounts.properties
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -21,17 +21,17 @@ verificationNotSentBody = We are unable 
 # deviceConnectedBody.noDeviceName is shown instead of deviceConnectedBody when we
 # could not get the device name that joined
 deviceConnectedTitle = Firefox Sync
 deviceConnectedBody = This computer is now syncing with %S.
 deviceConnectedBody.noDeviceName = This computer is now syncing with a new device.
 
 # LOCALIZATION NOTE (syncStartNotification.title, syncStartNotification.body)
 # These strings are used in a notification shown after Sync is connected.
-syncStartNotification.title = Sync enabled
+syncStartNotification.title = Sync Enabled
 # %S is brandShortName
 syncStartNotification.body2 = %S will begin syncing momentarily.
 
 # LOCALIZATION NOTE (deviceDisconnectedNotification.title, deviceDisconnectedNotification.body)
 # These strings are used in a notification shown after Sync was disconnected remotely.
 deviceDisconnectedNotification.title = Sync disconnected
 deviceDisconnectedNotification.body = This computer has been successfully disconnected from Firefox Sync.
 
--- a/browser/locales/searchplugins/bok-NO.xml
+++ b/browser/locales/searchplugins/bok-NO.xml
@@ -1,16 +1,15 @@
 <!-- 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/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Ordbok</ShortName>
-<Description>Norske ordbøker.</Description>
-<InputEncoding>ISO-8859-1</InputEncoding>
+<Description>Norske ordbøker</Description>
+<InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB2UlEQVQ4ja3TzWuSAQDH8edvyqioiIg61FBIRqNDjBi1YIeYk8VeFJJWTy+jIJq5ww5L7ZArNsuY0xE2e2G6YTC1NnGlOdPHR30eH19W8O2wQ8dq7vC7fk7fn1BVNVStuatVVQ2hpjXRGtu7Wk1rIqhtAOqeARW1yeqXEs/efOPhTBLRFcMVSKFqLbTGNtXFp8hTJipOE6rvFtqnOTRF/gMsxQrYX6xz40kc06Mo3TdDnBkKMBNMUlp6iXRVhzRymLJ4AmWig7qzk9Y7cQeQKnXmQlkmZzcQ3QkGHav03A6jHwlwRfSTHuum1L8PyXqUyt2TqA49Dfc5fvouohVTCKlsFe/b70y/TiO645jtK/SOv+esdZEui4/ENQPygI7S9WOU7+wATc95fvl7aKT8CNGExMTzJL3jYYyWAPrheYyWBQyjQYxDr0gMnEY26SiYD/Kj/wAF8yFk23Hq0520ovcRVpJFDMPznDL76LIFmfQm+LCWZyGS5Z4rwuaUDSU8i7L2kbLXQcHaQb5PR75vP2rEg5ArqlwYCzFoXyadq/49nkIe+fFlpNEjKJtxhK9bCg88n8kUav/egFxEcV5C3VhG2JJqrGeU/46onolRyyX2oMS2z9TunX8DKb6IjiC3ONQAAAAASUVORK5CYII=</Image>
-<Url type="text/html" method="GET" template="http://www.nob-ordbok.uio.no/perl/ordbok.cgi" resultdomain="uio.no">
+<Url type="text/html" method="GET" template="http://ordbok.uib.no/perl/ordbok.cgi" resultdomain="uib.no">
   <Param name="OPP" value="{searchTerms}"/>
   <Param name="sourceid" value="Mozilla-search"/>
 </Url>
-<SearchForm>http://www.nob-ordbok.uio.no/</SearchForm>
+<SearchForm>http://ordbok.uib.no/</SearchForm>
 </SearchPlugin>
-
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -88,16 +88,17 @@
 
 /* Page action button */
 #urlbar-page-action-button {
   -moz-appearance: none;
   border-style: none;
   list-style-image: url("chrome://browser/skin/page-action.svg");
   margin: 0;
   padding: 0 6px;
+  color: inherit;
   -moz-context-properties: fill;
   fill: currentColor;
 }
 
 #urlbar-page-action-button > .toolbarbutton-icon {
   width: 16px;
 }
 
--- a/devtools/bootstrap.js
+++ b/devtools/bootstrap.js
@@ -68,19 +68,21 @@ function interpretPreprocessingInstructi
   for (let line of lines) {
     if (line.startsWith("#if")) {
       if (!(line in ifMap)) {
         throw new Error("missing line in ifMap: " + line);
       }
       ignoring = !ifMap[line];
     } else if (line.startsWith("#else")) {
       ignoring = !ignoring;
+    } else if (line.startsWith("#endif")) {
+      ignoring = false;
     }
 
-    let isPrefLine = /^ *pref\("([^"]+)"/.test(line);
+    let isPrefLine = /^ *(sticky_)?pref\("([^"]+)"/.test(line);
     if (continuation || (!ignoring && isPrefLine)) {
       newLines.push(line);
 
       // The call to pref(...); might span more than one line.
       continuation = !/\);/.test(line);
     }
   }
   return newLines.join("\n");
@@ -89,17 +91,17 @@ function interpretPreprocessingInstructi
 // Read a preference file and set all of its defined pref as default values
 // (This replicates the behavior of preferences files from mozilla-central)
 function processPrefFile(url) {
   let content = readURI(url);
   content = interpretPreprocessingInstructions(content);
   content.match(/pref\("[^"]+",\s*.+\s*\)/g).forEach(item => {
     let m = item.match(/pref\("([^"]+)",\s*(.+)\s*\)/);
     let name = m[1];
-    let val = m[2];
+    let val = m[2].trim();
 
     // Prevent overriding prefs that have been changed by the user
     if (Services.prefs.prefHasUserValue(name)) {
       return;
     }
     let defaultBranch = Services.prefs.getDefaultBranch("");
     if ((val.startsWith("\"") && val.endsWith("\"")) ||
         (val.startsWith("'") && val.endsWith("'"))) {
--- a/devtools/client/inspector/webpack/prefs-loader.js
+++ b/devtools/client/inspector/webpack/prefs-loader.js
@@ -52,16 +52,18 @@ module.exports = function (content) {
     if (isDevtools) {
       if (line.startsWith("#if")) {
         if (!(line in ifMap)) {
           throw new Error("missing line in ifMap: " + line);
         }
         ignoring = !ifMap[line];
       } else if (line.startsWith("#else")) {
         ignoring = !ignoring;
+      } else if (line.startsWith("#endif")) {
+        ignoring = false;
       }
     }
 
     if (continuation || (!ignoring && acceptLine(line))) {
       newLines.push(line);
 
       // The call to pref(...); might span more than one line.
       continuation = !/\);/.test(line);
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -4,18 +4,16 @@
 
 devtools.jar:
 %   content devtools %content/
     content/shared/vendor/d3.js (shared/vendor/d3.js)
     content/shared/vendor/dagre-d3.js (shared/vendor/dagre-d3.js)
     content/shared/widgets/widgets.css (shared/widgets/widgets.css)
     content/netmonitor/src/assets/styles/netmonitor.css (netmonitor/src/assets/styles/netmonitor.css)
     content/shared/widgets/VariablesView.xul (shared/widgets/VariablesView.xul)
-    content/projecteditor/chrome/content/projecteditor.xul (projecteditor/chrome/content/projecteditor.xul)
-    content/projecteditor/lib/helpers/readdir.js (projecteditor/lib/helpers/readdir.js)
     content/netmonitor/index.html (netmonitor/index.html)
     content/webconsole/webconsole.xul (webconsole/webconsole.xul)
     content/scratchpad/scratchpad.xul (scratchpad/scratchpad.xul)
     content/scratchpad/scratchpad.js (scratchpad/scratchpad.js)
     content/shared/splitview.css (shared/splitview.css)
     content/shared/theme-switching.js (shared/theme-switching.js)
     content/shared/frame-script-utils.js (shared/frame-script-utils.js)
     content/styleeditor/styleeditor.xul (styleeditor/styleeditor.xul)
@@ -248,17 +246,16 @@ devtools.jar:
     skin/images/sort-descending-arrow.svg (themes/images/sort-descending-arrow.svg)
     skin/images/cubic-bezier-swatch.png (themes/images/cubic-bezier-swatch.png)
     skin/images/cubic-bezier-swatch@2x.png (themes/images/cubic-bezier-swatch@2x.png)
     skin/fonts.css (themes/fonts.css)
     skin/computed.css (themes/computed.css)
     skin/layout.css (themes/layout.css)
     skin/images/arrow-e.png (themes/images/arrow-e.png)
     skin/images/arrow-e@2x.png (themes/images/arrow-e@2x.png)
-    skin/projecteditor/projecteditor.css (themes/projecteditor/projecteditor.css)
     skin/images/search-clear-failed.svg (themes/images/search-clear-failed.svg)
     skin/images/search-clear-light.svg (themes/images/search-clear-light.svg)
     skin/images/search-clear-dark.svg (themes/images/search-clear-dark.svg)
     skin/tooltip/arrow-horizontal-dark.png (themes/tooltip/arrow-horizontal-dark.png)
     skin/tooltip/arrow-horizontal-dark@2x.png (themes/tooltip/arrow-horizontal-dark@2x.png)
     skin/tooltip/arrow-vertical-dark.png (themes/tooltip/arrow-vertical-dark.png)
     skin/tooltip/arrow-vertical-dark@2x.png (themes/tooltip/arrow-vertical-dark@2x.png)
     skin/tooltip/arrow-horizontal-light.png (themes/tooltip/arrow-horizontal-light.png)
deleted file mode 100644
--- a/devtools/client/locales/en-US/projecteditor.properties
+++ /dev/null
@@ -1,88 +0,0 @@
-# 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/.
-
-# LOCALIZATION NOTE These strings are used inside the ProjectEditor component
-# which is used for editing files in a directory and is used inside the
-# App Manager.
-# The correct localization of this file might be to keep it in
-# English, or another language commonly spoken among web developers.
-# You want to make that choice consistent across the developer tools.
-# A good criteria is the language in which you'd find the best
-# documentation on web development on the web.
-
-# LOCALIZATION NOTE (projecteditor.confirmUnsavedTitle):
-# This string is displayed as as the title of the confirm prompt that checks
-# to make sure if the project can be closed/switched without saving changes
-projecteditor.confirmUnsavedTitle=Unsaved Changes
-
-# LOCALIZATION NOTE (projecteditor.confirmUnsavedLabel2):
-# This string is displayed as the message of the confirm prompt that checks
-# to make sure if the project can be closed/switched without saving changes
-projecteditor.confirmUnsavedLabel2=You have unsaved changes that will be lost. Are you sure you want to continue?
-
-# LOCALIZATION NOTE (projecteditor.deleteLabel):
-# This string is displayed as a context menu item for allowing the selected
-# file / folder to be deleted.
-projecteditor.deleteLabel=Delete
-
-# LOCALIZATION NOTE (projecteditor.deletePromptTitle):
-# This string is displayed as as the title of the confirm prompt that checks
-# to make sure if a file or folder should be removed.
-projecteditor.deletePromptTitle=Delete
-
-# LOCALIZATION NOTE (projecteditor.deleteFolderPromptMessage):
-# This string is displayed as as the message of the confirm prompt that checks
-# to make sure if a folder should be removed.
-projecteditor.deleteFolderPromptMessage=Are you sure you want to delete this folder?
-
-# LOCALIZATION NOTE (projecteditor.deleteFilePromptMessage):
-# This string is displayed as as the message of the confirm prompt that checks
-# to make sure if a file should be removed.
-projecteditor.deleteFilePromptMessage=Are you sure you want to delete this file?
-
-# LOCALIZATION NOTE (projecteditor.newLabel):
-# This string is displayed as a menu item for adding a new file to
-# the directory.
-projecteditor.newLabel=New…
-
-# LOCALIZATION NOTE (projecteditor.renameLabel):
-# This string is displayed as a menu item for renaming a file in
-# the directory.
-projecteditor.renameLabel=Rename
-
-# LOCALIZATION NOTE (projecteditor.saveLabel):
-# This string is displayed as a menu item for saving the current file.
-projecteditor.saveLabel=Save
-
-# LOCALIZATION NOTE (projecteditor.saveAsLabel):
-# This string is displayed as a menu item for saving the current file
-# with a new name.
-projecteditor.saveAsLabel=Save As…
-
-# LOCALIZATION NOTE (projecteditor.selectFileLabel):
-# This string is displayed as the title on the file picker when saving a file.
-projecteditor.selectFileLabel=Select a File
-
-# LOCALIZATION NOTE (projecteditor.openFolderLabel):
-# This string is displayed as the title on the file picker when opening a folder.
-projecteditor.openFolderLabel=Select a Folder
-
-# LOCALIZATION NOTE (projecteditor.openFileLabel):
-# This string is displayed as the title on the file picker when opening a file.
-projecteditor.openFileLabel=Open a File
-
-# LOCALIZATION NOTE  (projecteditor.find.commandkey): This is the key to use in
-# conjunction with accel (Command on Mac or Ctrl on other platforms) to search
-# text in the files.
-projecteditor.find.commandkey=F
-
-# LOCALIZATION NOTE  (projecteditor.save.commandkey): This is the key to use in
-# conjunction with accel (Command on Mac or Ctrl on other platforms) to
-# save the file.  It is used with accel+shift to "save as".
-projecteditor.save.commandkey=S
-
-# LOCALIZATION NOTE  (projecteditor.new.commandkey): This is the key to use in
-# conjunction with accel (Command on Mac or Ctrl on other platforms) to
-# create a new file.
-projecteditor.new.commandkey=N
--- a/devtools/client/locales/en-US/webide.dtd
+++ b/devtools/client/locales/en-US/webide.dtd
@@ -40,18 +40,16 @@
 <!ENTITY runtimeMenu_showMonitor_accesskey "M">
 <!ENTITY runtimeMenu_showDevicePrefs_label "Device Preferences">
 <!ENTITY runtimeMenu_showDevicePrefs_accesskey "D">
 <!ENTITY runtimeMenu_showSettings_label "Device Settings">
 <!ENTITY runtimeMenu_showSettings_accesskey "s">
 
 <!ENTITY viewMenu_label "View">
 <!ENTITY viewMenu_accesskey "V">
-<!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
-<!ENTITY viewMenu_toggleEditor_accesskey "E">
 <!ENTITY viewMenu_zoomin_label "Zoom In">
 <!ENTITY viewMenu_zoomin_accesskey "I">
 <!ENTITY viewMenu_zoomout_label "Zoom Out">
 <!ENTITY viewMenu_zoomout_accesskey "O">
 <!ENTITY viewMenu_resetzoom_label "Reset Zoom">
 <!ENTITY viewMenu_resetzoom_accesskey "R">
 
 <!ENTITY projectButton_label "Open App">
@@ -61,18 +59,16 @@
 <!-- quit app -->
 <!ENTITY key_quit "W">
 <!-- open menu -->
 <!ENTITY key_showProjectPanel "O">
 <!-- reload app -->
 <!ENTITY key_play "R">
 <!-- show toolbox -->
 <!ENTITY key_toggleToolbox "VK_F12">
-<!-- toggle sidebar -->
-<!ENTITY key_toggleEditor "B">
 <!-- zoom -->
 <!ENTITY key_zoomin "+">
 <!ENTITY key_zoomin2 "=">
 <!ENTITY key_zoomout "-">
 <!ENTITY key_resetzoom "0">
 
 <!ENTITY projectPanel_myProjects "My Projects">
 <!ENTITY projectPanel_runtimeApps "Runtime Apps">
@@ -118,31 +114,16 @@
 <!ENTITY prefs_restore "Restore Defaults">
 <!ENTITY prefs_manage_components "Manage Extra Components">
 <!ENTITY prefs_options_autoconnectruntime "Reconnect to previous runtime">
 <!ENTITY prefs_options_autoconnectruntime_tooltip "Reconnect to previous runtime when WebIDE starts">
 <!ENTITY prefs_options_rememberlastproject "Remember last project">
 <!ENTITY prefs_options_rememberlastproject_tooltip "Restore previous project when WebIDE starts">
 <!ENTITY prefs_options_templatesurl "Templates URL">
 <!ENTITY prefs_options_templatesurl_tooltip "Index of available templates">
-<!ENTITY prefs_options_showeditor "Show editor">
-<!ENTITY prefs_options_showeditor_tooltip "Show internal editor">
-<!ENTITY prefs_options_tabsize "Tab size">
-<!ENTITY prefs_options_expandtab "Soft tabs">
-<!ENTITY prefs_options_expandtab_tooltip "Use spaces instead of the tab character">
-<!ENTITY prefs_options_detectindentation "Autoindent">
-<!ENTITY prefs_options_detectindentation_tooltip "Guess indentation based on source content">
-<!ENTITY prefs_options_autocomplete "Autocomplete">
-<!ENTITY prefs_options_autocomplete_tooltip "Enable code autocompletion">
-<!ENTITY prefs_options_autoclosebrackets "Autoclose brackets">
-<!ENTITY prefs_options_autoclosebrackets_tooltip "Automatically insert closing brackets">
-<!ENTITY prefs_options_keybindings "Keybindings">
-<!ENTITY prefs_options_keybindings_default "Default">
-<!ENTITY prefs_options_autosavefiles "Autosave files">
-<!ENTITY prefs_options_autosavefiles_tooltip "Automatically save edited files before running project">
 
 <!-- Runtime Details -->
 <!ENTITY runtimedetails_title "Runtime Info">
 <!ENTITY runtimedetails_adbIsRoot "ADB is root: ">
 <!ENTITY runtimedetails_summonADBRoot "root device">
 <!ENTITY runtimedetails_ADBRootWarning "(requires unlocked bootloader)">
 <!ENTITY runtimedetails_unrestrictedPrivileges "Unrestricted DevTools privileges: ">
 <!ENTITY runtimedetails_requestPrivileges "request higher privileges">
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -16,17 +16,16 @@ DIRS += [
     'framework',
     'inspector',
     'jsonview',
     'locales',
     'memory',
     'netmonitor',
     'performance',
     'preferences',
-    'projecteditor',
     'responsive.html',
     'responsivedesign',
     'scratchpad',
     'shadereditor',
     'shared',
     'shims',
     'sourceeditor',
     'storage',
@@ -48,10 +47,10 @@ EXTRA_COMPONENTS += [
 
 JAR_MANIFESTS += ['jar.mn']
 
 DevToolsModules(
     'definitions.js',
     'menus.js',
 )
 
-with Files('**'):
-    BUG_COMPONENT = ('Firefox', 'Developer Tools')
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'Developer Tools')
deleted file mode 100644
--- a/devtools/client/projecteditor/chrome/content/projecteditor.xul
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
-<?xml-stylesheet href="chrome://devtools/skin/light-theme.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/projecteditor/projecteditor.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/content/debugger/debugger.css" type="text/css"?>
-<?xml-stylesheet href="resource://devtools/client/themes/common.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/markup.css" type="text/css"?>
-
-<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
-
-<!DOCTYPE window [
-<!ENTITY % scratchpadDTD SYSTEM "chrome://devtools/locale/scratchpad.dtd" >
- %scratchpadDTD;
-<!ENTITY % editMenuStrings SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
-%editMenuStrings;
-<!ENTITY % sourceEditorStrings SYSTEM "chrome://devtools/locale/sourceeditor.dtd">
-%sourceEditorStrings;
-]>
-
-<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="theme-body theme-light">
-
-  <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
-
-  <commandset id="projecteditor-commandset" />
-  <commandset id="editMenuCommands"/>
-  <keyset id="projecteditor-keyset" />
-  <keyset id="editMenuKeys"/>
-
-  <!-- Eventually we want to let plugins declare their own menu items.
-       Wait unti app manager lands to deal with this integration point.
-  -->
-  <menubar id="projecteditor-menubar">
-    <menu id="file-menu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
-      <menupopup id="file-menu-popup" />
-    </menu>
-
-    <menu id="edit-menu" label="&editMenu.label;"
-          accesskey="&editMenu.accesskey;">
-      <menupopup id="edit-menu-popup">
-        <menuitem id="menu_undo"/>
-        <menuitem id="menu_redo"/>
-        <menuseparator/>
-        <menuitem id="menu_cut"/>
-        <menuitem id="menu_copy"/>
-        <menuitem id="menu_paste"/>
-      </menupopup>
-    </menu>
-  </menubar>
-
-  <popupset>
-    <menupopup id="context-menu-popup">
-    </menupopup>
-    <menupopup id="texteditor-context-popup">
-      <menuitem id="cMenu_cut"/>
-      <menuitem id="cMenu_copy"/>
-      <menuitem id="cMenu_paste"/>
-      <menuitem id="cMenu_delete"/>
-      <menuseparator/>
-      <menuitem id="cMenu_selectAll"/>
-    </menupopup>
-  </popupset>
-
-  <deck id="main-deck" flex="1">
-    <vbox flex="1" id="source-deckitem">
-      <hbox id="sources-body" flex="1">
-        <vbox width="250" id="sources">
-          <vbox flex="1">
-          </vbox>
-          <toolbar id="project-toolbar" class="devtools-toolbar" hidden="true"></toolbar>
-        </vbox>
-        <splitter id="source-editor-splitter" class="devtools-side-splitter"/>
-        <vbox id="shells" flex="4">
-          <toolbar id="projecteditor-toolbar" class="devtools-toolbar">
-            <hbox id="plugin-toolbar-left"/>
-            <spacer flex="1"/>
-            <hbox id="plugin-toolbar-right"/>
-          </toolbar>
-          <box id="shells-deck-container" flex="4"></box>
-          <toolbar id="projecteditor-toolbar-bottom" class="devtools-toolbar">
-          </toolbar>
-        </vbox>
-      </hbox>
-    </vbox>
-  </deck>
-</page>
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/editors.js
+++ /dev/null
@@ -1,303 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const { EventTarget } = require("sdk/event/target");
-const { emit } = require("sdk/event/core");
-const promise = require("promise");
-const Editor = require("devtools/client/sourceeditor/editor");
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-/**
- * ItchEditor is extended to implement an editor, which is the main view
- * that shows up when a file is selected.  This object should not be used
- * directly - use TextEditor for a basic code editor.
- */
-var ItchEditor = Class({
-  extends: EventTarget,
-
-  /**
-   * A boolean specifying if the toolbar above the editor should be hidden.
-   */
-  hidesToolbar: false,
-
-  /**
-   * A boolean specifying whether the editor can be edited / saved.
-   * For instance, a 'save' doesn't make sense on an image.
-   */
-  isEditable: false,
-
-  toString: function () {
-    return this.label || "";
-  },
-
-  emit: function (name, ...args) {
-    emit(this, name, ...args);
-  },
-
-  /* Does the editor not have any unsaved changes? */
-  isClean: function () {
-    return true;
-  },
-
-  /**
-   * Initialize the editor with a single host.  This should be called
-   * by objects extending this object with:
-   * ItchEditor.prototype.initialize.apply(this, arguments)
-   */
-  initialize: function (host) {
-    this.host = host;
-    this.doc = host.document;
-    this.label = "";
-    this.elt = this.doc.createElement("vbox");
-    this.elt.setAttribute("flex", "1");
-    this.elt.editor = this;
-    this.toolbar = this.doc.querySelector("#projecteditor-toolbar");
-    this.projectEditorKeyset = host.projectEditorKeyset;
-    this.projectEditorCommandset = host.projectEditorCommandset;
-  },
-
-  /**
-   * Sets the visibility of the element that shows up above the editor
-   * based on the this.hidesToolbar property.
-   */
-  setToolbarVisibility: function () {
-    if (this.hidesToolbar) {
-      this.toolbar.setAttribute("hidden", "true");
-    } else {
-      this.toolbar.removeAttribute("hidden");
-    }
-  },
-
-
-  /**
-   * Load a single resource into the editor.
-   *
-   * @param Resource resource
-   *        The single file / item that is being dealt with (see stores/base)
-   * @returns Promise
-   *          A promise that is resolved once the editor has loaded the contents
-   *          of the resource.
-   */
-  load: function (resource) {
-    return promise.resolve();
-  },
-
-  /**
-   * Clean up the editor.  This can have different meanings
-   * depending on the type of editor.
-   */
-  destroy: function () {
-
-  },
-
-  /**
-   * Give focus to the editor.  This can have different meanings
-   * depending on the type of editor.
-   *
-   * @returns Promise
-   *          A promise that is resolved once the editor has been focused.
-   */
-  focus: function () {
-    return promise.resolve();
-  }
-});
-exports.ItchEditor = ItchEditor;
-
-/**
- * The main implementation of the ItchEditor class.  The TextEditor is used
- * when editing any sort of plain text file, and can be created with different
- * modes for syntax highlighting depending on the language.
- */
-var TextEditor = Class({
-  extends: ItchEditor,
-
-  isEditable: true,
-
-  /**
-   * Extra keyboard shortcuts to use with the editor.  Shortcuts defined
-   * within projecteditor should be triggered when they happen in the editor, and
-   * they would usually be swallowed without registering them.
-   * See "devtools/sourceeditor/editor" for more information.
-   */
-  get extraKeys() {
-    let extraKeys = {};
-
-    // Copy all of the registered keys into extraKeys object, to notify CodeMirror
-    // that it should be ignoring these keys
-    [...this.projectEditorKeyset.querySelectorAll("key")].forEach((key) => {
-      let keyUpper = key.getAttribute("key").toUpperCase();
-      let toolModifiers = key.getAttribute("modifiers");
-      let modifiers = {
-        alt: toolModifiers.includes("alt"),
-        shift: toolModifiers.includes("shift")
-      };
-
-      // On the key press, we will dispatch the event within projecteditor.
-      extraKeys[Editor.accel(keyUpper, modifiers)] = () => {
-        let doc = this.projectEditorCommandset.ownerDocument;
-        let event = doc.createEvent("Event");
-        event.initEvent("command", true, true);
-        let command = this.projectEditorCommandset.querySelector("#" + key.getAttribute("command"));
-        command.dispatchEvent(event);
-      };
-    });
-
-    return extraKeys;
-  },
-
-  isClean: function () {
-    if (!this.editor.isAppended()) {
-      return true;
-    }
-    return this.editor.getText() === this._savedResourceContents;
-  },
-
-  initialize: function (document, mode = Editor.modes.text) {
-    ItchEditor.prototype.initialize.apply(this, arguments);
-    this.label = mode.name;
-    this.editor = new Editor({
-      mode: mode,
-      lineNumbers: true,
-      extraKeys: this.extraKeys,
-      themeSwitching: false,
-      autocomplete: true,
-      contextMenu:  this.host.textEditorContextMenuPopup
-    });
-
-    // Trigger a few editor specific events on `this`.
-    this.editor.on("change", (...args) => {
-      this.emit("change", ...args);
-    });
-    this.editor.on("cursorActivity", (...args) => {
-      this.emit("cursorActivity", ...args);
-    });
-    this.editor.on("focus", (...args) => {
-      this.emit("focus", ...args);
-    });
-    this.editor.on("saveRequested", (...args) => {
-      this.emit("saveRequested", ...args);
-    });
-
-    this.appended = this.editor.appendTo(this.elt);
-  },
-
-  /**
-   * Clean up the editor.  This can have different meanings
-   * depending on the type of editor.
-   */
-  destroy: function () {
-    this.editor.destroy();
-    this.editor = null;
-  },
-
-  /**
-   * Load a single resource into the text editor.
-   *
-   * @param Resource resource
-   *        The single file / item that is being dealt with (see stores/base)
-   * @returns Promise
-   *          A promise that is resolved once the text editor has loaded the
-   *          contents of the resource.
-   */
-  load: function (resource) {
-    // Wait for the editor.appendTo and resource.load before proceeding.
-    // They can run in parallel.
-    return promise.all([
-      resource.load(),
-      this.appended
-    ]).then(([resourceContents])=> {
-      if (!this.editor) {
-        return;
-      }
-      this._savedResourceContents = resourceContents;
-      this.editor.setText(resourceContents);
-      this.editor.clearHistory();
-      this.editor.setClean();
-      this.emit("load");
-    }, console.error);
-  },
-
-  /**
-   * Save the resource based on the current state of the editor
-   *
-   * @param Resource resource
-   *        The single file / item to be saved
-   * @returns Promise
-   *          A promise that is resolved once the resource has been
-   *          saved.
-   */
-  save: function (resource) {
-    let newText = this.editor.getText();
-    return resource.save(newText).then(() => {
-      this._savedResourceContents = newText;
-      this.emit("save", resource);
-    });
-  },
-
-  /**
-   * Give focus to the code editor.
-   *
-   * @returns Promise
-   *          A promise that is resolved once the editor has been focused.
-   */
-  focus: function () {
-    return this.appended.then(() => {
-      if (this.editor) {
-        this.editor.focus();
-      }
-    });
-  }
-});
-
-/**
- * Wrapper for TextEditor using JavaScript syntax highlighting.
- */
-function JSEditor(host) {
-  return TextEditor(host, Editor.modes.js);
-}
-
-/**
- * Wrapper for TextEditor using CSS syntax highlighting.
- */
-function CSSEditor(host) {
-  return TextEditor(host, Editor.modes.css);
-}
-
-/**
- * Wrapper for TextEditor using HTML syntax highlighting.
- */
-function HTMLEditor(host) {
-  return TextEditor(host, Editor.modes.html);
-}
-
-/**
- * Get the type of editor that can handle a particular resource.
- * @param Resource resource
- *        The single file that is going to be opened.
- * @returns Type:Editor
- *          The type of editor that can handle this resource.  The
- *          return value is a constructor function.
- */
-function EditorTypeForResource(resource) {
-  const categoryMap = {
-    "txt": TextEditor,
-    "html": HTMLEditor,
-    "xml": HTMLEditor,
-    "css": CSSEditor,
-    "js": JSEditor,
-    "json": JSEditor
-  };
-  return categoryMap[resource.contentCategory] || TextEditor;
-}
-
-exports.TextEditor = TextEditor;
-exports.JSEditor = JSEditor;
-exports.CSSEditor = CSSEditor;
-exports.HTMLEditor = HTMLEditor;
-exports.EditorTypeForResource = EditorTypeForResource;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/helpers/event.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/* -*- 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/. */
-
-/**
- * This file wraps EventEmitter objects to provide functions to forget
- * all events bound on a certain object.
- */
-
-const { Class } = require("sdk/core/heritage");
-
-/**
- * The Scope object is used to keep track of listeners.
- * This object is not exported.
- */
-var Scope = Class({
-  on: function (target, event, handler) {
-    this.listeners = this.listeners || [];
-    this.listeners.push({
-      target: target,
-      event: event,
-      handler: handler
-    });
-    target.on(event, handler);
-  },
-
-  off: function (t, e, h) {
-    if (!this.listeners) return;
-    this.listeners = this.listeners.filter(({ target, event, handler }) => {
-      return !(target === t && event === e && handler === h);
-    });
-    target.off(event, handler);
-  },
-
-  clear: function (clearTarget) {
-    if (!this.listeners) return;
-    this.listeners = this.listeners.filter(({ target, event, handler }) => {
-      if (target === clearTarget) {
-        target.off(event, handler);
-        return false;
-      }
-      return true;
-    });
-  },
-
-  destroy: function () {
-    if (!this.listeners) return;
-    this.listeners.forEach(({ target, event, handler }) => {
-      target.off(event, handler);
-    });
-    this.listeners = undefined;
-  }
-});
-
-var scopes = new WeakMap();
-function scope(owner) {
-  if (!scopes.has(owner)) {
-    let scope = new Scope(owner);
-    scopes.set(owner, scope);
-    return scope;
-  }
-  return scopes.get(owner);
-}
-exports.scope = scope;
-
-exports.on = function on(owner, target, event, handler) {
-  if (!target) return;
-  scope(owner).on(target, event, handler);
-};
-
-exports.off = function off(owner, target, event, handler) {
-  if (!target) return;
-  scope(owner).off(target, event, handler);
-};
-
-exports.forget = function forget(owner, target) {
-  scope(owner).clear(target);
-};
-
-exports.done = function done(owner) {
-  scope(owner).destroy();
-  scopes.delete(owner);
-};
-
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/helpers/file-picker.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/* -*- 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/. */
-
-/**
- * This file contains helper functions for showing OS-specific
- * file and folder pickers.
- */
-
-const { Cu, Cc, Ci } = require("chrome");
-const { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
-const promise = require("promise");
-const { merge } = require("sdk/util/object");
-const { getLocalizedString } = require("devtools/client/projecteditor/lib/helpers/l10n");
-
-/**
- * Show a file / folder picker.
- * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIFilePicker
- *
- * @param object options
- *        Additional options for setting the source. Supported options:
- *          - directory: string, The path to default opening
- *          - defaultName: string, The filename including extension that
- *                         should be suggested to the user as a default
- *          - window: DOMWindow, The filename including extension that
- *                         should be suggested to the user as a default
- *          - title: string, The filename including extension that
- *                         should be suggested to the user as a default
- *          - mode: int, The type of picker to open.
- *
- * @return promise
- *         A promise that is resolved with the full path
- *         after the file has been picked.
- */
-function showPicker(options) {
-  let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-  if (options.directory) {
-    try {
-      fp.displayDirectory = FileUtils.File(options.directory);
-    } catch (ex) {
-      console.warn(ex);
-    }
-  }
-
-  if (options.defaultName) {
-    fp.defaultString = options.defaultName;
-  }
-
-  fp.init(options.window, options.title, options.mode);
-  let deferred = promise.defer();
-  fp.open({
-    done: function (res) {
-      if (res === Ci.nsIFilePicker.returnOK || res === Ci.nsIFilePicker.returnReplace) {
-        deferred.resolve(fp.file.path);
-      } else {
-        deferred.reject();
-      }
-    }
-  });
-  return deferred.promise;
-}
-exports.showPicker = showPicker;
-
-/**
- * Show a save dialog
- * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIFilePicker
- *
- * @param object options
- *        Additional options as specified in showPicker
- *
- * @return promise
- *         A promise that is resolved when the save dialog has closed
- */
-function showSave(options) {
-  return showPicker(merge({
-    title: getLocalizedString("projecteditor.selectFileLabel"),
-    mode: Ci.nsIFilePicker.modeSave
-  }, options));
-}
-exports.showSave = showSave;
-
-/**
- * Show a file open dialog
- *
- * @param object options
- *        Additional options as specified in showPicker
- *
- * @return promise
- *         A promise that is resolved when the file has been opened
- */
-function showOpen(options) {
-  return showPicker(merge({
-    title: getLocalizedString("projecteditor.openFileLabel"),
-    mode: Ci.nsIFilePicker.modeOpen
-  }, options));
-}
-exports.showOpen = showOpen;
-
-/**
- * Show a folder open dialog
- *
- * @param object options
- *        Additional options as specified in showPicker
- *
- * @return promise
- *         A promise that is resolved when the folder has been opened
- */
-function showOpenFolder(options) {
-  return showPicker(merge({
-    title: getLocalizedString("projecteditor.openFolderLabel"),
-    mode: Ci.nsIFilePicker.modeGetFolder
-  }, options));
-}
-exports.showOpenFolder = showOpenFolder;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/helpers/l10n.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/* -*- 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";
-
-/**
- * This file contains helper functions for internationalizing projecteditor strings
- */
-
-const { LocalizationHelper } = require("devtools/shared/l10n");
-const ITCHPAD_STRINGS_URI = "devtools/client/locales/projecteditor.properties";
-const L10N = new LocalizationHelper(ITCHPAD_STRINGS_URI);
-
-function getLocalizedString(name) {
-  try {
-    return L10N.getStr(name);
-  } catch (ex) {
-    console.log("Error reading '" + name + "'");
-    throw new Error("l10n error with " + name);
-  }
-}
-
-exports.getLocalizedString = getLocalizedString;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/helpers/moz.build
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- Mode: python; 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(
-    'event.js',
-    'file-picker.js',
-    'l10n.js',
-    'prompts.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/helpers/prompts.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
-
-/**
- * This file contains helper functions for showing user prompts.
- * See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPromptService
- */
-
-const { Cu, Cc, Ci } = require("chrome");
-const { getLocalizedString } = require("devtools/client/projecteditor/lib/helpers/l10n");
-const prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"]
-                        .getService(Ci.nsIPromptService);
-
-/**
- * Show a prompt.
- *
- * @param string title
- *               The title to the dialog
- * @param string message
- *               The message to display
- *
- * @return bool
- *         Whether the user has confirmed the action
- */
-function confirm(title, message) {
-  var result = prompts.confirm(null, title || "Title of this Dialog", message || "Are you sure?");
-  return result;
-}
-exports.confirm = confirm;
-
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/helpers/readdir.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/* -*- 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/. */
-
-importScripts("resource://gre/modules/osfile.jsm");
-
-/**
- * This file is meant to be loaded in a worker using:
- *   new ChromeWorker("chrome://devtools/content/projecteditor/lib/helpers/readdir.js");
- *
- * Read a local directory inside of a web woker
- *
- * @param {string} path
- *        window to inspect
- * @param {RegExp|string} ignore
- *        A pattern to ignore certain files.  This is
- *        called with file.name.match(ignore).
- * @param {Number} maxDepth
- *        How many directories to recurse before stopping.
- *        Directories with depth > maxDepth will be ignored.
- */
-function readDir(path, ignore, maxDepth = Infinity) {
-  let ret = {};
-
-  let set = new Set();
-
-  let info = OS.File.stat(path);
-  set.add({
-    path: path,
-    name: info.name,
-    isDir: info.isDir,
-    isSymLink: info.isSymLink,
-    depth: 0
-  });
-
-  for (let info of set) {
-    let children = [];
-
-    if (info.isDir && !info.isSymLink) {
-      if (info.depth > maxDepth) {
-        continue;
-      }
-
-      let iterator = new OS.File.DirectoryIterator(info.path);
-      try {
-        for (let child in iterator) {
-          if (ignore && child.name.match(ignore)) {
-            continue;
-          }
-
-          children.push(child.path);
-          set.add({
-            path: child.path,
-            name: child.name,
-            isDir: child.isDir,
-            isSymLink: child.isSymLink,
-            depth: info.depth + 1
-          });
-        }
-      } finally {
-        iterator.close();
-      }
-    }
-
-    ret[info.path] = {
-      name: info.name,
-      isDir: info.isDir,
-      isSymLink: info.isSymLink,
-      depth: info.depth,
-      children: children,
-    };
-  }
-
-  return ret;
-}
-
-onmessage = function (event) {
-  try {
-    let {path, ignore, depth} = event.data;
-    let message = readDir(path, ignore, depth);
-    postMessage(message);
-  } catch (ex) {
-    console.log(ex);
-  }
-};
-
-
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/moz.build
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- Mode: python; 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/.
-
-DIRS += [
-    'helpers',
-    'plugins',
-    'stores',
-]
-
-DevToolsModules(
-    'editors.js',
-    'project.js',
-    'projecteditor.js',
-    'shells.js',
-    'tree.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/app-manager/app-project-editor.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const promise = require("promise");
-const { ItchEditor } = require("devtools/client/projecteditor/lib/editors");
-
-var AppProjectEditor = Class({
-  extends: ItchEditor,
-
-  hidesToolbar: true,
-
-  initialize: function (host) {
-    ItchEditor.prototype.initialize.apply(this, arguments);
-    this.appended = promise.resolve();
-    this.host = host;
-    this.label = "app-manager";
-  },
-
-  destroy: function () {
-    this.elt.remove();
-    this.elt = null;
-  },
-
-  load: function (resource) {
-    let {appManagerOpts} = this.host.project;
-
-    // Only load the frame the first time it is selected
-    if (!this.iframe || this.iframe.getAttribute("src") !== appManagerOpts.projectOverviewURL) {
-
-      this.elt.textContent = "";
-      let iframe = this.iframe = this.elt.ownerDocument.createElement("iframe");
-      let iframeLoaded = this.iframeLoaded = promise.defer();
-
-      iframe.addEventListener("load", function () {
-        iframeLoaded.resolve();
-      }, {once: true});
-
-      iframe.setAttribute("flex", "1");
-      iframe.setAttribute("src", appManagerOpts.projectOverviewURL);
-      this.elt.appendChild(iframe);
-
-    }
-
-    promise.all([this.iframeLoaded.promise, this.appended]).then(() => {
-      this.emit("load");
-    });
-  }
-});
-
-exports.AppProjectEditor = AppProjectEditor;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/app-manager/moz.build
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- Mode: python; 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(
-    'app-project-editor.js',
-    'plugin.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/app-manager/plugin.js
+++ /dev/null
@@ -1,77 +0,0 @@
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const { EventTarget } = require("sdk/event/target");
-const { emit } = require("sdk/event/core");
-const promise = require("promise");
-var { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-const { AppProjectEditor } = require("./app-project-editor");
-const OPTION_URL = "chrome://devtools/skin/images/tool-options.svg";
-const Services = require("Services");
-const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
-
-var AppManagerRenderer = Class({
-  extends: Plugin,
-
-  isAppManagerProject: function () {
-    return !!this.host.project.appManagerOpts;
-  },
-  editorForResource: function (resource) {
-    if (!resource.parent && this.isAppManagerProject()) {
-      return AppProjectEditor;
-    }
-  },
-  getUI: function (parent) {
-    let doc = parent.ownerDocument;
-    if (parent.childElementCount == 0) {
-      let image = doc.createElement("image");
-      let optionImage = doc.createElement("image");
-      let flexElement = doc.createElement("div");
-      let nameLabel = doc.createElement("span");
-      let statusElement = doc.createElement("div");
-
-      image.className = "project-image";
-      optionImage.className = "project-options";
-      optionImage.setAttribute("src", OPTION_URL);
-      nameLabel.className = "project-name-label";
-      statusElement.className = "project-status";
-      flexElement.className = "project-flex";
-
-      parent.appendChild(image);
-      parent.appendChild(nameLabel);
-      parent.appendChild(flexElement);
-      parent.appendChild(statusElement);
-      parent.appendChild(optionImage);
-    }
-
-    return {
-      image: parent.querySelector(".project-image"),
-      nameLabel: parent.querySelector(".project-name-label"),
-      statusElement: parent.querySelector(".project-status")
-    };
-  },
-  onAnnotate: function (resource, editor, elt) {
-    if (resource.parent || !this.isAppManagerProject()) {
-      return;
-    }
-
-    let {appManagerOpts} = this.host.project;
-    let doc = elt.ownerDocument;
-
-    let {image, nameLabel, statusElement} = this.getUI(elt);
-    let name = appManagerOpts.name || resource.basename;
-    let url = appManagerOpts.iconUrl || "icon-sample.png";
-    let status = appManagerOpts.validationStatus || "unknown";
-    let tooltip = Strings.formatStringFromName("status_tooltip",
-      [Strings.GetStringFromName("status_" + status)], 1);
-
-    nameLabel.textContent = name;
-    image.setAttribute("src", url);
-    statusElement.setAttribute("status", status);
-    statusElement.setAttribute("tooltiptext", tooltip);
-
-    return true;
-  }
-});
-
-exports.AppManagerRenderer = AppManagerRenderer;
-registerPlugin(AppManagerRenderer);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/core.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- 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/. */
-
-// This is the core plugin API.
-
-const { Class } = require("sdk/core/heritage");
-
-var Plugin = Class({
-  initialize: function (host) {
-    this.host = host;
-    this.init(host);
-  },
-
-  destroy: function (host) { },
-
-  init: function (host) {},
-
-  showForCategories: function (elt, categories) {
-    this._showFor = this._showFor || [];
-    let set = new Set(categories);
-    this._showFor.push({
-      elt: elt,
-      categories: new Set(categories)
-    });
-    if (this.host.currentEditor) {
-      this.onEditorActivated(this.host.currentEditor);
-    } else {
-      elt.classList.add("plugin-hidden");
-    }
-  },
-
-  priv: function (item) {
-    if (!this._privData) {
-      this._privData = new WeakMap();
-    }
-    if (!this._privData.has(item)) {
-      this._privData.set(item, {});
-    }
-    return this._privData.get(item);
-  },
-  onTreeSelected: function (resource) {},
-
-
-  // Editor state lifetime...
-  onEditorCreated: function (editor) {},
-  onEditorDestroyed: function (editor) {},
-
-  onEditorActivated: function (editor) {
-    if (this._showFor) {
-      let category = editor.category;
-      for (let item of this._showFor) {
-        if (item.categories.has(category)) {
-          item.elt.classList.remove("plugin-hidden");
-        } else {
-          item.elt.classList.add("plugin-hidden");
-        }
-      }
-    }
-  },
-  onEditorDeactivated: function (editor) {
-    if (this._showFor) {
-      for (let item of this._showFor) {
-        item.elt.classList.add("plugin-hidden");
-      }
-    }
-  },
-
-  onEditorLoad: function (editor) {},
-  onEditorSave: function (editor) {},
-  onEditorChange: function (editor) {},
-  onEditorCursorActivity: function (editor) {},
-});
-exports.Plugin = Plugin;
-
-function registerPlugin(constr) {
-  exports.registeredPlugins.push(constr);
-}
-exports.registerPlugin = registerPlugin;
-
-exports.registeredPlugins = [];
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/delete/delete.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- 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/. */
-
-const { Class } = require("sdk/core/heritage");
-const { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-const { confirm } = require("devtools/client/projecteditor/lib/helpers/prompts");
-const { getLocalizedString } = require("devtools/client/projecteditor/lib/helpers/l10n");
-
-var DeletePlugin = Class({
-  extends: Plugin,
-  shouldConfirm: true,
-
-  init: function (host) {
-    this.host.addCommand(this, {
-      id: "cmd-delete"
-    });
-    this.contextMenuItem = this.host.createMenuItem({
-      parent: this.host.contextMenuPopup,
-      label: getLocalizedString("projecteditor.deleteLabel"),
-      command: "cmd-delete"
-    });
-  },
-
-  confirmDelete: function (resource) {
-    let deletePromptMessage = resource.isDir ?
-      getLocalizedString("projecteditor.deleteFolderPromptMessage") :
-      getLocalizedString("projecteditor.deleteFilePromptMessage");
-    return !this.shouldConfirm || confirm(
-      getLocalizedString("projecteditor.deletePromptTitle"),
-      deletePromptMessage
-    );
-  },
-
-  onContextMenuOpen: function (resource) {
-    // Do not allow deletion of the top level items in the tree.  In the
-    // case of the Web IDE in particular this can leave the UI in a weird
-    // state. If we'd like to add ability to delete the project folder from
-    // the tree in the future, then the UI could be cleaned up by listening
-    // to the ProjectTree's "resource-removed" event.
-    if (!resource.parent) {
-      this.contextMenuItem.setAttribute("hidden", "true");
-    } else {
-      this.contextMenuItem.removeAttribute("hidden");
-    }
-  },
-
-  onCommand: function (cmd) {
-    if (cmd === "cmd-delete") {
-      let tree = this.host.projectTree;
-      let resource = tree.getSelectedResource();
-
-      if (!this.confirmDelete(resource)) {
-        return;
-      }
-
-      resource.delete().then(() => {
-        this.host.project.refresh();
-      });
-    }
-  }
-});
-
-exports.DeletePlugin = DeletePlugin;
-registerPlugin(DeletePlugin);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/delete/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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(
-    'delete.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/dirty/dirty.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- 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/. */
-
-const { Class } = require("sdk/core/heritage");
-const { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-const { emit } = require("sdk/event/core");
-
-var DirtyPlugin = Class({
-  extends: Plugin,
-
-  onEditorSave: function (editor) { this.onEditorChange(editor); },
-  onEditorLoad: function (editor) { this.onEditorChange(editor); },
-
-  onEditorChange: function (editor) {
-    // Only run on a TextEditor
-    if (!editor || !editor.editor) {
-      return;
-    }
-
-    // Dont' force a refresh unless the dirty state has changed...
-    let priv = this.priv(editor);
-    let clean = editor.isClean();
-    if (priv.isClean !== clean) {
-      let resource = editor.shell.resource;
-      emit(resource, "label-change", resource);
-      priv.isClean = clean;
-    }
-  },
-
-  onAnnotate: function (resource, editor, elt) {
-    // Only run on a TextEditor
-    if (!editor || !editor.editor) {
-      return;
-    }
-
-    if (!editor.isClean()) {
-      elt.textContent = "*" + resource.displayName;
-      return true;
-    }
-  }
-});
-exports.DirtyPlugin = DirtyPlugin;
-
-registerPlugin(DirtyPlugin);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/dirty/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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(
-    'dirty.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/image-view/image-editor.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const promise = require("promise");
-const { ItchEditor } = require("devtools/client/projecteditor/lib/editors");
-
-var ImageEditor = Class({
-  extends: ItchEditor,
-
-  initialize: function () {
-    ItchEditor.prototype.initialize.apply(this, arguments);
-    this.label = "image";
-    this.appended = promise.resolve();
-  },
-
-  load: function (resource) {
-    this.elt.innerHTML = "";
-    let image = this.image = this.doc.createElement("image");
-    image.className = "editor-image";
-    image.setAttribute("src", resource.uri);
-
-    let box1 = this.doc.createElement("box");
-    box1.appendChild(image);
-
-    let box2 = this.doc.createElement("box");
-    box2.setAttribute("flex", 1);
-
-    this.elt.appendChild(box1);
-    this.elt.appendChild(box2);
-
-    this.appended.then(() => {
-      this.emit("load");
-    });
-  },
-
-  destroy: function () {
-    if (this.image) {
-      this.image.remove();
-      this.image = null;
-    }
-  }
-
-});
-
-exports.ImageEditor = ImageEditor;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/image-view/moz.build
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- Mode: python; 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(
-    'image-editor.js',
-    'plugin.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/image-view/plugin.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const promise = require("promise");
-const { ImageEditor } = require("./image-editor");
-const { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-
-var ImageEditorPlugin = Class({
-  extends: Plugin,
-
-  editorForResource: function (node) {
-    if (node.contentCategory === "image") {
-      return ImageEditor;
-    }
-  },
-
-  init: function (host) {
-
-  }
-});
-
-exports.ImageEditorPlugin = ImageEditorPlugin;
-registerPlugin(ImageEditorPlugin);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/logging/logging.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- 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/. */
-
-var { Class } = require("sdk/core/heritage");
-var { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-
-var LoggingPlugin = Class({
-  extends: Plugin,
-
-  // Editor state lifetime...
-  onEditorCreated: function (editor) { console.log("editor created: " + editor); },
-  onEditorDestroyed: function (editor) { console.log("editor destroyed: " + editor);},
-
-  onEditorSave: function (editor) { console.log("editor saved: " + editor); },
-  onEditorLoad: function (editor) { console.log("editor loaded: " + editor); },
-
-  onEditorActivated: function (editor) { console.log("editor activated: " + editor);},
-  onEditorDeactivated: function (editor) { console.log("editor deactivated: " + editor);},
-
-  onEditorChange: function (editor) { console.log("editor changed: " + editor);},
-
-  onCommand: function (cmd) { console.log("Command: " + cmd); }
-});
-exports.LoggingPlugin = LoggingPlugin;
-
-registerPlugin(LoggingPlugin);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/logging/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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(
-    'logging.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/moz.build
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- Mode: python; 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/.
-
-DIRS += [
-    'app-manager',
-    'delete',
-    'dirty',
-    'image-view',
-    'logging',
-    'new',
-    'rename',
-    'save',
-    'status-bar',
-]
-
-DevToolsModules(
-    'core.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/new/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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(
-    'new.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/new/new.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- 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/. */
-
-const { Class } = require("sdk/core/heritage");
-const { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-const { getLocalizedString } = require("devtools/client/projecteditor/lib/helpers/l10n");
-
-// Handles the new command.
-var NewFile = Class({
-  extends: Plugin,
-
-  init: function () {
-    this.command = this.host.addCommand(this, {
-      id: "cmd-new",
-      key: getLocalizedString("projecteditor.new.commandkey"),
-      modifiers: "accel"
-    });
-    this.host.createMenuItem({
-      parent: this.host.fileMenuPopup,
-      label: getLocalizedString("projecteditor.newLabel"),
-      command: "cmd-new",
-      key: "key_cmd-new"
-    });
-    this.host.createMenuItem({
-      parent: this.host.contextMenuPopup,
-      label: getLocalizedString("projecteditor.newLabel"),
-      command: "cmd-new"
-    });
-  },
-
-  onCommand: function (cmd) {
-    if (cmd === "cmd-new") {
-      let tree = this.host.projectTree;
-      let resource = tree.getSelectedResource();
-      parent = resource.isDir ? resource : resource.parent;
-      sibling = resource.isDir ? null : resource;
-
-      if (!("createChild" in parent)) {
-        return;
-      }
-
-      let extension = sibling ? sibling.contentCategory : parent.store.defaultCategory;
-      let template = "untitled{1}." + extension;
-      let name = this.suggestName(parent, template);
-
-      tree.promptNew(name, parent, sibling).then(name => {
-
-        // XXX: sanitize bad file names.
-
-        // If the name is already taken, just add/increment a number.
-        if (parent.hasChild(name)) {
-          let matches = name.match(/([^\d.]*)(\d*)([^.]*)(.*)/);
-          template = matches[1] + "{1}" + matches[3] + matches[4];
-          name = this.suggestName(parent, template, parseInt(matches[2]) || 2);
-        }
-
-        return parent.createChild(name);
-      }).then(resource => {
-        tree.selectResource(resource);
-        this.host.currentEditor.focus();
-      }).then(null, console.error);
-    }
-  },
-
-  suggestName: function (parent, template, start = 1) {
-    let i = start;
-    let name;
-    do {
-      name = template.replace("\{1\}", i === 1 ? "" : i);
-      i++;
-    } while (parent.hasChild(name));
-
-    return name;
-  }
-});
-exports.NewFile = NewFile;
-registerPlugin(NewFile);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/rename/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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(
-    'rename.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/rename/rename.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- 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/. */
-
-const { Class } = require("sdk/core/heritage");
-const { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-const { getLocalizedString } = require("devtools/client/projecteditor/lib/helpers/l10n");
-
-var RenamePlugin = Class({
-  extends: Plugin,
-
-  init: function (host) {
-    this.host.addCommand(this, {
-      id: "cmd-rename"
-    });
-    this.contextMenuItem = this.host.createMenuItem({
-      parent: this.host.contextMenuPopup,
-      label: getLocalizedString("projecteditor.renameLabel"),
-      command: "cmd-rename"
-    });
-  },
-
-  onContextMenuOpen: function (resource) {
-    if (resource.isRoot) {
-      this.contextMenuItem.setAttribute("hidden", "true");
-    } else {
-      this.contextMenuItem.removeAttribute("hidden");
-    }
-  },
-
-  onCommand: function (cmd) {
-    if (cmd === "cmd-rename") {
-      let tree = this.host.projectTree;
-      let resource = tree.getSelectedResource();
-      let parent = resource.parent;
-      let oldName = resource.basename;
-
-      tree.promptEdit(oldName, resource).then(name => {
-        if (name === oldName) {
-          return resource;
-        }
-        if (parent.hasChild(name)) {
-          let matches = name.match(/([^\d.]*)(\d*)([^.]*)(.*)/);
-          let template = matches[1] + "{1}" + matches[3] + matches[4];
-          name = this.suggestName(resource, template, parseInt(matches[2]) || 2);
-        }
-        return parent.rename(oldName, name);
-      }).then(resource => {
-        this.host.project.refresh();
-        tree.selectResource(resource);
-        if (!resource.isDir) {
-          this.host.currentEditor.focus();
-        }
-      }).then(null, console.error);
-    }
-  },
-
-  suggestName: function (resource, template, start = 1) {
-    let i = start;
-    let name;
-    let parent = resource.parent;
-    do {
-      name = template.replace("\{1\}", i === 1 ? "" : i);
-      i++;
-    } while (parent.hasChild(name));
-
-    return name;
-  }
-});
-
-exports.RenamePlugin = RenamePlugin;
-registerPlugin(RenamePlugin);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/save/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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(
-    'save.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/save/save.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/* -*- 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/. */
-
-const { Class } = require("sdk/core/heritage");
-const { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-const picker = require("devtools/client/projecteditor/lib/helpers/file-picker");
-const { getLocalizedString } = require("devtools/client/projecteditor/lib/helpers/l10n");
-
-// Handles the save command.
-var SavePlugin = Class({
-  extends: Plugin,
-
-  init: function (host) {
-
-    this.host.addCommand(this, {
-      id: "cmd-save",
-      key: getLocalizedString("projecteditor.save.commandkey"),
-      modifiers: "accel"
-    });
-    this.host.addCommand(this, {
-      id: "cmd-saveas",
-      key: getLocalizedString("projecteditor.save.commandkey"),
-      modifiers: "accel shift"
-    });
-    this.host.createMenuItem({
-      parent: this.host.fileMenuPopup,
-      label: getLocalizedString("projecteditor.saveLabel"),
-      command: "cmd-save",
-      key: "key_cmd-save"
-    });
-    this.host.createMenuItem({
-      parent: this.host.fileMenuPopup,
-      label: getLocalizedString("projecteditor.saveAsLabel"),
-      command: "cmd-saveas",
-      key: "key_cmd-saveas"
-    });
-  },
-
-  isCommandEnabled: function (cmd) {
-    let currentEditor = this.host.currentEditor;
-    return currentEditor.isEditable;
-  },
-
-  onCommand: function (cmd) {
-    if (cmd === "cmd-save") {
-      this.onEditorSaveRequested();
-    } else if (cmd === "cmd-saveas") {
-      this.saveAs();
-    }
-  },
-
-  saveAs: function () {
-    let editor = this.host.currentEditor;
-    let project = this.host.resourceFor(editor);
-
-    let resource;
-    picker.showSave({
-      window: this.host.window,
-      directory: project && project.parent ? project.parent.path : null,
-      defaultName: project ? project.basename : null,
-    }).then(path => {
-      return this.createResource(path);
-    }).then(res => {
-      resource = res;
-      return this.saveResource(editor, resource);
-    }).then(() => {
-      this.host.openResource(resource);
-    }).then(null, console.error);
-  },
-
-  onEditorSaveRequested: function () {
-    let editor = this.host.currentEditor;
-    let resource = this.host.resourceFor(editor);
-    if (!resource) {
-      return this.saveAs();
-    }
-
-    return this.saveResource(editor, resource);
-  },
-
-  createResource: function (path) {
-    return this.host.project.resourceFor(path, { create: true });
-  },
-
-  saveResource: function (editor, resource) {
-    return editor.save(resource);
-  }
-});
-exports.SavePlugin = SavePlugin;
-registerPlugin(SavePlugin);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/status-bar/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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(
-    'plugin.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/plugins/status-bar/plugin.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const promise = require("promise");
-const { registerPlugin, Plugin } = require("devtools/client/projecteditor/lib/plugins/core");
-
-/**
- * Print information about the currently opened file
- * and the state of the current editor
- */
-var StatusBarPlugin = Class({
-  extends: Plugin,
-
-  init: function () {
-    this.box = this.host.createElement("hbox", {
-      parent: "#projecteditor-toolbar-bottom"
-    });
-
-    this.activeMode = this.host.createElement("label", {
-      parent: this.box,
-      class: "projecteditor-basic-display"
-    });
-
-    this.cursorPosition = this.host.createElement("label", {
-      parent: this.box,
-      class: "projecteditor-basic-display"
-    });
-
-    this.fileLabel = this.host.createElement("label", {
-      parent: "#plugin-toolbar-left",
-      class: "projecteditor-file-label"
-    });
-  },
-
-  destroy: function () {
-  },
-
-  /**
-   * Print information about the current state of the editor
-   *
-   * @param Editor editor
-   */
-  render: function (editor, resource) {
-    if (!resource || resource.isDir) {
-      this.fileLabel.textContent = "";
-      this.cursorPosition.value = "";
-      return;
-    }
-
-    this.fileLabel.textContent = resource.basename;
-    this.activeMode.value = editor.toString();
-    if (editor.editor) {
-      let cursorStart = editor.editor.getCursor("start");
-      let cursorEnd = editor.editor.getCursor("end");
-      if (cursorStart.line === cursorEnd.line && cursorStart.ch === cursorEnd.ch) {
-        this.cursorPosition.value = cursorStart.line + " " + cursorStart.ch;
-      } else {
-        this.cursorPosition.value = cursorStart.line + " " + cursorStart.ch + " | " +
-                                    cursorEnd.line + " " + cursorEnd.ch;
-      }
-    } else {
-      this.cursorPosition.value = "";
-    }
-  },
-
-
-  /**
-   * Print the current file name
-   *
-   * @param Resource resource
-   */
-  onTreeSelected: function (resource) {
-    if (!resource || resource.isDir) {
-      this.fileLabel.textContent = "";
-      return;
-    }
-    this.fileLabel.textContent = resource.basename;
-  },
-
-  onEditorDeactivated: function (editor) {
-    this.fileLabel.textContent = "";
-    this.cursorPosition.value = "";
-  },
-
-  onEditorChange: function (editor, resource) {
-    this.render(editor, resource);
-  },
-
-  onEditorCursorActivity: function (editor, resource) {
-    this.render(editor, resource);
-  },
-
-  onEditorActivated: function (editor, resource) {
-    this.render(editor, resource);
-  },
-
-});
-
-exports.StatusBarPlugin = StatusBarPlugin;
-registerPlugin(StatusBarPlugin);
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/project.js
+++ /dev/null
@@ -1,246 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const { EventTarget } = require("sdk/event/target");
-const { emit } = require("sdk/event/core");
-const { scope, on, forget } = require("devtools/client/projecteditor/lib/helpers/event");
-const prefs = require("sdk/preferences/service");
-const { LocalStore } = require("devtools/client/projecteditor/lib/stores/local");
-const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
-const { Task } = require("devtools/shared/task");
-const promise = require("promise");
-const { TextEncoder, TextDecoder } = require("sdk/io/buffer");
-const url = require("sdk/url");
-
-const gDecoder = new TextDecoder();
-const gEncoder = new TextEncoder();
-
-/**
- * A Project keeps track of the opened folders using LocalStore
- * objects.  Resources are generally requested from the project,
- * even though the Store is actually keeping track of them.
- *
- *
- * This object emits the following events:
- *   - "refresh-complete": After all stores have been refreshed from disk.
- *   - "store-added": When a store has been added to the project.
- *   - "store-removed": When a store has been removed from the project.
- *   - "resource-added": When a resource has been added to one of the stores.
- *   - "resource-removed": When a resource has been removed from one of the stores.
- */
-var Project = Class({
-  extends: EventTarget,
-
-  /**
-   * Intialize the Project.
-   *
-   * @param Object options
-   *               Options to be passed into Project.load function
-   */
-  initialize: function (options) {
-    this.localStores = new Map();
-
-    this.load(options);
-  },
-
-  destroy: function () {
-    // We are removing the store because the project never gets persisted.
-    // There may need to be separate destroy functionality that doesn't remove
-    // from project if this is saved to DB.
-    this.removeAllStores();
-  },
-
-  toString: function () {
-    return "[Project] " + this.name;
-  },
-
-  /**
-   * Load a project given metadata about it.
-   *
-   * @param Object options
-   *               Information about the project, containing:
-   *                id: An ID (currently unused, but could be used for saving)
-   *                name: The display name of the project
-   *                directories: An array of path strings to load
-   */
-  load: function (options) {
-    this.id = options.id;
-    this.name = options.name || "Untitled";
-
-    let paths = new Set(options.directories.map(name => OS.Path.normalize(name)));
-
-    for (let [path, store] of this.localStores) {
-      if (!paths.has(path)) {
-        this.removePath(path);
-      }
-    }
-
-    for (let path of paths) {
-      this.addPath(path);
-    }
-  },
-
-  /**
-   * Refresh all project stores from disk
-   *
-   * @returns Promise
-   *          A promise that resolves when everything has been refreshed.
-   */
-  refresh: function () {
-    return Task.spawn(function* () {
-      for (let [path, store] of this.localStores) {
-        yield store.refresh();
-      }
-      emit(this, "refresh-complete");
-    }.bind(this));
-  },
-
-
-  /**
-   * Fetch a resource from the backing storage system for the store.
-   *
-   * @param string path
-   *               The path to fetch
-   * @param Object options
-   *               "create": bool indicating whether to create a file if it does not exist.
-   * @returns Promise
-   *          A promise that resolves with the Resource.
-   */
-  resourceFor: function (path, options) {
-    let store = this.storeContaining(path);
-    return store.resourceFor(path, options);
-  },
-
-  /**
-   * Get every resource used inside of the project.
-   *
-   * @returns Array<Resource>
-   *          A list of all Resources in all Stores.
-   */
-  allResources: function () {
-    let resources = [];
-    for (let store of this.allStores()) {
-      resources = resources.concat(store.allResources());
-    }
-    return resources;
-  },
-
-  /**
-   * Get every Path used inside of the project.
-   *
-   * @returns generator-iterator<Store>
-   *          A list of all Stores
-   */
-  allStores: function* () {
-    for (let [path, store] of this.localStores) {
-      yield store;
-    }
-  },
-
-  /**
-   * Get every file path used inside of the project.
-   *
-   * @returns Array<string>
-   *          A list of all file paths
-   */
-  allPaths: function () {
-    return [...this.localStores.keys()];
-  },
-
-  /**
-   * Get the store that contains a path.
-   *
-   * @returns Store
-   *          The store, if any.  Will return null if no store
-   *          contains the given path.
-   */
-  storeContaining: function (path) {
-    let containingStore = null;
-    for (let store of this.allStores()) {
-      if (store.contains(path)) {
-        // With nested projects, the final containing store will be returned.
-        containingStore = store;
-      }
-    }
-    return containingStore;
-  },
-
-  /**
-   * Add a store at the current path.  If a store already exists
-   * for this path, then return it.
-   *
-   * @param string path
-   * @returns LocalStore
-   */
-  addPath: function (path) {
-    if (!this.localStores.has(path)) {
-      this.addLocalStore(new LocalStore(path));
-    }
-    return this.localStores.get(path);
-  },
-
-  /**
-   * Remove a store for a given path.
-   *
-   * @param string path
-   */
-  removePath: function (path) {
-    this.removeLocalStore(this.localStores.get(path));
-  },
-
-
-  /**
-   * Add the given Store to the project.
-   * Fires a 'store-added' event on the project.
-   *
-   * @param Store store
-   */
-  addLocalStore: function (store) {
-    store.canPair = true;
-    this.localStores.set(store.path, store);
-
-    // Originally StoreCollection.addStore
-    on(this, store, "resource-added", (resource) => {
-      emit(this, "resource-added", resource);
-    });
-    on(this, store, "resource-removed", (resource) => {
-      emit(this, "resource-removed", resource);
-    });
-
-    emit(this, "store-added", store);
-  },
-
-
-  /**
-   * Remove all of the Stores belonging to the project.
-   */
-  removeAllStores: function () {
-    for (let store of this.allStores()) {
-      this.removeLocalStore(store);
-    }
-  },
-
-  /**
-   * Remove the given Store from the project.
-   * Fires a 'store-removed' event on the project.
-   *
-   * @param Store store
-   */
-  removeLocalStore: function (store) {
-    // XXX: tree selection should be reset if active element is affected by
-    // the store being removed
-    if (store) {
-      this.localStores.delete(store.path);
-      forget(this, store);
-      emit(this, "store-removed", store);
-      store.destroy();
-    }
-  }
-});
-
-exports.Project = Project;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/projecteditor.js
+++ /dev/null
@@ -1,816 +0,0 @@
-/* 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 { Class } = require("sdk/core/heritage");
-const { Project } = require("devtools/client/projecteditor/lib/project");
-const { ProjectTreeView } = require("devtools/client/projecteditor/lib/tree");
-const { ShellDeck } = require("devtools/client/projecteditor/lib/shells");
-const { Resource } = require("devtools/client/projecteditor/lib/stores/resource");
-const { registeredPlugins } = require("devtools/client/projecteditor/lib/plugins/core");
-const { EventTarget } = require("sdk/event/target");
-const { on, forget } = require("devtools/client/projecteditor/lib/helpers/event");
-const { emit } = require("sdk/event/core");
-const { merge } = require("sdk/util/object");
-const promise = require("promise");
-const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
-const { DOMHelpers } = require("resource://devtools/client/shared/DOMHelpers.jsm");
-const Services = require("Services");
-const { Task } = require("devtools/shared/task");
-const ITCHPAD_URL = "chrome://devtools/content/projecteditor/chrome/content/projecteditor.xul";
-const { confirm } = require("devtools/client/projecteditor/lib/helpers/prompts");
-const { getLocalizedString } = require("devtools/client/projecteditor/lib/helpers/l10n");
-
-// Enabled Plugins
-require("devtools/client/projecteditor/lib/plugins/dirty/dirty");
-require("devtools/client/projecteditor/lib/plugins/delete/delete");
-require("devtools/client/projecteditor/lib/plugins/new/new");
-require("devtools/client/projecteditor/lib/plugins/rename/rename");
-require("devtools/client/projecteditor/lib/plugins/save/save");
-require("devtools/client/projecteditor/lib/plugins/image-view/plugin");
-require("devtools/client/projecteditor/lib/plugins/app-manager/plugin");
-require("devtools/client/projecteditor/lib/plugins/status-bar/plugin");
-
-// Uncomment to enable logging.
-// require("devtools/client/projecteditor/lib/plugins/logging/logging");
-
-/**
- * This is the main class tying together an instance of the ProjectEditor.
- * The frontend is contained inside of this.iframe, which loads projecteditor.xul.
- *
- * Usage:
- *   let projecteditor = new ProjectEditor(frame);
- *   projecteditor.loaded.then((projecteditor) => {
- *      // Ready to use.
- *   });
- *
- * Responsible for maintaining:
- *   - The list of Plugins for this instance.
- *   - The ShellDeck, which includes all Shells for opened Resources
- *   -- Shells take in a Resource, and construct the appropriate Editor
- *   - The Project, which includes all Stores for this instance
- *   -- Stores manage all Resources starting from a root directory
- *   --- Resources are a representation of a file on disk
- *   - The ProjectTreeView that builds the UI for interacting with the
- *     project.
- *
- * This object emits the following events:
- *   - "onEditorDestroyed": When editor is destroyed
- *   - "onEditorSave": When editor is saved
- *   - "onEditorLoad": When editor is loaded
- *   - "onEditorActivated": When editor is activated
- *   - "onEditorChange": When editor is changed
- *   - "onEditorCursorActivity": When there is cursor activity in a text editor
- *   - "onCommand": When a command happens
- *   - "onEditorDestroyed": When editor is destroyed
- *   - "onContextMenuOpen": When the context menu is opened on the project tree
- *
- * The events can be bound like so:
- *   projecteditor.on("onEditorCreated", (editor) => { });
- */
-var ProjectEditor = Class({
-  extends: EventTarget,
-
-  /**
-   * Initialize ProjectEditor, and load into an iframe if specified.
-   *
-   * @param Iframe iframe
-   *        The iframe to inject the DOM into.  If this is not
-   *        specified, then this.load(frame) will need to be called
-   *        before accessing ProjectEditor.
-   * @param Object options
-   *         - menubar: a <menubar> element to inject menus into
-   *         - menuindex: Integer child index to insert menus
-   */
-  initialize: function (iframe, options = {}) {
-    this._onTreeSelected = this._onTreeSelected.bind(this);
-    this._onTreeResourceRemoved = this._onTreeResourceRemoved.bind(this);
-    this._onEditorCreated = this._onEditorCreated.bind(this);
-    this._onEditorActivated = this._onEditorActivated.bind(this);
-    this._onEditorDeactivated = this._onEditorDeactivated.bind(this);
-    this._updateMenuItems = this._updateMenuItems.bind(this);
-    this._updateContextMenuItems = this._updateContextMenuItems.bind(this);
-    this.destroy = this.destroy.bind(this);
-    this.menubar = options.menubar || null;
-    this.menuindex = options.menuindex || null;
-    this._menuEnabled = true;
-    this._destroyed = false;
-    this._loaded = false;
-    this._pluginCommands = new Map();
-    if (iframe) {
-      this.load(iframe);
-    }
-  },
-
-  /**
-   * Load the instance inside of a specified iframe.
-   * This can be called more than once, and it will return the promise
-   * from the first call.
-   *
-   * @param Iframe iframe
-   *        The iframe to inject the projecteditor DOM into
-   * @returns Promise
-   *          A promise that is resolved once the iframe has been
-   *          loaded.
-   */
-  load: function (iframe) {
-    if (this.loaded) {
-      return this.loaded;
-    }
-
-    let deferred = promise.defer();
-    this.loaded = deferred.promise;
-    this.iframe = iframe;
-
-    let domReady = () => {
-      if (this._destroyed) {
-        deferred.reject("Error: ProjectEditor has been destroyed before loading");
-        return;
-      }
-      this._onLoad();
-      this._loaded = true;
-      deferred.resolve(this);
-    };
-
-    let domHelper = new DOMHelpers(this.iframe.contentWindow);
-    domHelper.onceDOMReady(domReady);
-
-    this.iframe.setAttribute("src", ITCHPAD_URL);
-
-    return this.loaded;
-  },
-
-  /**
-   * Build the projecteditor DOM inside of this.iframe.
-   */
-  _onLoad: function () {
-    this.document = this.iframe.contentDocument;
-    this.window = this.iframe.contentWindow;
-
-    this._initCommands();
-    this._buildMenubar();
-    this._buildSidebar();
-
-    this.window.addEventListener("unload", this.destroy);
-
-    // Editor management
-    this.shells = new ShellDeck(this, this.document);
-    this.shells.on("editor-created", this._onEditorCreated);
-    this.shells.on("editor-activated", this._onEditorActivated);
-    this.shells.on("editor-deactivated", this._onEditorDeactivated);
-
-    let shellContainer = this.document.querySelector("#shells-deck-container");
-    shellContainer.appendChild(this.shells.elt);
-
-    // We are not allowing preset projects for now - rebuild a fresh one
-    // each time.
-    this.setProject(new Project({
-      id: "",
-      name: "",
-      directories: [],
-      openFiles: []
-    }));
-
-    this._initPlugins();
-  },
-
-  _buildMenubar: function () {
-
-    this.contextMenuPopup = this.document.getElementById("context-menu-popup");
-    this.contextMenuPopup.addEventListener("popupshowing", this._updateContextMenuItems);
-
-    this.textEditorContextMenuPopup = this.document.getElementById("texteditor-context-popup");
-    this.textEditorContextMenuPopup.addEventListener("popupshowing", this._updateMenuItems);
-
-    this.editMenu = this.document.getElementById("edit-menu");
-    this.fileMenu = this.document.getElementById("file-menu");
-
-    this.editMenuPopup = this.document.getElementById("edit-menu-popup");
-    this.fileMenuPopup = this.document.getElementById("file-menu-popup");
-    this.editMenu.addEventListener("popupshowing", this._updateMenuItems);
-    this.fileMenu.addEventListener("popupshowing", this._updateMenuItems);
-
-    if (this.menubar) {
-      let body = this.menubar.ownerDocument.body ||
-                 this.menubar.ownerDocument.querySelector("window");
-      body.appendChild(this.projectEditorCommandset);
-      body.appendChild(this.projectEditorKeyset);
-      body.appendChild(this.editorCommandset);
-      body.appendChild(this.editorKeyset);
-      body.appendChild(this.contextMenuPopup);
-      body.appendChild(this.textEditorContextMenuPopup);
-
-      let index = this.menuindex || 0;
-      this.menubar.insertBefore(this.editMenu, this.menubar.children[index]);
-      this.menubar.insertBefore(this.fileMenu, this.menubar.children[index]);
-    } else {
-      this.document.getElementById("projecteditor-menubar").style.display = "block";
-    }
-
-    // Insert a controller to allow enabling and disabling of menu items.
-    this._commandWindow = this.editorCommandset.ownerDocument.defaultView;
-    this._commandController = getCommandController(this);
-    this._commandWindow.controllers.insertControllerAt(0, this._commandController);
-  },
-
-  /**
-   * Create the project tree sidebar that lists files.
-   */
-  _buildSidebar: function () {
-    this.projectTree = new ProjectTreeView(this.document, {
-      resourceVisible: this.resourceVisible.bind(this),
-      resourceFormatter: this.resourceFormatter.bind(this),
-      contextMenuPopup: this.contextMenuPopup
-    });
-    on(this, this.projectTree, "selection", this._onTreeSelected);
-    on(this, this.projectTree, "resource-removed", this._onTreeResourceRemoved);
-
-    let sourcesBox = this.document.querySelector("#sources > vbox");
-    sourcesBox.appendChild(this.projectTree.elt);
-  },
-
-  /**
-   * Set up listeners for commands to dispatch to all of the plugins
-   */
-  _initCommands: function () {
-
-    this.projectEditorCommandset = this.document.getElementById("projecteditor-commandset");
-    this.projectEditorKeyset = this.document.getElementById("projecteditor-keyset");
-
-    this.editorCommandset = this.document.getElementById("editMenuCommands");
-    this.editorKeyset = this.document.getElementById("editMenuKeys");
-
-    this.projectEditorCommandset.addEventListener("command", (evt) => {
-      evt.stopPropagation();
-      evt.preventDefault();
-      this.pluginDispatch("onCommand", evt.target.id, evt.target);
-    });
-  },
-
-  /**
-   * Initialize each plugin in registeredPlugins
-   */
-  _initPlugins: function () {
-    this._plugins = [];
-
-    for (let plugin of registeredPlugins) {
-      try {
-        this._plugins.push(plugin(this));
-      } catch (ex) {
-        console.exception(ex);
-      }
-    }
-
-    this.pluginDispatch("lateInit");
-  },
-
-  /**
-   * Enable / disable necessary menu items using globalOverlay.js.
-   */
-  _updateMenuItems: function () {
-    let window = this.editMenu.ownerDocument.defaultView;
-    let commands = ["cmd_undo", "cmd_redo", "cmd_delete", "cmd_cut", "cmd_copy", "cmd_paste"];
-    commands.forEach(window.goUpdateCommand);
-
-    for (let c of this._pluginCommands.keys()) {
-      window.goUpdateCommand(c);
-    }
-  },
-
-  /**
-   * Enable / disable necessary context menu items by passing an event
-   * onto plugins.
-   */
-  _updateContextMenuItems: function () {
-    let resource = this.projectTree.getSelectedResource();
-    this.pluginDispatch("onContextMenuOpen", resource);
-  },
-
-  /**
-   * Destroy all objects on the iframe unload event.
-   */
-  destroy: function () {
-    this._destroyed = true;
-
-
-    // If been destroyed before the iframe finished loading, then
-    // the properties below will not exist.
-    if (!this._loaded) {
-      this.iframe.setAttribute("src", "about:blank");
-      return;
-    }
-
-    // Reset the src for the iframe so if it reused for a new ProjectEditor
-    // instance, the load will fire properly.
-    this.window.removeEventListener("unload", this.destroy);
-    this.iframe.setAttribute("src", "about:blank");
-
-    this._plugins.forEach(plugin => { plugin.destroy(); });
-
-    forget(this, this.projectTree);
-    this.projectTree.destroy();
-    this.projectTree = null;
-
-    this.shells.destroy();
-
-    this.projectEditorCommandset.remove();
-    this.projectEditorKeyset.remove();
-    this.editorCommandset.remove();
-    this.editorKeyset.remove();
-    this.contextMenuPopup.remove();
-    this.textEditorContextMenuPopup.remove();
-    this.editMenu.remove();
-    this.fileMenu.remove();
-
-    this._commandWindow.controllers.removeController(this._commandController);
-    this._commandController = null;
-
-    forget(this, this.project);
-    this.project.destroy();
-    this.project = null;
-  },
-
-  /**
-   * Set the current project viewed by the projecteditor.
-   *
-   * @param Project project
-   *        The project to set.
-   */
-  setProject: function (project) {
-    if (this.project) {
-      forget(this, this.project);
-    }
-    this.project = project;
-    this.projectTree.setProject(project);
-
-    // Whenever a store gets removed, clean up any editors that
-    // exist for resources within it.
-    on(this, project, "store-removed", (store) => {
-      store.allResources().forEach((resource) => {
-        this.shells.removeResource(resource);
-      });
-    });
-  },
-
-  /**
-   * Set the current project viewed by the projecteditor to a single path,
-   * used by the app manager.
-   *
-   * @param string path
-   *               The file path to set
-   * @param Object opts
-   *               Custom options used by the project.
-   *                - name: display name for project
-   *                - iconUrl: path to icon for project
-   *                - validationStatus: one of 'unknown|error|warning|valid'
-   *                - projectOverviewURL: path to load for iframe when project
-   *                    is selected in the tree.
-   * @param Promise
-   *        Promise that is resolved once the project is ready to be used.
-   */
-  setProjectToAppPath: function (path, opts = {}) {
-    this.project.appManagerOpts = opts;
-
-    let existingPaths = this.project.allPaths();
-    if (existingPaths.length !== 1 || existingPaths[0] !== path) {
-      // Only fully reset if this is a new path.
-      this.project.removeAllStores();
-      this.project.addPath(path);
-    } else {
-      // Otherwise, just ask for the root to be redrawn
-      let rootResource = this.project.localStores.get(path).root;
-      emit(rootResource, "label-change", rootResource);
-    }
-
-    return this.project.refresh();
-  },
-
-  /**
-   * Open a resource in a particular shell.
-   *
-   * @param Resource resource
-   *                 The file to be opened.
-   */
-  openResource: function (resource) {
-    let shell = this.shells.open(resource);
-    this.projectTree.selectResource(resource);
-    shell.editor.focus();
-  },
-
-  /**
-   * When a node is selected in the tree, open its associated editor.
-   *
-   * @param Resource resource
-   *                 The file that has been selected
-   */
-  _onTreeSelected: function (resource) {
-    // Don't attempt to open a directory that is not the root element.
-    if (resource.isDir && resource.parent) {
-      return;
-    }
-    this.pluginDispatch("onTreeSelected", resource);
-    this.openResource(resource);
-  },
-
-  /**
-   * When a node is removed, destroy it and its associated editor.
-   *
-   * @param Resource resource
-   *                 The resource being removed
-   */
-  _onTreeResourceRemoved: function (resource) {
-    this.shells.removeResource(resource);
-  },
-
-  /**
-   * Create an xul element with options
-   *
-   * @param string type
-   *               The tag name of the element to create.
-   * @param Object options
-   *               "command": DOMNode or string ID of a command element.
-   *               "parent": DOMNode or selector of parent to append child to.
-   *               anything other keys are set as an attribute as the element.
-   * @returns DOMElement
-   *          The element that has been created.
-   */
-  createElement: function (type, options) {
-    let elt = this.document.createElement(type);
-
-    let parent;
-
-    for (let opt in options) {
-      if (opt === "command") {
-        let command = typeof (options.command) === "string" ? options.command : options.command.id;
-        elt.setAttribute("command", command);
-      } else if (opt === "parent") {
-        continue;
-      } else {
-        elt.setAttribute(opt, options[opt]);
-      }
-    }
-
-    if (options.parent) {
-      let parent = options.parent;
-      if (typeof (parent) === "string") {
-        parent = this.document.querySelector(parent);
-      }
-      parent.appendChild(elt);
-    }
-
-    return elt;
-  },
-
-  /**
-   * Create a "menuitem" xul element with options
-   *
-   * @param Object options
-   *               See createElement for available options.
-   * @returns DOMElement
-   *          The menuitem that has been created.
-   */
-  createMenuItem: function (options) {
-    return this.createElement("menuitem", options);
-  },
-
-  /**
-   * Add a command to the projecteditor document.
-   * This method is meant to be used with plugins.
-   *
-   * @param Object definition
-   *               key: a key/keycode string. Example: "f".
-   *               id: Unique ID.  Example: "find".
-   *               modifiers: Key modifiers. Example: "accel".
-   * @returns DOMElement
-   *          The command element that has been created.
-   */
-  addCommand: function (plugin, definition) {
-    this._pluginCommands.set(definition.id, plugin);
-    let document = this.projectEditorKeyset.ownerDocument;
-    let command = document.createElement("command");
-    command.setAttribute("id", definition.id);
-    if (definition.key) {
-      let key = document.createElement("key");
-      key.id = "key_" + definition.id;
-
-      let keyName = definition.key;
-      if (keyName.startsWith("VK_")) {
-        key.setAttribute("keycode", keyName);
-      } else {
-        key.setAttribute("key", keyName);
-      }
-      key.setAttribute("modifiers", definition.modifiers);
-      key.setAttribute("command", definition.id);
-      this.projectEditorKeyset.appendChild(key);
-    }
-    command.setAttribute("oncommand", "void(0);"); // needed. See bug 371900
-    this.projectEditorCommandset.appendChild(command);
-    return command;
-  },
-
-  /**
-   * Get the instance of a plugin registered with a certain type.
-   *
-   * @param Type pluginType
-   *             The type, such as SavePlugin
-   * @returns Plugin
-   *          The plugin instance matching the specified type.
-   */
-  getPlugin: function (pluginType) {
-    for (let plugin of this.plugins) {
-      if (plugin.constructor === pluginType) {
-        return plugin;
-      }
-    }
-    return null;
-  },
-
-  /**
-   * Get all plugin instances active for the current project
-   *
-   * @returns [Plugin]
-   */
-  get plugins() {
-    if (!this._plugins) {
-      console.log("plugins requested before _plugins was set");
-      return [];
-    }
-    // Could filter further based on the type of project selected,
-    // but no need right now.
-    return this._plugins;
-  },
-
-  /**
-   * Dispatch an onEditorCreated event, and listen for other events specific
-   * to this editor instance.
-   *
-   * @param Editor editor
-   *               The new editor instance.
-   */
-  _onEditorCreated: function (editor) {
-    this.pluginDispatch("onEditorCreated", editor);
-    this._editorListenAndDispatch(editor, "change", "onEditorChange");
-    this._editorListenAndDispatch(editor, "cursorActivity", "onEditorCursorActivity");
-    this._editorListenAndDispatch(editor, "load", "onEditorLoad");
-    this._editorListenAndDispatch(editor, "saveRequested", "onEditorSaveRequested");
-    this._editorListenAndDispatch(editor, "save", "onEditorSave");
-
-    editor.on("focus", () => {
-      this.projectTree.selectResource(this.resourceFor(editor));
-    });
-  },
-
-  /**
-   * Dispatch an onEditorActivated event and finish setting up once the
-   * editor is ready to use.
-   *
-   * @param Editor editor
-   *               The editor instance, which is now appended in the document.
-   * @param Resource resource
-   *               The resource used by the editor
-   */
-  _onEditorActivated: function (editor, resource) {
-    editor.setToolbarVisibility();
-    this.pluginDispatch("onEditorActivated", editor, resource);
-  },
-
-  /**
-   * Dispatch an onEditorDactivated event once an editor loses focus
-   *
-   * @param Editor editor
-   *               The editor instance, which is no longer active.
-   * @param Resource resource
-   *               The resource used by the editor
-   */
-  _onEditorDeactivated: function (editor, resource) {
-    this.pluginDispatch("onEditorDeactivated", editor, resource);
-  },
-
-  /**
-   * Call a method on all plugins that implement the method.
-   * Also emits the same handler name on `this`.
-   *
-   * @param string handler
-   *               Which function name to call on plugins.
-   * @param ...args args
-   *                All remaining parameters are passed into the handler.
-   */
-  pluginDispatch: function (handler, ...args) {
-    emit(this, handler, ...args);
-    this.plugins.forEach(plugin => {
-      try {
-        if (handler in plugin) plugin[handler](...args);
-      } catch (ex) {
-        console.error(ex);
-      }
-    });
-  },
-
-  /**
-   * Listen to an event on the editor object and dispatch it
-   * to all plugins that implement the associated method
-   *
-   * @param Editor editor
-   *               Which editor to listen to
-   * @param string event
-   *               Which editor event to listen for
-   * @param string handler
-   *               Which plugin method to call
-   */
-  _editorListenAndDispatch: function (editor, event, handler) {
-    editor.on(event, (...args) => {
-      this.pluginDispatch(handler, editor, this.resourceFor(editor), ...args);
-    });
-  },
-
-  /**
-   * Find a shell for a resource.
-   *
-   * @param Resource resource
-   *                 The file to be opened.
-   * @returns Shell
-   */
-  shellFor: function (resource) {
-    return this.shells.shellFor(resource);
-  },
-
-  /**
-   * Returns the Editor for a given resource.
-   *
-   * @param Resource resource
-   *                 The file to check.
-   * @returns Editor
-   *          Instance of the editor for this file.
-   */
-  editorFor: function (resource) {
-    let shell = this.shellFor(resource);
-    return shell ? shell.editor : shell;
-  },
-
-  /**
-   * Returns a resource for the given editor
-   *
-   * @param Editor editor
-   *               The editor to check
-   * @returns Resource
-   *          The resource associated with this editor
-   */
-  resourceFor: function (editor) {
-    if (editor && editor.shell && editor.shell.resource) {
-      return editor.shell.resource;
-    }
-    return null;
-  },
-
-  /**
-   * Decide whether a given resource should be hidden in the tree.
-   *
-   * @param Resource resource
-   *                 The resource in the tree
-   * @returns Boolean
-   *          True if the node should be visible, false if hidden.
-   */
-  resourceVisible: function (resource) {
-    return true;
-  },
-
-  /**
-   * Format the given node for display in the resource tree view.
-   *
-   * @param Resource resource
-   *                 The file to be opened.
-   * @param DOMNode elt
-   *                The element in the tree to render into.
-   */
-  resourceFormatter: function (resource, elt) {
-    let editor = this.editorFor(resource);
-    let renderedByPlugin = false;
-
-    // Allow plugins to override default templating of resource in tree.
-    this.plugins.forEach(plugin => {
-      if (!plugin.onAnnotate) {
-        return;
-      }
-      if (plugin.onAnnotate(resource, editor, elt)) {
-        renderedByPlugin = true;
-      }
-    });
-
-    // If no plugin wants to handle it, just use a string from the resource.
-    if (!renderedByPlugin) {
-      elt.textContent = resource.displayName;
-    }
-  },
-
-  get sourcesVisible() {
-    return this.sourceToggle.classList.contains("pane-collapsed");
-  },
-
-  get currentShell() {
-    return this.shells.currentShell;
-  },
-
-  get currentEditor() {
-    return this.shells.currentEditor;
-  },
-
-  /**
-   * Whether or not menu items should be able to be enabled.
-   * Note that even if this is true, certain menu items will not be
-   * enabled until the correct state is achieved (for instance, the
-   * 'copy' menu item is only enabled when there is a selection).
-   * But if this is false, then nothing will be enabled.
-   */
-  set menuEnabled(val) {
-    this._menuEnabled = val;
-    if (this._loaded) {
-      this._updateMenuItems();
-    }
-  },
-
-  get menuEnabled() {
-    return this._menuEnabled;
-  },
-
-  /**
-   * Are there any unsaved resources in the Project?
-   */
-  get hasUnsavedResources() {
-    return this.project.allResources().some(resource=> {
-      let editor = this.editorFor(resource);
-      return editor && !editor.isClean();
-    });
-  },
-
-  /**
-   * Check with the user about navigating away with unsaved changes.
-   *
-   * @returns Boolean
-   *          True if there are no unsaved changes
-   *          Otherwise, ask the user to confirm and return the outcome.
-   */
-  confirmUnsaved: function () {
-    if (this.hasUnsavedResources) {
-      return confirm(
-        getLocalizedString("projecteditor.confirmUnsavedTitle"),
-        getLocalizedString("projecteditor.confirmUnsavedLabel2")
-      );
-    }
-
-    return true;
-  },
-
-  /**
-   * Save all the changes in source files.
-   *
-   * @returns Boolean
-   *          True if there were resources to save.
-   */
-  saveAllFiles: Task.async(function* () {
-    if (this.hasUnsavedResources) {
-      for (let resource of this.project.allResources()) {
-        let editor = this.editorFor(resource);
-        if (editor && !editor.isClean()) {
-          yield editor.save(resource);
-        }
-      }
-
-      return true;
-    }
-
-    return false;
-  })
-
-});
-
-
-/**
- * Returns a controller object that can be used for
- * editor-specific commands such as find, jump to line,
- * copy/paste, etc.
- */
-function getCommandController(host) {
-  return {
-    supportsCommand: function (cmd) {
-      return host._pluginCommands.get(cmd);
-    },
-
-    isCommandEnabled: function (cmd) {
-      if (!host.menuEnabled) {
-        return false;
-      }
-      let plugin = host._pluginCommands.get(cmd);
-      if (plugin && plugin.isCommandEnabled) {
-        return plugin.isCommandEnabled(cmd);
-      }
-      return true;
-    },
-    doCommand: function (cmd) {
-    }
-  };
-}
-
-exports.ProjectEditor = ProjectEditor;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/shells.js
+++ /dev/null
@@ -1,243 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const { EventTarget } = require("sdk/event/target");
-const { emit } = require("sdk/event/core");
-const { EditorTypeForResource } = require("devtools/client/projecteditor/lib/editors");
-const NetworkHelper = require("devtools/shared/webconsole/network-helper");
-const promise = require("promise");
-
-/**
- * The Shell is the object that manages the editor for a single resource.
- * It is in charge of selecting the proper Editor (text/image/plugin-defined)
- * and instantiating / appending the editor.
- * This object is not exported, it is just used internally by the ShellDeck.
- *
- * This object has a promise `editorAppended`, that will resolve once the editor
- * is ready to be used.
- */
-var Shell = Class({
-  extends: EventTarget,
-
-  /**
-   * @param ProjectEditor host
-   * @param Resource resource
-   */
-  initialize: function (host, resource) {
-    this.host = host;
-    this.doc = host.document;
-    this.resource = resource;
-    this.elt = this.doc.createElement("vbox");
-    this.elt.classList.add("view-project-detail");
-    this.elt.shell = this;
-
-    let constructor = this._editorTypeForResource();
-
-    this.editor = constructor(this.host);
-    this.editor.shell = this;
-    this.editorAppended = this.editor.appended;
-
-    this.editor.on("load", () => {
-      this.editorDeferred.resolve();
-    });
-    this.elt.appendChild(this.editor.elt);
-  },
-
-  /**
-   * Start loading the resource.  The 'load' event happens as
-   * a result of this function, so any listeners to 'editorAppended'
-   * need to be added before calling this.
-   */
-  load: function () {
-    this.editorDeferred = promise.defer();
-    this.editorLoaded = this.editorDeferred.promise;
-    this.editor.load(this.resource);
-  },
-
-  /**
-   * Destroy the shell and its associated editor
-   */
-  destroy: function () {
-    this.editor.destroy();
-    this.resource.destroy();
-  },
-
-  /**
-   * Make sure the correct editor is selected for the resource.
-   * @returns Type:Editor
-   */
-  _editorTypeForResource: function () {
-    let resource = this.resource;
-    let constructor = EditorTypeForResource(resource);
-
-    if (this.host.plugins) {
-      this.host.plugins.forEach(plugin => {
-        if (plugin.editorForResource) {
-          let pluginEditor = plugin.editorForResource(resource);
-          if (pluginEditor) {
-            constructor = pluginEditor;
-          }
-        }
-      });
-    }
-
-    return constructor;
-  }
-});
-
-/**
- * The ShellDeck is in charge of managing the list of active Shells for
- * the current ProjectEditor instance (aka host).
- *
- * This object emits the following events:
- *   - "editor-created": When an editor is initially created
- *   - "editor-activated": When an editor is ready to use
- *   - "editor-deactivated": When an editor is ready to use
- */
-var ShellDeck = Class({
-  extends: EventTarget,
-
-  /**
-   * @param ProjectEditor host
-   * @param Document document
-   */
-  initialize: function (host, document) {
-    this.doc = document;
-    this.host = host;
-    this.deck = this.doc.createElement("deck");
-    this.deck.setAttribute("flex", "1");
-    this.elt = this.deck;
-
-    this.shells = new Map();
-
-    this._activeShell = null;
-  },
-
-  /**
-   * Open a resource in a Shell.  Will create the Shell
-   * if it doesn't exist yet.
-   *
-   * @param Resource resource
-   *                 The file to be opened
-   * @returns Shell
-   */
-  open: function (defaultResource) {
-    let shell = this.shellFor(defaultResource);
-    if (!shell) {
-      shell = this._createShell(defaultResource);
-      this.shells.set(defaultResource, shell);
-    }
-    this.selectShell(shell);
-    return shell;
-  },
-
-  /**
-   * Create a new Shell for a resource.  Called by `open`.
-   *
-   * @returns Shell
-   */
-  _createShell: function (defaultResource) {
-    let shell = Shell(this.host, defaultResource);
-
-    shell.editorAppended.then(() => {
-      this.shells.set(shell.resource, shell);
-      emit(this, "editor-created", shell.editor);
-      if (this.currentShell === shell) {
-        this.selectShell(shell);
-      }
-
-    });
-
-    shell.load();
-    this.deck.appendChild(shell.elt);
-    return shell;
-  },
-
-  /**
-   * Remove the shell for a given resource.
-   *
-   * @param Resource resource
-   */
-  removeResource: function (resource) {
-    let shell = this.shellFor(resource);
-    if (shell) {
-      this.shells.delete(resource);
-      shell.destroy();
-    }
-  },
-
-  destroy: function () {
-    for (let [resource, shell] of this.shells.entries()) {
-      this.shells.delete(resource);
-      shell.destroy();
-    }
-  },
-
-  /**
-   * Select a given shell and open its editor.
-   * Will fire editor-deactivated on the old selected Shell (if any),
-   * and editor-activated on the new one once it is ready
-   *
-   * @param Shell shell
-   */
-  selectShell: function (shell) {
-    // Don't fire another activate if this is already the active shell
-    if (this._activeShell != shell) {
-      if (this._activeShell) {
-        emit(this, "editor-deactivated", this._activeShell.editor, this._activeShell.resource);
-      }
-      this.deck.selectedPanel = shell.elt;
-      this._activeShell = shell;
-
-      // Only reload the shell if the editor doesn't have local changes.
-      if (shell.editor.isClean()) {
-        shell.load();
-      }
-      shell.editorLoaded.then(() => {
-        // Handle case where another shell has been requested before this
-        // one is finished loading.
-        if (this._activeShell === shell) {
-          emit(this, "editor-activated", shell.editor, shell.resource);
-        }
-      });
-    }
-  },
-
-  /**
-   * Find a Shell for a Resource.
-   *
-   * @param Resource resource
-   * @returns Shell
-   */
-  shellFor: function (resource) {
-    return this.shells.get(resource);
-  },
-
-  /**
-   * The currently active Shell.  Note: the editor may not yet be available
-   * on the current shell.  Best to wait for the 'editor-activated' event
-   * instead.
-   *
-   * @returns Shell
-   */
-  get currentShell() {
-    return this._activeShell;
-  },
-
-  /**
-   * The currently active Editor, or null if it is not ready.
-   *
-   * @returns Editor
-   */
-  get currentEditor() {
-    let shell = this.currentShell;
-    return shell ? shell.editor : null;
-  },
-
-});
-exports.ShellDeck = ShellDeck;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/stores/base.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- 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/. */
-
-const { Cc, Ci, Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const { EventTarget } = require("sdk/event/target");
-const { emit } = require("sdk/event/core");
-const promise = require("promise");
-
-/**
- * A Store object maintains a collection of Resource objects stored in a tree.
- *
- * The Store class should not be instantiated directly.  Instead, you should
- * use a class extending it - right now this is only a LocalStore.
- *
- * Events:
- * This object emits the 'resource-added' and 'resource-removed' events.
- */
-var Store = Class({
-  extends: EventTarget,
-
-  /**
-   * Should be called during initialize() of a subclass.
-   */
-  initStore: function () {
-    this.resources = new Map();
-  },
-
-  refresh: function () {
-    return promise.resolve();
-  },
-
-  /**
-   * Return a sorted Array of all Resources in the Store
-   */
-  allResources: function () {
-    var resources = [];
-    function addResource(resource) {
-      resources.push(resource);
-      resource.childrenSorted.forEach(addResource);
-    }
-    addResource(this.root);
-    return resources;
-  },
-
-  notifyAdd: function (resource) {
-    emit(this, "resource-added", resource);
-  },
-
-  notifyRemove: function (resource) {
-    emit(this, "resource-removed", resource);
-  }
-});
-
-exports.Store = Store;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/stores/local.js
+++ /dev/null
@@ -1,215 +0,0 @@
-/* -*- 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/. */
-
-const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const { OS } = require("resource://gre/modules/osfile.jsm");
-const { emit } = require("sdk/event/core");
-const { Store } = require("devtools/client/projecteditor/lib/stores/base");
-const { Task } = require("devtools/shared/task");
-const promise = require("promise");
-const Services = require("Services");
-const { on, forget } = require("devtools/client/projecteditor/lib/helpers/event");
-const { FileResource } = require("devtools/client/projecteditor/lib/stores/resource");
-
-const CHECK_LINKED_DIRECTORY_DELAY = 5000;
-const SHOULD_LIVE_REFRESH = true;
-// XXX: Ignores should be customizable
-const IGNORE_REGEX = /(^\.)|(\~$)|(^node_modules$)/;
-
-/**
- * A LocalStore object maintains a collection of Resource objects
- * from the file system.
- *
- * This object emits the following events:
- *   - "resource-added": When a resource is added
- *   - "resource-removed": When a resource is removed
- */
-var LocalStore = Class({
-  extends: Store,
-
-  defaultCategory: "js",
-
-  initialize: function(path) {
-    this.initStore();
-    this.path = OS.Path.normalize(path);
-    this.rootPath = this.path;
-    this.displayName = this.path;
-    this.root = this._forPath(this.path);
-    this.notifyAdd(this.root);
-    this.refreshLoop = this.refreshLoop.bind(this);
-    this.refreshLoop();
-  },
-
-  destroy: function() {
-    clearTimeout(this._refreshTimeout);
-
-    if (this._refreshDeferred) {
-      this._refreshDeferred.reject("destroy");
-    }
-    if (this.worker) {
-      this.worker.terminate();
-    }
-
-    this._refreshTimeout = null;
-    this._refreshDeferred = null;
-    this.worker = null;
-
-    if (this.root) {
-      forget(this, this.root);
-      this.root.destroy();
-    }
-  },
-
-  toString: function() { return "[LocalStore:" + this.path + "]" },
-
-  /**
-   * Return a FileResource object for the given path.  If a FileInfo
-   * is provided the resource will use it, otherwise the FileResource
-   * might not have full information until the next refresh.
-   *
-   * The following parameters are passed into the FileResource constructor
-   * See resource.js for information about them
-   *
-   * @param String path
-   * @param FileInfo info
-   * @returns Resource
-   */
-  _forPath: function(path, info=null) {
-    if (this.resources.has(path)) {
-      return this.resources.get(path);
-    }
-
-    let resource = FileResource(this, path, info);
-    this.resources.set(path, resource);
-    return resource;
-  },
-
-  /**
-   * Return a promise that resolves to a fully-functional FileResource
-   * within this project.  This will hit the disk for stat info.
-   * options:
-   *
-   *   create: If true, a resource will be created even if the underlying
-   *     file doesn't exist.
-   */
-  resourceFor: function(path, options) {
-    path = OS.Path.normalize(path);
-
-    if (this.resources.has(path)) {
-      return promise.resolve(this.resources.get(path));
-    }
-
-    if (!this.contains(path)) {
-      return promise.reject(new Error(path + " does not belong to " + this.path));
-    }
-
-    return Task.spawn(function*() {
-      let parent = yield this.resourceFor(OS.Path.dirname(path));
-
-      let info;
-      try {
-        info = yield OS.File.stat(path);
-      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
-        if (!options.create) {
-          throw ex;
-        }
-      }
-
-      let resource = this._forPath(path, info);
-      parent.addChild(resource);
-      return resource;
-    }.bind(this));
-  },
-
-  refreshLoop: function() {
-    // XXX: Once Bug 958280 adds a watch function, will not need to forever loop here.
-    this.refresh().then(() => {
-      if (SHOULD_LIVE_REFRESH) {
-        this._refreshTimeout = setTimeout(this.refreshLoop,
-          CHECK_LINKED_DIRECTORY_DELAY);
-      }
-    });
-  },
-
-  _refreshTimeout: null,
-  _refreshDeferred: null,
-
-  /**
-   * Refresh the directory structure.
-   */
-  refresh: function(path=this.rootPath) {
-    if (this._refreshDeferred) {
-      return this._refreshDeferred.promise;
-    }
-    this._refreshDeferred = promise.defer();
-
-    let worker = this.worker = new ChromeWorker("chrome://devtools/content/projecteditor/lib/helpers/readdir.js");
-    let start = Date.now();
-
-    worker.onmessage = evt => {
-      // console.log("Directory read finished in " + ( Date.now() - start ) +"ms", evt);
-      for (path in evt.data) {
-        let info = evt.data[path];
-        info.path = path;
-
-        let resource = this._forPath(path, info);
-        resource.info = info;
-        if (info.isDir) {
-          let newChildren = new Set();
-          for (let childPath of info.children) {
-            childInfo = evt.data[childPath];
-            newChildren.add(this._forPath(childPath, childInfo));
-          }
-          resource.setChildren(newChildren);
-        }
-        resource.info.children = null;
-      }
-
-      worker = null;
-      this._refreshDeferred.resolve();
-      this._refreshDeferred = null;
-    };
-    worker.onerror = ex => {
-      console.error(ex);
-      worker = null;
-      this._refreshDeferred.reject(ex);
-      this._refreshDeferred = null;
-    }
-    worker.postMessage({ path: this.rootPath, ignore: IGNORE_REGEX });
-    return this._refreshDeferred.promise;
-  },
-
-  /**
-   * Returns true if the given path would be a child of the store's
-   * root directory.
-   */
-  contains: function(path) {
-    path = OS.Path.normalize(path);
-    let thisPath = OS.Path.split(this.rootPath);
-    let thatPath = OS.Path.split(path)
-
-    if (!(thisPath.absolute && thatPath.absolute)) {
-      throw new Error("Contains only works with absolute paths.");
-    }
-
-    if (thisPath.winDrive && (thisPath.winDrive != thatPath.winDrive)) {
-      return false;
-    }
-
-    if (thatPath.components.length <= thisPath.components.length) {
-      return false;
-    }
-
-    for (let i = 0; i < thisPath.components.length; i++) {
-      if (thisPath.components[i] != thatPath.components[i]) {
-        return false;
-      }
-    }
-    return true;
-  }
-});
-exports.LocalStore = LocalStore;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/stores/moz.build
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- Mode: python; 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(
-    'base.js',
-    'local.js',
-    'resource.js',
-)
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/stores/resource.js
+++ /dev/null
@@ -1,398 +0,0 @@
-/* -*- 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 { TextEncoder, TextDecoder } = require("sdk/io/buffer");
-const { Class } = require("sdk/core/heritage");
-const { EventTarget } = require("sdk/event/target");
-const { emit } = require("sdk/event/core");
-const URL = require("sdk/url");
-const promise = require("promise");
-const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
-const { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
-const mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
-const { Task } = require("devtools/shared/task");
-
-const gDecoder = new TextDecoder();
-const gEncoder = new TextEncoder();
-
-/**
- * A Resource is a single file-like object that can be respresented
- * as a file for ProjectEditor.
- *
- * The Resource class is not exported, and should not be instantiated
- * Instead, you should use the FileResource class that extends it.
- *
- * This object emits the following events:
- *   - "children-changed": When a child has been added or removed.
- *                         See setChildren.
- *   - "deleted": When the resource has been deleted.
- */
-var Resource = Class({
-  extends: EventTarget,
-
-  refresh: function () { return promise.resolve(this); },
-  destroy: function () { },
-  delete: function () { },
-
-  setURI: function (uri) {
-    if (typeof (uri) === "string") {
-      uri = URL.URL(uri);
-    }
-    this.uri = uri;
-  },
-
-  /**
-   * Is there more than 1 child Resource?
-   */
-  get hasChildren() { return this.children && this.children.size > 0; },
-
-  /**
-   * Is this Resource the root (top level for the store)?
-   */
-  get isRoot() {
-    return !this.parent;
-  },
-
-  /**
-   * Sorted array of children for display
-   */
-  get childrenSorted() {
-    if (!this.hasChildren) {
-      return [];
-    }
-
-    return [...this.children].sort((a, b)=> {
-      // Put directories above files.
-      if (a.isDir !== b.isDir) {
-        return b.isDir;
-      }
-      return a.basename.toLowerCase() > b.basename.toLowerCase();
-    });
-  },
-
-  /**
-   * Set the children set of this Resource, and notify of any
-   * additions / removals that happened in the change.
-   */
-  setChildren: function (newChildren) {
-    let oldChildren = this.children || new Set();
-    let change = false;
-
-    for (let child of oldChildren) {
-      if (!newChildren.has(child)) {
-        change = true;
-        child.parent = null;
-        this.store.notifyRemove(child);
-      }
-    }
-
-    for (let child of newChildren) {
-      if (!oldChildren.has(child)) {
-        change = true;
-        child.parent = this;
-        this.store.notifyAdd(child);
-      }
-    }
-
-    this.children = newChildren;
-    if (change) {
-      emit(this, "children-changed", this);
-    }
-  },
-
-  /**
-   * Add a resource to children set and notify of the change.
-   *
-   * @param Resource resource
-   */
-  addChild: function (resource) {
-    this.children = this.children || new Set();
-
-    resource.parent = this;
-    this.children.add(resource);
-    this.store.notifyAdd(resource);
-    emit(this, "children-changed", this);
-    return resource;
-  },
-
-  /**
-   * Checks if current object has child with specific name.
-   *
-   * @param string name
-   */
-  hasChild: function (name) {
-    for (let child of this.children) {
-      if (child.basename === name) {
-        return true;
-      }
-    }
-    return false;
-  },
-
-  /**
-   * Remove a resource to children set and notify of the change.
-   *
-   * @param Resource resource
-   */
-  removeChild: function (resource) {
-    resource.parent = null;
-    this.children.remove(resource);
-    this.store.notifyRemove(resource);
-    emit(this, "children-changed", this);
-    return resource;
-  },
-
-  /**
-   * Return a set with children, children of children, etc -
-   * gathered recursively.
-   *
-   * @returns Set<Resource>
-   */
-  allDescendants: function () {
-    let set = new Set();
-
-    function addChildren(item) {
-      if (!item.children) {
-        return;
-      }
-
-      for (let child of item.children) {
-        set.add(child);
-      }
-    }
-
-    addChildren(this);
-    for (let item of set) {
-      addChildren(item);
-    }
-
-    return set;
-  },
-});
-
-/**
- * A FileResource is an implementation of Resource for a File System
- * backing.  This is exported, and should be used instead of Resource.
- */
-var FileResource = Class({
-  extends: Resource,
-
-  /**
-   * @param Store store
-   * @param String path
-   * @param FileInfo info
-   *        https://developer.mozilla.org/en-US/docs/JavaScript_OS.File/OS.File.Info
-   */
-  initialize: function (store, path, info) {
-    this.store = store;
-    this.path = path;
-
-    this.setURI(URL.URL(URL.fromFilename(path)));
-    this._lastReadModification = undefined;
-
-    this.info = info;
-    this.parent = null;
-  },
-
-  toString: function () {
-    return "[FileResource:" + this.path + "]";
-  },
-
-  destroy: function () {
-    if (this._refreshDeferred) {
-      this._refreshDeferred.reject();
-    }
-    this._refreshDeferred = null;
-  },
-
-  /**
-   * Fetch and cache information about this particular file.
-   * https://developer.mozilla.org/en-US/docs/JavaScript_OS.File/OS.File_for_the_main_thread#OS.File.stat
-   *
-   * @returns Promise
-   *          Resolves once the File.stat has finished.
-   */
-  refresh: function () {
-    if (this._refreshDeferred) {
-      return this._refreshDeferred.promise;
-    }
-    this._refreshDeferred = promise.defer();
-    OS.File.stat(this.path).then(info => {
-      this.info = info;
-      if (this._refreshDeferred) {
-        this._refreshDeferred.resolve(this);
-        this._refreshDeferred = null;
-      }
-    });
-    return this._refreshDeferred.promise;
-  },
-
-  /**
-   * Return the trailing name component of this Resource
-   */
-  get basename() {
-    return this.path.replace(/\/+$/, "").replace(/\\/g, "/").replace(/.*\//, "");
-  },
-
-  /**
-   * A string to be used when displaying this Resource in views
-   */
-  get displayName() {
-    return this.basename + (this.isDir ? "/" : "");
-  },
-
-  /**
-   * Is this FileResource a directory?  Rather than checking children
-   * here, we use this.info.  So this could return a false negative
-   * if there was no info passed in on constructor and the first
-   * refresh hasn't yet finished.
-   */
-  get isDir() {
-    if (!this.info) { return false; }
-    return this.info.isDir && !this.info.isSymLink;
-  },
-
-  /**
-   * Read the file as a string asynchronously.
-   *
-   * @returns Promise
-   *          Resolves with the text of the file.
-   */
-  load: function () {
-    return OS.File.read(this.path).then(bytes => {
-      return gDecoder.decode(bytes);
-    });
-  },
-
-  /**
-   * Delete the file from the filesystem
-   *
-   * @returns Promise
-   *          Resolves when the file is deleted
-   */
-  delete: function () {
-    emit(this, "deleted", this);
-    if (this.isDir) {
-      return OS.File.removeDir(this.path);
-    } else {
-      return OS.File.remove(this.path);
-    }
-  },
-
-  /**
-   * Add a text file as a child of this FileResource.
-   * This instance must be a directory.
-   *
-   * @param string name
-   *               The filename (path will be generated based on this.path).
-   *        string initial
-   *               The content to write to the new file.
-   * @returns Promise
-   *          Resolves with the new FileResource once it has
-   *          been written to disk.
-   *          Rejected if this is not a directory.
-   */
-  createChild: function (name, initial = "") {
-    if (!this.isDir) {
-      return promise.reject(new Error("Cannot add child to a regular file"));
-    }
-
-    let newPath = OS.Path.join(this.path, name);
-
-    let buffer = initial ? gEncoder.encode(initial) : "";
-    return OS.File.writeAtomic(newPath, buffer, {
-      noOverwrite: true
-    }).then(() => {
-      return this.store.refresh();
-    }).then(() => {
-      let resource = this.store.resources.get(newPath);
-      if (!resource) {
-        throw new Error("Error creating " + newPath);
-      }
-      return resource;
-    });
-  },
-
-  /**
-   * Rename the file from the filesystem
-   *
-   * @returns Promise
-   *          Resolves with the renamed FileResource.
-   */
-  rename: function (oldName, newName) {
-    let oldPath = OS.Path.join(this.path, oldName);
-    let newPath = OS.Path.join(this.path, newName);
-
-    return OS.File.move(oldPath, newPath).then(() => {
-      return this.store.refresh();
-    }).then(() => {
-      let resource = this.store.resources.get(newPath);
-      if (!resource) {
-        throw new Error("Error creating " + newPath);
-      }
-      return resource;
-    });
-  },
-
-  /**
-   * Write a string to this file.
-   *
-   * @param string content
-   * @returns Promise
-   *          Resolves once it has been written to disk.
-   *          Rejected if there is an error
-   */
-  save: Task.async(function* (content) {
-    // XXX: writeAtomic was losing permissions after saving on OSX
-    // return OS.File.writeAtomic(this.path, buffer, { tmpPath: this.path + ".tmp" });
-    let buffer = gEncoder.encode(content);
-    let path = this.path;
-    let file = yield OS.File.open(path, {truncate: true});
-    yield file.write(buffer);
-    yield file.close();
-  }),
-
-  /**
-   * Attempts to get the content type from the file.
-   */
-  get contentType() {
-    if (this._contentType) {
-      return this._contentType;
-    }
-    if (this.isDir) {
-      return "x-directory/normal";
-    }
-    try {
-      this._contentType = mimeService.getTypeFromFile(new FileUtils.File(this.path));
-    } catch (ex) {
-      if (ex.name !== "NS_ERROR_NOT_AVAILABLE" &&
-          ex.name !== "NS_ERROR_FAILURE") {
-        console.error(ex, this.path);
-      }
-      this._contentType = null;
-    }
-    return this._contentType;
-  },
-
-  /**
-   * A string used when determining the type of Editor to open for this.
-   * See editors.js -> EditorTypeForResource.
-   */
-  get contentCategory() {
-    const NetworkHelper = require("devtools/shared/webconsole/network-helper");
-    let category = NetworkHelper.mimeCategoryMap[this.contentType];
-    // Special treatment for manifest.webapp.
-    if (!category && this.basename === "manifest.webapp") {
-      return "json";
-    }
-    return category || "txt";
-  }
-});
-
-exports.FileResource = FileResource;
deleted file mode 100644
--- a/devtools/client/projecteditor/lib/tree.js
+++ /dev/null
@@ -1,593 +0,0 @@
-/* -*- 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/. */
-
-const { Cu } = require("chrome");
-const { Class } = require("sdk/core/heritage");
-const { emit } = require("sdk/event/core");
-const { EventTarget } = require("sdk/event/target");
-const { merge } = require("sdk/util/object");
-const promise = require("promise");
-const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
-const { on, forget } = require("devtools/client/projecteditor/lib/helpers/event");
-const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
-
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-
-/**
- * ResourceContainer is used as the view of a single Resource in
- * the tree.  It is not exported.
- */
-var ResourceContainer = Class({
-  /**
-   * @param ProjectTreeView tree
-   * @param Resource resource
-   */
-  initialize: function (tree, resource) {
-    this.tree = tree;
-    this.resource = resource;
-    this.elt = null;
-    this.expander = null;
-    this.children = null;
-
-    let doc = tree.doc;
-
-    this.elt = doc.createElementNS(HTML_NS, "li");
-    this.elt.classList.add("child");
-
-    this.line = doc.createElementNS(HTML_NS, "div");
-    this.line.classList.add("child");
-    this.line.classList.add("entry");
-    this.line.setAttribute("theme", "dark");
-    this.line.setAttribute("tabindex", "0");
-
-    this.elt.appendChild(this.line);
-
-    this.highlighter = doc.createElementNS(HTML_NS, "span");
-    this.highlighter.classList.add("highlighter");
-    this.line.appendChild(this.highlighter);
-
-    this.expander = doc.createElementNS(HTML_NS, "span");
-    this.expander.className = "arrow expander";
-    this.expander.setAttribute("open", "");
-    this.line.appendChild(this.expander);
-
-    this.label = doc.createElementNS(HTML_NS, "span");
-    this.label.className = "file-label";
-    this.line.appendChild(this.label);
-
-    this.line.addEventListener("contextmenu", (ev) => {
-      this.select();
-      this.openContextMenu(ev);
-    });
-
-    this.children = doc.createElementNS(HTML_NS, "ul");
-    this.children.classList.add("children");
-
-    this.elt.appendChild(this.children);
-
-    this.line.addEventListener("click", (evt) => {
-      this.select();
-      this.toggleExpansion();
-      evt.stopPropagation();
-    });
-    this.expander.addEventListener("click", (evt) => {
-      this.toggleExpansion();
-      this.select();
-      evt.stopPropagation();
-    }, true);
-
-    if (!this.resource.isRoot) {
-      this.expanded = false;
-    }
-    this.update();
-  },
-
-  toggleExpansion: function () {
-    if (!this.resource.isRoot) {
-      this.expanded = !this.expanded;
-    } else {
-      this.expanded = true;
-    }
-  },
-
-  destroy: function () {
-    this.elt.remove();
-    this.expander.remove();
-    this.highlighter.remove();
-    this.children.remove();
-    this.label.remove();
-    this.elt = this.expander = this.highlighter = this.children = this.label = null;
-  },
-
-  /**
-   * Open the context menu when right clicking on the view.
-   * XXX: We could pass this to plugins to allow themselves
-   * to be register/remove items from the context menu if needed.
-   *
-   * @param Event e
-   */
-  openContextMenu: function (ev) {
-    ev.preventDefault();
-    let popup = this.tree.options.contextMenuPopup;
-    popup.openPopupAtScreen(ev.screenX, ev.screenY, true);
-  },
-
-  /**
-   * Update the view based on the current state of the Resource.
-   */
-  update: function () {
-    let visible = this.tree.options.resourceVisible ?
-      this.tree.options.resourceVisible(this.resource) :
-      true;
-
-    this.elt.hidden = !visible;
-
-    this.tree.options.resourceFormatter(this.resource, this.label);
-
-    this.expander.style.visibility = this.resource.hasChildren ? "visible" : "hidden";
-
-  },
-
-  /**
-   * Select this view in the ProjectTreeView.
-   */
-  select: function () {
-    this.tree.selectContainer(this);
-  },
-
-  /**
-   * @returns Boolean
-   *          Is this view currently selected
-   */
-  get selected() {
-    return this.line.classList.contains("selected");
-  },
-
-  /**
-   * Set the selected state in the UI.
-   */
-  set selected(v) {
-    if (v) {
-      this.line.classList.add("selected");
-    } else {
-      this.line.classList.remove("selected");
-    }
-  },
-
-  /**
-   * @returns Boolean
-   *          Are any children visible.
-   */
-  get expanded() {
-    return !this.elt.classList.contains("tree-collapsed");
-  },
-
-  /**
-   * Set the visiblity state of children.
-   */
-  set expanded(v) {
-    if (v) {
-      this.elt.classList.remove("tree-collapsed");
-      this.expander.setAttribute("open", "");
-    } else {
-      this.expander.removeAttribute("open");
-      this.elt.classList.add("tree-collapsed");
-    }
-  }
-});
-
-/**
- * TreeView is a view managing a list of children.
- * It is not to be instantiated directly - only extended.
- * Use ProjectTreeView instead.
- */
-var TreeView = Class({
-  extends: EventTarget,
-
-  /**
-   * @param Document document
-   * @param Object options
-   *               - contextMenuPopup: a <menupopup> element
-   *               - resourceFormatter: a function(Resource, DOMNode)
-   *                 that renders the resource into the view
-   *               - resourceVisible: a function(Resource) -> Boolean
-   *                 that determines if the resource should show up.
-   */
-  initialize: function (doc, options) {
-    this.doc = doc;
-    this.options = merge({
-      resourceFormatter: function (resource, elt) {
-        elt.textContent = resource.toString();
-      }
-    }, options);
-    this.models = new Set();
-    this.roots = new Set();
-    this._containers = new Map();
-    this.elt = this.doc.createElementNS(HTML_NS, "div");
-    this.elt.tree = this;
-    this.elt.className = "sources-tree";
-    this.elt.setAttribute("with-arrows", "true");
-    this.elt.setAttribute("theme", "dark");
-    this.elt.setAttribute("flex", "1");
-
-    this.children = this.doc.createElementNS(HTML_NS, "ul");
-    this.elt.appendChild(this.children);
-
-    this.resourceChildrenChanged = this.resourceChildrenChanged.bind(this);
-    this.removeResource = this.removeResource.bind(this);
-    this.updateResource = this.updateResource.bind(this);
-  },
-
-  destroy: function () {
-    this._destroyed = true;
-    this.elt.remove();
-  },
-
-  /**
-   * Helper function to create DOM elements for promptNew and promptEdit
-   */
-  createInputContainer: function () {
-    let inputholder = this.doc.createElementNS(HTML_NS, "div");
-    inputholder.className = "child entry";
-
-    let expander = this.doc.createElementNS(HTML_NS, "span");
-    expander.className = "arrow expander";
-    expander.setAttribute("invisible", "");
-    inputholder.appendChild(expander);
-
-    let placeholder = this.doc.createElementNS(HTML_NS, "div");
-    placeholder.className = "child";
-    inputholder.appendChild(placeholder);
-
-    return {inputholder, placeholder};
-  },
-
-  /**
-   * Prompt the user to create a new file in the tree.
-   *
-   * @param string initial
-   *               The suggested starting file name
-   * @param Resource parent
-   * @param Resource sibling
-   *                 Which resource to put this next to.  If not set,
-   *                 it will be put in front of all other children.
-   *
-   * @returns Promise
-   *          Resolves once the prompt has been successful,
-   *          Rejected if it is cancelled
-   */
-  promptNew: function (initial, parent, sibling = null) {
-    let deferred = promise.defer();
-
-    let parentContainer = this._containers.get(parent);
-    let item = this.doc.createElement("li");
-    item.className = "child";
-
-    let {inputholder, placeholder} = this.createInputContainer();
-    item.appendChild(inputholder);
-
-    let children = parentContainer.children;
-    sibling = sibling ? this._containers.get(sibling).elt : null;
-    parentContainer.children.insertBefore(item, sibling ? sibling.nextSibling : children.firstChild);
-
-    new InplaceEditor({
-      element: placeholder,
-      initial: initial,
-      preserveTextStyles: true,
-      start: editor => {
-        editor.input.select();
-      },
-      done: function (val, commit) {
-        if (commit) {
-          deferred.resolve(val);
-        } else {
-          deferred.reject(val);
-        }
-        parentContainer.line.focus();
-      },
-      destroy: () => {
-        item.remove();
-      },
-    });
-
-    return deferred.promise;
-  },
-
-  /**
-   * Prompt the user to rename file in the tree.
-   *
-   * @param string initial
-   *               The suggested starting file name
-   * @param resource
-   *
-   * @returns Promise
-   *          Resolves once the prompt has been successful,
-   *          Rejected if it is cancelled
-   */
-  promptEdit: function (initial, resource) {
-    let deferred = promise.defer();
-    let item = this._containers.get(resource).elt;
-    let originalText = item.childNodes[0];
-
-    let {inputholder, placeholder} = this.createInputContainer();
-    item.insertBefore(inputholder, originalText);
-
-    item.removeChild(originalText);
-
-    new InplaceEditor({
-      element: placeholder,
-      initial: initial,
-      preserveTextStyles: true,
-      start: editor => {
-        editor.input.select();
-      },
-      done: function (val, commit) {
-        if (val === initial) {
-          item.insertBefore(originalText, inputholder);
-        }
-
-        item.removeChild(inputholder);
-
-        if (commit) {
-          deferred.resolve(val);
-        } else {
-          deferred.reject(val);
-        }
-      },
-    });
-
-    return deferred.promise;
-  },
-
-  /**
-   * Add a new Store into the TreeView
-   *
-   * @param Store model
-   */
-  addModel: function (model) {
-    if (this.models.has(model)) {
-      // Requesting to add a model that already exists
-      return;
-    }
-    this.models.add(model);
-    let placeholder = this.doc.createElementNS(HTML_NS, "li");
-    placeholder.style.display = "none";
-    this.children.appendChild(placeholder);
-    this.roots.add(model.root);
-    model.root.refresh().then(root => {
-      if (this._destroyed || !this.models.has(model)) {
-        // model may have been removed during the initial refresh.
-        // In this case, do not import the resource or add to DOM, just leave it be.
-        return;
-      }
-      let container = this.importResource(root);
-      container.line.classList.add("entry-group-title");
-      container.line.setAttribute("theme", "dark");
-      this.selectContainer(container);
-
-      this.children.insertBefore(container.elt, placeholder);
-      this.children.removeChild(placeholder);
-    });
-  },
-
-  /**
-   * Remove a Store from the TreeView
-   *
-   * @param Store model
-   */
-  removeModel: function (model) {
-    this.models.delete(model);
-    this.removeResource(model.root);
-  },
-
-
-  /**
-   * Get the ResourceContainer.  Used for testing the view.
-   *
-   * @param Resource resource
-   * @returns ResourceContainer
-   */
-  getViewContainer: function (resource) {
-    return this._containers.get(resource);
-  },
-
-  /**
-   * Select a ResourceContainer in the tree.
-   *
-   * @param ResourceContainer container
-   */
-  selectContainer: function (container) {
-    if (this.selectedContainer === container) {
-      return;
-    }
-    if (this.selectedContainer) {
-      this.selectedContainer.selected = false;
-    }
-    this.selectedContainer = container;
-    container.selected = true;
-    emit(this, "selection", container.resource);
-  },
-
-  /**
-   * Select a Resource in the tree.
-   *
-   * @param Resource resource
-   */
-  selectResource: function (resource) {
-    this.selectContainer(this._containers.get(resource));
-  },
-
-  /**
-   * Get the currently selected Resource
-   *
-   * @param Resource resource
-   */
-  getSelectedResource: function () {
-    return this.selectedContainer.resource;
-  },
-
-  /**
-   * Insert a Resource into the view.
-   * Makes a new ResourceContainer if needed
-   *
-   * @param Resource resource
-   */
-  importResource: function (resource) {
-    if (!resource) {
-      return null;
-    }
-
-    if (this._containers.has(resource)) {
-      return this._containers.get(resource);
-    }
-    var container = ResourceContainer(this, resource);
-    this._containers.set(resource, container);
-    this._updateChildren(container);
-
-    on(this, resource, "children-changed", this.resourceChildrenChanged);
-    on(this, resource, "label-change", this.updateResource);
-    on(this, resource, "deleted", this.removeResource);
-
-    return container;
-  },
-
-  /**
-   * Remove a Resource (including children) from the view.
-   *
-   * @param Resource resource
-   */
-  removeResource: function (resource) {
-    let toRemove = resource.allDescendants();
-    toRemove.add(resource);
-    for (let remove of toRemove) {
-      this._removeResource(remove);
-    }
-  },
-
-  /**
-   * Remove an individual Resource (but not children) from the view.
-   *
-   * @param Resource resource
-   */
-  _removeResource: function (resource) {
-    forget(this, resource);
-    if (this._containers.get(resource)) {
-      this._containers.get(resource).destroy();
-      this._containers.delete(resource);
-    }
-    emit(this, "resource-removed", resource);
-  },
-
-  /**
-   * Listener for when a resource has new children.
-   * This can happen as files are being loaded in from FileSystem, for example.
-   *
-   * @param Resource resource
-   */
-  resourceChildrenChanged: function (resource) {
-    this.updateResource(resource);
-    this._updateChildren(this._containers.get(resource));
-  },
-
-  /**
-   * Listener for when a label in the view has been updated.
-   * For example, the 'dirty' plugin marks changed files with an '*'
-   * next to the filename, and notifies with this event.
-   *
-   * @param Resource resource
-   */
-  updateResource: function (resource) {
-    let container = this._containers.get(resource);
-    container.update();
-  },
-
-  /**
-   * Build necessary ResourceContainers for a Resource and its
-   * children, then append them into the view.
-   *
-   * @param ResourceContainer container
-   */
-  _updateChildren: function (container) {
-    let resource = container.resource;
-    let fragment = this.doc.createDocumentFragment();
-    if (resource.children) {
-      for (let child of resource.childrenSorted) {
-        let childContainer = this.importResource(child);
-        fragment.appendChild(childContainer.elt);
-      }
-    }
-
-    while (container.children.firstChild) {
-      container.children.firstChild.remove();
-    }
-
-    container.children.appendChild(fragment);
-  },
-});
-
-/**
- * ProjectTreeView is the implementation of TreeView
- * that is exported.  This is the class that is to be used
- * directly.
- */
-var ProjectTreeView = Class({
-  extends: TreeView,
-
-  /**
-   * See TreeView.initialize
-   *
-   * @param Document document
-   * @param Object options
-   */
-  initialize: function (document, options) {
-    TreeView.prototype.initialize.apply(this, arguments);
-  },
-
-  destroy: function () {
-    this.forgetProject();
-    TreeView.prototype.destroy.apply(this, arguments);
-  },
-
-  /**
-   * Remove current project and empty the tree
-   */
-  forgetProject: function () {
-    if (this.project) {
-      forget(this, this.project);
-      for (let store of this.project.allStores()) {
-        this.removeModel(store);
-      }
-    }
-  },
-
-  /**
-   * Show a project in the tree
-   *
-   * @param Project project
-   *        The project to render into a tree
-   */
-  setProject: function (project) {
-    this.forgetProject();
-    this.project = project;
-    if (this.project) {
-      on(this, project, "store-added", this.addModel.bind(this));
-      on(this, project, "store-removed", this.removeModel.bind(this));
-      on(this, project, "project-saved", this.refresh.bind(this));
-      this.refresh();
-    }
-  },
-
-  /**
-   * Refresh the tree with all of the current project stores
-   */
-  refresh: function () {
-    for (let store of this.project.allStores()) {
-      this.addModel(store);
-    }
-  }
-});
-
-exports.ProjectTreeView = ProjectTreeView;
deleted file mode 100644
--- a/devtools/client/projecteditor/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; 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/.
-
-DIRS += ['lib']
-
-BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
deleted file mode 100644
--- a/devtools/client/projecteditor/test/.eslintrc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-"use strict";
-
-module.exports = {
-  // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../.eslintrc.mochitests.js"
-};
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser.ini
+++ /dev/null
@@ -1,32 +0,0 @@
-[DEFAULT]
-tags = devtools
-subsuite = devtools
-support-files =
-  head.js
-  helper_homepage.html
-  helper_edits.js
-  projecteditor-test.xul
-
-[browser_projecteditor_app_options.js]
-[browser_projecteditor_confirm_unsaved.js]
-[browser_projecteditor_contextmenu_01.js]
-skip-if = asan # Bug 1083140
-[browser_projecteditor_contextmenu_02.js]
-skip-if = true # Bug 1173950
-[browser_projecteditor_delete_file.js]
-skip-if = e10s # Frequent failures in e10s - Bug 1020027
-[browser_projecteditor_rename_file_01.js]
-[browser_projecteditor_rename_file_02.js]
-[browser_projecteditor_editing_01.js]
-[browser_projecteditor_editors_image.js]
-[browser_projecteditor_external_change.js]
-[browser_projecteditor_immediate_destroy.js]
-[browser_projecteditor_init.js]
-[browser_projecteditor_menubar_01.js]
-[browser_projecteditor_menubar_02.js]
-skip-if = true # Bug 1173950
-[browser_projecteditor_new_file.js]
-[browser_projecteditor_saveall.js]
-[browser_projecteditor_stores.js]
-[browser_projecteditor_tree_selection_01.js]
-[browser_projecteditor_tree_selection_02.js]
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_app_options.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/* 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 that options can be changed without resetting the whole
-// editor.
-add_task(function* () {
-
-  let TEMP_PATH = buildTempDirectoryStructure();
-  let projecteditor = yield addProjectEditorTab();
-
-  let resourceBeenAdded = promise.defer();
-  projecteditor.project.once("resource-added", () => {
-    info("A resource has been added");
-    resourceBeenAdded.resolve();
-  });
-
-  info("About to set project to: " + TEMP_PATH);
-  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
-    name: "Test",
-    iconUrl: "chrome://devtools/skin/images/tool-options.svg",
-    projectOverviewURL: SAMPLE_WEBAPP_URL
-  });
-
-  info("Making sure a resource has been added before continuing");
-  yield resourceBeenAdded.promise;
-
-  info("From now on, if a resource is added it should fail");
-  projecteditor.project.on("resource-added", failIfResourceAdded);
-
-  info("Getting ahold and validating the project header DOM");
-  let header = projecteditor.document.querySelector(".entry-group-title");
-  let image = header.querySelector(".project-image");
-  let nameLabel = header.querySelector(".project-name-label");
-  let statusElement = header.querySelector(".project-status");
-  is(statusElement.getAttribute("status"), "unknown", "The status starts out as unknown.");
-  is(nameLabel.textContent, "Test", "The name label has been set correctly");
-  is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-options.svg", "The icon has been set correctly");
-
-  info("About to set project with new options.");
-  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
-    name: "Test2",
-    iconUrl: "chrome://devtools/skin/images/tool-inspector.svg",
-    projectOverviewURL: SAMPLE_WEBAPP_URL,
-    validationStatus: "error"
-  });
-
-  info("Getting ahold of and validating the project header DOM");
-  is(statusElement.getAttribute("status"), "error", "The status has been set correctly.");
-  is(nameLabel.textContent, "Test2", "The name label has been set correctly");
-  is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-inspector.svg", "The icon has been set correctly");
-
-  info("About to set project with new options.");
-  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
-    name: "Test3",
-    iconUrl: "chrome://devtools/skin/images/tool-webconsole.svg",
-    projectOverviewURL: SAMPLE_WEBAPP_URL,
-    validationStatus: "warning"
-  });
-
-  info("Getting ahold of and validating the project header DOM");
-  is(statusElement.getAttribute("status"), "warning", "The status has been set correctly.");
-  is(nameLabel.textContent, "Test3", "The name label has been set correctly");
-  is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-webconsole.svg", "The icon has been set correctly");
-
-  info("About to set project with new options.");
-  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
-    name: "Test4",
-    iconUrl: "chrome://devtools/skin/images/tool-debugger.svg",
-    projectOverviewURL: SAMPLE_WEBAPP_URL,
-    validationStatus: "valid"
-  });
-
-  info("Getting ahold of and validating the project header DOM");
-  is(statusElement.getAttribute("status"), "valid", "The status has been set correctly.");
-  is(nameLabel.textContent, "Test4", "The name label has been set correctly");
-  is(image.getAttribute("src"), "chrome://devtools/skin/images/tool-debugger.svg", "The icon has been set correctly");
-
-  info("Test finished, cleaning up");
-  projecteditor.project.off("resource-added", failIfResourceAdded);
-});
-
-function failIfResourceAdded() {
-  ok(false, "A resource has been added, but it shouldn't have been");
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_confirm_unsaved.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/* 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";
-
-loadHelperScript("helper_edits.js");
-
-// Test that a prompt shows up when requested if a file is unsaved.
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  ok(true, "ProjectEditor has loaded");
-
-  let resources = projecteditor.project.allResources();
-  yield selectFile(projecteditor, resources[2]);
-  let editor = projecteditor.currentEditor;
-  let originalText = editor.editor.getText();
-
-  ok(!projecteditor.hasUnsavedResources, "There are no unsaved resources");
-  ok(projecteditor.confirmUnsaved(), "When there are no unsaved changes, confirmUnsaved() is true");
-  editor.editor.setText("bar");
-  editor.editor.setText(originalText);
-  ok(!projecteditor.hasUnsavedResources, "There are no unsaved resources");
-  ok(projecteditor.confirmUnsaved(), "When an editor has changed but is still the original text, confirmUnsaved() is true");
-
-  editor.editor.setText("bar");
-
-  checkConfirmYes(projecteditor);
-  checkConfirmNo(projecteditor);
-});
-
-function checkConfirmYes(projecteditor, container) {
-  function confirmYes(aSubject) {
-    info("confirm dialog observed as expected, going to click OK");
-    Services.obs.removeObserver(confirmYes, "common-dialog-loaded");
-    Services.obs.removeObserver(confirmYes, "tabmodal-dialog-loaded");
-    aSubject.Dialog.ui.button0.click();
-  }
-
-  Services.obs.addObserver(confirmYes, "common-dialog-loaded");
-  Services.obs.addObserver(confirmYes, "tabmodal-dialog-loaded");
-
-  ok(projecteditor.hasUnsavedResources, "There are unsaved resources");
-  ok(projecteditor.confirmUnsaved(), "When there are unsaved changes, clicking OK makes confirmUnsaved() true");
-}
-
-function checkConfirmNo(projecteditor, container) {
-  function confirmNo(aSubject) {
-    info("confirm dialog observed as expected, going to click cancel");
-    Services.obs.removeObserver(confirmNo, "common-dialog-loaded");
-    Services.obs.removeObserver(confirmNo, "tabmodal-dialog-loaded");
-    aSubject.Dialog.ui.button1.click();
-  }
-
-  Services.obs.addObserver(confirmNo, "common-dialog-loaded");
-  Services.obs.addObserver(confirmNo, "tabmodal-dialog-loaded");
-
-  ok(projecteditor.hasUnsavedResources, "There are unsaved resources");
-  ok(!projecteditor.confirmUnsaved(), "When there are unsaved changes, clicking cancel makes confirmUnsaved() false");
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_01.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* 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 that context menus append to the correct document.
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory({
-    menubar: false
-  });
-  ok(projecteditor, "ProjectEditor has loaded");
-
-  let contextMenuPopup = projecteditor.document.querySelector("#context-menu-popup");
-  let textEditorContextMenuPopup = projecteditor.document.querySelector("#texteditor-context-popup");
-  ok(contextMenuPopup, "The menu has loaded in the projecteditor document");
-  ok(textEditorContextMenuPopup, "The menu has loaded in the projecteditor document");
-
-  let projecteditor2 = yield addProjectEditorTabForTempDirectory();
-  contextMenuPopup = projecteditor2.document.getElementById("context-menu-popup");
-  textEditorContextMenuPopup = projecteditor2.document.getElementById("texteditor-context-popup");
-  ok(!contextMenuPopup, "The menu has NOT loaded in the projecteditor document");
-  ok(!textEditorContextMenuPopup, "The menu has NOT loaded in the projecteditor document");
-  ok(content.document.querySelector("#context-menu-popup"), "The menu has loaded in the specified element");
-  ok(content.document.querySelector("#texteditor-context-popup"), "The menu has loaded in the specified element");
-});
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_contextmenu_02.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* 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";
-
-loadHelperScript("helper_edits.js");
-
-// Test context menu enabled / disabled state in editor
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  ok(projecteditor, "ProjectEditor has loaded");
-
-  let {textEditorContextMenuPopup} = projecteditor;
-
-  // Update menu items for a clean slate, so previous tests cannot
-  // affect paste, and possibly other side effects
-  projecteditor._updateMenuItems();
-
-  let cmdDelete = textEditorContextMenuPopup.querySelector("[command=cmd_delete]");
-  let cmdSelectAll = textEditorContextMenuPopup.querySelector("[command=cmd_selectAll]");
-  let cmdCut = textEditorContextMenuPopup.querySelector("[command=cmd_cut]");
-  let cmdCopy = textEditorContextMenuPopup.querySelector("[command=cmd_copy]");
-  let cmdPaste = textEditorContextMenuPopup.querySelector("[command=cmd_paste]");
-
-  info("Opening resource");
-  let resource = projecteditor.project.allResources()[2];
-  yield selectFile(projecteditor, resource);
-  let editor = projecteditor.currentEditor;
-  editor.editor.focus();
-
-  info("Opening context menu on resource");
-  yield openContextMenuForEditor(editor, textEditorContextMenuPopup);
-
-  is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
-  is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
-  is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
-  is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
-  is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
-
-  info("Setting a selection and repening context menu on resource");
-  yield closeContextMenuForEditor(editor, textEditorContextMenuPopup);
-  editor.editor.setSelection({line: 0, ch: 0}, {line: 0, ch: 2});
-  yield openContextMenuForEditor(editor, textEditorContextMenuPopup);
-
-  is(cmdDelete.getAttribute("disabled"), "", "cmdDelete is enabled");
-  is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
-  is(cmdCut.getAttribute("disabled"), "", "cmdCut is enabled");
-  is(cmdCopy.getAttribute("disabled"), "", "cmdCopy is enabled");
-  is(cmdPaste.getAttribute("disabled"), "", "cmdPaste is enabled");
-});
-
-function* openContextMenuForEditor(editor, contextMenu) {
-  let editorDoc = editor.editor.container.contentDocument;
-  let shown = onPopupShow(contextMenu);
-  EventUtils.synthesizeMouse(editorDoc.body, 2, 2,
-    {type: "contextmenu", button: 2}, editorDoc.defaultView);
-  yield shown;
-}
-function* closeContextMenuForEditor(editor, contextMenu) {
-  let editorDoc = editor.editor.container.contentDocument;
-  let hidden = onPopupHidden(contextMenu);
-  contextMenu.hidePopup();
-  yield hidden;
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_delete_file.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/* 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 tree selection functionality
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  ok(true, "ProjectEditor has loaded");
-
-  let root = [...projecteditor.project.allStores()][0].root;
-  is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
-  for (let child of root.children) {
-    yield deleteWithContextMenu(projecteditor, projecteditor.projectTree.getViewContainer(child));
-  }
-
-  yield testDeleteOnRoot(projecteditor, projecteditor.projectTree.getViewContainer(root));
-});
-
-
-function openContextMenuOn(node) {
-  EventUtils.synthesizeMouseAtCenter(
-    node,
-    {button: 2, type: "contextmenu"},
-    node.ownerDocument.defaultView
-  );
-}
-
-function* testDeleteOnRoot(projecteditor, container) {
-  let popup = projecteditor.contextMenuPopup;
-  let oncePopupShown = onPopupShow(popup);
-  openContextMenuOn(container.label);
-  yield oncePopupShown;
-
-  let deleteCommand = popup.querySelector("[command=cmd-delete]");
-  ok(deleteCommand, "Delete command exists in popup");
-  is(deleteCommand.getAttribute("hidden"), "true", "Delete command is hidden");
-}
-
-function deleteWithContextMenu(projecteditor, container) {
-  let defer = promise.defer();
-
-  let popup = projecteditor.contextMenuPopup;
-  let resource = container.resource;
-  info("Going to attempt deletion for: " + resource.path);
-
-  onPopupShow(popup).then(function () {
-    let deleteCommand = popup.querySelector("[command=cmd-delete]");
-    ok(deleteCommand, "Delete command exists in popup");
-    is(deleteCommand.getAttribute("hidden"), "", "Delete command is visible");
-    is(deleteCommand.getAttribute("disabled"), "", "Delete command is enabled");
-
-    function onConfirmShown(aSubject) {
-      info("confirm dialog observed as expected");
-      Services.obs.removeObserver(onConfirmShown, "common-dialog-loaded");
-      Services.obs.removeObserver(onConfirmShown, "tabmodal-dialog-loaded");
-
-      projecteditor.project.on("refresh-complete", function refreshComplete() {
-        projecteditor.project.off("refresh-complete", refreshComplete);
-        OS.File.stat(resource.path).then(() => {
-          ok(false, "The file was not deleted");
-          defer.resolve();
-        }, (ex) => {
-          ok(ex instanceof OS.File.Error && ex.becauseNoSuchFile, "OS.File.stat promise was rejected because the file is gone");
-          defer.resolve();
-        });
-      });
-
-      // Click the 'OK' button
-      aSubject.Dialog.ui.button0.click();
-    }
-
-    Services.obs.addObserver(onConfirmShown, "common-dialog-loaded");
-    Services.obs.addObserver(onConfirmShown, "tabmodal-dialog-loaded");
-
-    deleteCommand.click();
-    popup.hidePopup();
-  });
-
-  openContextMenuOn(container.label);
-
-  return defer.promise;
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_editing_01.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* 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";
-
-//
-// Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
-
-loadHelperScript("helper_edits.js");
-
-// Test ProjectEditor basic functionality
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = projecteditor.project.allPaths()[0];
-
-  is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
-
-  ok(projecteditor.currentEditor, "There is an editor for projecteditor");
-  let resources = projecteditor.project.allResources();
-
-  for (let data of helperEditData) {
-    info("Processing " + data.path);
-    let resource = resources.filter(r=>r.basename === data.basename)[0];
-    yield selectFile(projecteditor, resource);
-    yield testEditFile(projecteditor, getTempFile(data.path).path, data.newContent);
-  }
-});
-
-function* testEditFile(projecteditor, filePath, newData) {
-  info("Testing file editing for: " + filePath);
-
-  let initialData = yield getFileData(filePath);
-  let editor = projecteditor.currentEditor;
-  let resource = projecteditor.resourceFor(editor);
-  let viewContainer = projecteditor.projectTree.getViewContainer(resource);
-  let originalTreeLabel = viewContainer.label.textContent;
-
-  is(resource.path, filePath, "Resource path is set correctly");
-  is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
-
-  info("Setting text in the editor and doing checks before saving");
-
-  editor.editor.undo();
-  editor.editor.undo();
-  is(editor.editor.getText(), initialData, "Editor is still loaded with correct contents after undo");
-
-  editor.editor.setText(newData);
-  is(editor.editor.getText(), newData, "Editor has been filled with new data");
-  is(viewContainer.label.textContent, "*" + originalTreeLabel, "Label is marked as changed");
-
-  info("Saving the editor and checking to make sure the file gets saved on disk");
-
-  editor.save(resource);
-
-  let savedResource = yield onceEditorSave(projecteditor);
-
-  is(viewContainer.label.textContent, originalTreeLabel, "Label is unmarked as changed");
-  is(savedResource.path, filePath, "The saved resouce path matches the original file path");
-  is(savedResource, resource, "The saved resource is the same as the original resource");
-
-  let savedData = yield getFileData(filePath);
-  is(savedData, newData, "Data has been correctly saved to disk");
-
-  info("Finished checking saving for " + filePath);
-
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_editors_image.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/* 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";
-
-//
-// Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
-
-loadHelperScript("helper_edits.js");
-
-// Test ProjectEditor image editor functionality
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = projecteditor.project.allPaths()[0];
-
-  is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
-
-  ok(projecteditor.currentEditor, "There is an editor for projecteditor");
-  let resources = projecteditor.project.allResources();
-
-  let helperImageData = [
-    {
-      basename: "16x16.png",
-      path: "img/icons/16x16.png"
-    },
-    {
-      basename: "32x32.png",
-      path: "img/icons/32x32.png"
-    },
-    {
-      basename: "128x128.png",
-      path: "img/icons/128x128.png"
-    },
-  ];
-
-  for (let data of helperImageData) {
-    info("Processing " + data.path);
-    let resource = resources.filter(r=>r.basename === data.basename)[0];
-    yield selectFile(projecteditor, resource);
-    yield testEditor(projecteditor, getTempFile(data.path).path);
-  }
-});
-
-function* testEditor(projecteditor, filePath) {
-  info("Testing file editing for: " + filePath);
-
-  let editor = projecteditor.currentEditor;
-  let resource = projecteditor.resourceFor(editor);
-
-  is(resource.path, filePath, "Resource path is set correctly");
-
-  let images = editor.elt.querySelectorAll("image");
-  is(images.length, 1, "There is one image inside the editor");
-  is(images[0], editor.image, "The image property is set correctly with the DOM");
-  is(editor.image.getAttribute("src"), resource.uri, "The image has the resource URL");
-
-  info("Selecting another resource, then reselecting this one");
-  projecteditor.projectTree.selectResource(resource.store.root);
-  yield onceEditorActivated(projecteditor);
-  projecteditor.projectTree.selectResource(resource);
-  yield onceEditorActivated(projecteditor);
-
-  editor = projecteditor.currentEditor;
-  images = editor.elt.querySelectorAll("image");
-  ok(images.length, 1, "There is one image inside the editor");
-  is(images[0], editor.image, "The image property is set correctly with the DOM");
-  is(editor.image.getAttribute("src"), resource.uri, "The image has the resource URL");
-
-  info("Finished checking saving for " + filePath);
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_external_change.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* 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";
-
-loadHelperScript("helper_edits.js");
-
-// Test ProjectEditor reaction to external changes (made outside of the)
-// editor.
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = projecteditor.project.allPaths()[0];
-
-  is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
-
-  ok(projecteditor.currentEditor, "There is an editor for projecteditor");
-  let resources = projecteditor.project.allResources();
-
-  for (let data of helperEditData) {
-    info("Processing " + data.path);
-    let resource = resources.filter(r=>r.basename === data.basename)[0];
-    yield selectFile(projecteditor, resource);
-    yield testChangeFileExternally(projecteditor, getTempFile(data.path).path, data.newContent);
-    yield testChangeUnsavedFileExternally(projecteditor, getTempFile(data.path).path, data.newContent + "[changed]");
-  }
-});
-
-function* testChangeUnsavedFileExternally(projecteditor, filePath, newData) {
-  info("Testing file external changes for: " + filePath);
-
-  let editor = projecteditor.currentEditor;
-  let resource = projecteditor.resourceFor(editor);
-  let initialData = yield getFileData(filePath);
-
-  is(resource.path, filePath, "Resource path is set correctly");
-  is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
-
-  info("Editing but not saving file in project editor");
-  ok(editor.isClean(), "Editor is clean");
-  editor.editor.setText("foobar");
-  ok(!editor.isClean(), "Editor is dirty");
-
-  info("Editor has been selected, writing to file externally");
-  yield writeToFile(resource.path, newData);
-
-  info("Selecting another resource, then reselecting this one");
-  projecteditor.projectTree.selectResource(resource.store.root);
-  yield onceEditorActivated(projecteditor);
-  projecteditor.projectTree.selectResource(resource);
-  yield onceEditorActivated(projecteditor);
-
-  editor = projecteditor.currentEditor;
-  info("Checking to make sure the editor is now populated correctly");
-  is(editor.editor.getText(), "foobar", "Editor has not been updated with new file contents");
-
-  info("Finished checking saving for " + filePath);
-}
-
-function* testChangeFileExternally(projecteditor, filePath, newData) {
-  info("Testing file external changes for: " + filePath);
-
-  let editor = projecteditor.currentEditor;
-  let resource = projecteditor.resourceFor(editor);
-  let initialData = yield getFileData(filePath);
-
-  is(resource.path, filePath, "Resource path is set correctly");
-  is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
-
-  info("Editor has been selected, writing to file externally");
-  yield writeToFile(resource.path, newData);
-
-  info("Selecting another resource, then reselecting this one");
-  projecteditor.projectTree.selectResource(resource.store.root);
-  yield onceEditorActivated(projecteditor);
-  projecteditor.projectTree.selectResource(resource);
-  yield onceEditorActivated(projecteditor);
-
-  editor = projecteditor.currentEditor;
-  info("Checking to make sure the editor is now populated correctly");
-  is(editor.editor.getText(), newData, "Editor has been updated with correct file contents");
-
-  info("Finished checking saving for " + filePath);
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_immediate_destroy.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/* 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";
-
-//
-// Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.window is null");
-
-// Test that projecteditor can be destroyed in various states of loading
-// without causing any leaks or exceptions.
-
-add_task(function* () {
-
-  info("Testing tab closure when projecteditor is in various states");
-  let loaderUrl = "chrome://mochitests/content/browser/devtools/client/projecteditor/test/projecteditor-test.xul";
-
-  yield addTab(loaderUrl).then(() => {
-    let iframe = content.document.getElementById("projecteditor-iframe");
-    ok(iframe, "Tab has placeholder iframe for projecteditor");
-
-    info("Closing the tab without doing anything");
-    gBrowser.removeCurrentTab();
-  });
-
-  yield addTab(loaderUrl).then(() => {
-    let iframe = content.document.getElementById("projecteditor-iframe");
-    ok(iframe, "Tab has placeholder iframe for projecteditor");
-
-    let projecteditor = ProjectEditor.ProjectEditor();
-    ok(projecteditor, "ProjectEditor has been initialized");
-
-    info("Closing the tab before attempting to load");
-    gBrowser.removeCurrentTab();
-  });
-
-  yield addTab(loaderUrl).then(() => {
-    let iframe = content.document.getElementById("projecteditor-iframe");
-    ok(iframe, "Tab has placeholder iframe for projecteditor");
-
-    let projecteditor = ProjectEditor.ProjectEditor();
-    ok(projecteditor, "ProjectEditor has been initialized");
-
-    projecteditor.load(iframe);
-
-    info("Closing the tab after a load is requested, but before load is finished");
-    gBrowser.removeCurrentTab();
-  });
-
-  yield addTab(loaderUrl).then(() => {
-    let iframe = content.document.getElementById("projecteditor-iframe");
-    ok(iframe, "Tab has placeholder iframe for projecteditor");
-
-    let projecteditor = ProjectEditor.ProjectEditor();
-    ok(projecteditor, "ProjectEditor has been initialized");
-
-    return projecteditor.load(iframe).then(() => {
-      info("Closing the tab after a load has been requested and finished");
-      gBrowser.removeCurrentTab();
-    });
-  });
-
-  yield addTab(loaderUrl).then(() => {
-    let iframe = content.document.getElementById("projecteditor-iframe");
-    ok(iframe, "Tab has placeholder iframe for projecteditor");
-
-    let projecteditor = ProjectEditor.ProjectEditor(iframe);
-    ok(projecteditor, "ProjectEditor has been initialized");
-
-    let loadedDone = promise.defer();
-    projecteditor.loaded.then(() => {
-      ok(false, "Loaded has finished after destroy() has been called");
-      loadedDone.resolve();
-    }, () => {
-      ok(true, "Loaded has been rejected after destroy() has been called");
-      loadedDone.resolve();
-    });
-
-    projecteditor.destroy();
-
-    return loadedDone.promise.then(() => {
-      gBrowser.removeCurrentTab();
-    });
-  });
-
-  finish();
-});
-
-
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_init.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* 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 that projecteditor can be initialized.
-
-function test() {
-  info("Initializing projecteditor");
-  addProjectEditorTab().then((projecteditor) => {
-    ok(projecteditor, "Load callback has been called");
-    ok(projecteditor.shells, "ProjectEditor has shells");
-    ok(projecteditor.project, "ProjectEditor has a project");
-    finish();
-  });
-}
-
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_menubar_01.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* 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 that menu bar appends to the correct document.
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory({
-    menubar: false
-  });
-  ok(projecteditor, "ProjectEditor has loaded");
-
-  let fileMenu = projecteditor.document.getElementById("file-menu");
-  let editMenu = projecteditor.document.getElementById("edit-menu");
-  ok(fileMenu, "The menu has loaded in the projecteditor document");
-  ok(editMenu, "The menu has loaded in the projecteditor document");
-
-  let projecteditor2 = yield addProjectEditorTabForTempDirectory();
-  let menubar = projecteditor2.menubar;
-  fileMenu = projecteditor2.document.getElementById("file-menu");
-  editMenu = projecteditor2.document.getElementById("edit-menu");
-  ok(!fileMenu, "The menu has NOT loaded in the projecteditor document");
-  ok(!editMenu, "The menu has NOT loaded in the projecteditor document");
-  ok(content.document.querySelector("#file-menu"), "The menu has loaded in the specified element");
-  ok(content.document.querySelector("#edit-menu"), "The menu has loaded in the specified element");
-});
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_menubar_02.js
+++ /dev/null
@@ -1,123 +0,0 @@
-/* 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";
-
-loadHelperScript("helper_edits.js");
-
-// Test menu bar enabled / disabled state.
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let menubar = projecteditor.menubar;
-
-  // Update menu items for a clean slate, so previous tests cannot
-  // affect paste, and possibly other side effects
-  projecteditor._updateMenuItems();
-
-  // let projecteditor = yield addProjectEditorTabForTempDirectory();
-  ok(projecteditor, "ProjectEditor has loaded");
-
-  let fileMenu = menubar.querySelector("#file-menu");
-  let editMenu = menubar.querySelector("#edit-menu");
-  ok(fileMenu, "The menu has loaded in the projecteditor document");
-  ok(editMenu, "The menu has loaded in the projecteditor document");
-
-  let cmdNew = fileMenu.querySelector("[command=cmd-new]");
-  let cmdSave = fileMenu.querySelector("[command=cmd-save]");
-  let cmdSaveas = fileMenu.querySelector("[command=cmd-saveas]");
-
-  let cmdUndo = editMenu.querySelector("[command=cmd_undo]");
-  let cmdRedo = editMenu.querySelector("[command=cmd_redo]");
-  let cmdCut = editMenu.querySelector("[command=cmd_cut]");
-  let cmdCopy = editMenu.querySelector("[command=cmd_copy]");
-  let cmdPaste = editMenu.querySelector("[command=cmd_paste]");
-
-  info("Checking initial state of menus");
-  yield openAndCloseMenu(fileMenu);
-  yield openAndCloseMenu(editMenu);
-
-  is(cmdNew.getAttribute("disabled"), "", "File menu item is enabled");
-  is(cmdSave.getAttribute("disabled"), "true", "File menu item is disabled");
-  is(cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled");
-
-  is(cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled");
-
-  projecteditor.menuEnabled = false;
-
-  info("Checking with menuEnabled = false");
-  yield openAndCloseMenu(fileMenu);
-  yield openAndCloseMenu(editMenu);
-
-  is(cmdNew.getAttribute("disabled"), "true", "File menu item is disabled");
-  is(cmdSave.getAttribute("disabled"), "true", "File menu item is disabled");
-  is(cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled");
-
-  is(cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled");
-
-  info("Checking with menuEnabled=true");
-  projecteditor.menuEnabled = true;
-
-  yield openAndCloseMenu(fileMenu);
-  yield openAndCloseMenu(editMenu);
-
-  is(cmdNew.getAttribute("disabled"), "", "File menu item is enabled");
-  is(cmdSave.getAttribute("disabled"), "true", "File menu item is disabled");
-  is(cmdSaveas.getAttribute("disabled"), "true", "File menu item is disabled");
-
-  is(cmdUndo.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdPaste.getAttribute("disabled"), "true", "Edit menu item is disabled");
-
-  info("Checking with resource selected");
-  let resource = projecteditor.project.allResources()[2];
-  yield selectFile(projecteditor, resource);
-  let editor = projecteditor.currentEditor;
-
-  let onChange = promise.defer();
-
-  projecteditor.on("onEditorChange", () => {
-    info("onEditorChange has been detected");
-    onChange.resolve();
-  });
-  editor.editor.focus();
-  EventUtils.synthesizeKey("f", { }, projecteditor.window);
-
-  yield onChange;
-  yield openAndCloseMenu(fileMenu);
-  yield openAndCloseMenu(editMenu);
-
-  is(cmdNew.getAttribute("disabled"), "", "File menu item is enabled");
-  is(cmdSave.getAttribute("disabled"), "", "File menu item is enabled");
-  is(cmdSaveas.getAttribute("disabled"), "", "File menu item is enabled");
-
-  // Use editor.canUndo() to see if this is failing - the menu disabled property
-  // should be in sync with this because of isCommandEnabled in editor.js.
-  info('cmdUndo.getAttribute("disabled") is: "' + cmdUndo.getAttribute("disabled") + '"');
-  ok(editor.editor.canUndo(), "Edit menu item is enabled");
-
-  is(cmdRedo.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCut.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdCopy.getAttribute("disabled"), "true", "Edit menu item is disabled");
-  is(cmdPaste.getAttribute("disabled"), "", "Edit menu item is enabled");
-});
-
-function* openAndCloseMenu(menu) {
-  let shown = onPopupShow(menu);
-  EventUtils.synthesizeMouseAtCenter(menu, {}, menu.ownerDocument.defaultView);
-  yield shown;
-  let hidden = onPopupHidden(menu);
-  EventUtils.synthesizeMouseAtCenter(menu, {}, menu.ownerDocument.defaultView);
-  yield hidden;
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_new_file.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/* 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 tree selection functionality
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  ok(projecteditor, "ProjectEditor has loaded");
-
-});
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_rename_file_01.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* 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 file rename functionality
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  ok(true, "ProjectEditor has loaded");
-
-  let root = [...projecteditor.project.allStores()][0].root;
-  is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
-  for (let child of root.children) {
-    yield renameWithContextMenu(projecteditor,
-      projecteditor.projectTree.getViewContainer(child), ".renamed");
-  }
-});
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_rename_file_02.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/* 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 file rename functionality with non ascii characters
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  ok(true, "ProjectEditor has loaded");
-
-  let root = [...projecteditor.project.allStores()][0].root;
-  is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
-
-  let childrenList = [];
-  for (let child of root.children) {
-    yield renameWithContextMenu(projecteditor,
-      projecteditor.projectTree.getViewContainer(child), ".ren\u0061\u0308med");
-    childrenList.push(child.basename + ".ren\u0061\u0308med");
-  }
-  for (let child of root.children) {
-    is(childrenList.indexOf(child.basename) == -1, false,
-        "Failed to update tree with non-ascii character");
-  }
-});
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_saveall.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/* 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";
-
-//
-// Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
-
-loadHelperScript("helper_edits.js");
-
-// Test ProjectEditor basic functionality
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = projecteditor.project.allPaths()[0];
-
-  is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
-
-  ok(projecteditor.currentEditor, "There is an editor for projecteditor");
-  let resources = projecteditor.project.allResources();
-
-  for (let data of helperEditData) {
-    info("Processing " + data.path);
-    let resource = resources.filter(r=>r.basename === data.basename)[0];
-    yield selectFile(projecteditor, resource);
-    yield editFile(projecteditor, getTempFile(data.path).path, data.newContent);
-  }
-
-  info("Saving all resources");
-  ok(projecteditor.hasUnsavedResources, "hasUnsavedResources");
-  yield projecteditor.saveAllFiles();
-  ok(!projecteditor.hasUnsavedResources, "!hasUnsavedResources");
-  for (let data of helperEditData) {
-    let filePath = getTempFile(data.path).path;
-    info("Asserting that data at " + filePath + " has been saved");
-    let resource = resources.filter(r=>r.basename === data.basename)[0];
-    yield selectFile(projecteditor, resource);
-    let editor = projecteditor.currentEditor;
-    let savedData = yield getFileData(filePath);
-    is(savedData, data.newContent, "Data has been correctly saved to disk");
-  }
-});
-
-function* editFile(projecteditor, filePath, newData) {
-  info("Testing file editing for: " + filePath);
-
-  let initialData = yield getFileData(filePath);
-  let editor = projecteditor.currentEditor;
-  let resource = projecteditor.resourceFor(editor);
-  let viewContainer = projecteditor.projectTree.getViewContainer(resource);
-  let originalTreeLabel = viewContainer.label.textContent;
-
-  is(resource.path, filePath, "Resource path is set correctly");
-  is(editor.editor.getText(), initialData, "Editor is loaded with correct file contents");
-
-  info("Setting text in the editor");
-
-  editor.editor.setText(newData);
-  is(editor.editor.getText(), newData, "Editor has been filled with new data");
-  is(viewContainer.label.textContent, "*" + originalTreeLabel, "Label is marked as changed");
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_stores.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* 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 ProjectEditor basic functionality
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = projecteditor.project.allPaths()[0];
-  is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
-
-  is(projecteditor.project.allPaths().length, 1, "1 path is set");
-  projecteditor.project.removeAllStores();
-  is(projecteditor.project.allPaths().length, 0, "No paths are remaining");
-});
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_01.js
+++ /dev/null
@@ -1,98 +0,0 @@
-/* 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 tree selection functionality
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = projecteditor.project.allPaths()[0];
-
-  is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
-
-  ok(projecteditor.currentEditor, "There is an editor for projecteditor");
-  let resources = projecteditor.project.allResources();
-
-  is(
-    resources.map(r=>r.basename).join("|"),
-    TEMP_FOLDER_NAME + "|css|styles.css|data|img|icons|128x128.png|16x16.png|32x32.png|vector.svg|fake.png|js|script.js|index.html|LICENSE|README.md",
-    "Resources came through in proper order"
-  );
-
-  for (let i = 0; i < resources.length; i++) {
-    yield selectFileFirstLoad(projecteditor, resources[i]);
-  }
-  for (let i = 0; i < resources.length; i++) {
-    yield selectFileSubsequentLoad(projecteditor, resources[i]);
-  }
-  for (let i = 0; i < resources.length; i++) {
-    yield selectFileSubsequentLoad(projecteditor, resources[i]);
-  }
-});
-
-function* selectFileFirstLoad(projecteditor, resource) {
-  ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
-  projecteditor.projectTree.selectResource(resource);
-  let container = projecteditor.projectTree.getViewContainer(resource);
-
-  if (resource.isRoot) {
-    ok(container.expanded, "The root directory is expanded by default.");
-    container.line.click();
-    ok(container.expanded, "Clicking on the line does not toggles expansion.");
-    return;
-  }
-  if (resource.isDir) {
-    ok(!container.expanded, "A directory is not expanded by default.");
-    container.line.click();
-    ok(container.expanded, "Clicking on the line toggles expansion.");
-    container.line.click();
-    ok(!container.expanded, "Clicking on the line toggles expansion.");
-    return;
-  }
-
-  let [editorCreated, editorLoaded, editorActivated] = yield promise.all([
-    onceEditorCreated(projecteditor),
-    onceEditorLoad(projecteditor),
-    onceEditorActivated(projecteditor)
-  ]);
-
-  is(editorCreated, projecteditor.currentEditor, "Editor has been created for " + resource.path);
-  is(editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
-  is(editorLoaded, projecteditor.currentEditor, "Editor has been loaded for " + resource.path);
-}
-
-function* selectFileSubsequentLoad(projecteditor, resource) {
-  ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
-  projecteditor.projectTree.selectResource(resource);
-
-  if (resource.isDir) {
-    return;
-  }
-
-  // Make sure text editors are focused immediately when selected.
-  let focusPromise = promise.resolve();
-  if (projecteditor.currentEditor.editor) {
-    focusPromise = onEditorFocus(projecteditor.currentEditor);
-  }
-
-  // Only activated should fire the next time
-  // (may add load() if we begin checking for changes from disk)
-  let [editorActivated] = yield promise.all([
-    onceEditorActivated(projecteditor)
-  ]);
-
-  is(editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
-
-  yield focusPromise;
-}
-
-function onEditorFocus(editor) {
-  let def = promise.defer();
-  editor.on("focus", function focus() {
-    editor.off("focus", focus);
-    def.resolve();
-  });
-  return def.promise;
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/browser_projecteditor_tree_selection_02.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/* 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";
-
-//
-// Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("destroy");
-
-// Test that files get reselected in the tree when their editor
-// is focused.  https://bugzilla.mozilla.org/show_bug.cgi?id=1011116.
-
-add_task(function* () {
-  let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = projecteditor.project.allPaths()[0];
-
-  is(getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
-
-  ok(projecteditor.currentEditor, "There is an editor for projecteditor");
-  let resources = projecteditor.project.allResources();
-
-  is(
-    resources.map(r=>r.basename).join("|"),
-    TEMP_FOLDER_NAME + "|css|styles.css|data|img|icons|128x128.png|16x16.png|32x32.png|vector.svg|fake.png|js|script.js|index.html|LICENSE|README.md",
-    "Resources came through in proper order"
-  );
-
-  for (let i = 0; i < resources.length; i++) {
-    yield selectAndRefocusFile(projecteditor, resources[i]);
-  }
-});
-
-function* selectAndRefocusFile(projecteditor, resource) {
-  ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
-  projecteditor.projectTree.selectResource(resource);
-
-  if (resource.isDir) {
-    return;
-  }
-
-  let [editorCreated, editorLoaded, editorActivated] = yield promise.all([
-    onceEditorCreated(projecteditor),
-    onceEditorLoad(projecteditor),
-    onceEditorActivated(projecteditor)
-  ]);
-
-  if (projecteditor.currentEditor.editor) {
-    // This is a text editor.  Go ahead and select a directory then refocus
-    // the editor to make sure it is reselected in tree.
-    let treeContainer = projecteditor.projectTree.getViewContainer(getDirectoryInStore(resource));
-    treeContainer.line.click();
-    EventUtils.synthesizeMouseAtCenter(treeContainer.elt, {}, treeContainer.elt.ownerDocument.defaultView);
-    let waitForTreeSelect = onTreeSelection(projecteditor);
-    projecteditor.currentEditor.focus();
-    yield waitForTreeSelect;
-
-    is(projecteditor.projectTree.getSelectedResource(), resource, "The resource gets reselected in the tree");
-  }
-}
-
-// Return a directory to select in the tree.
-function getDirectoryInStore(resource) {
-  return resource.store.root.childrenSorted.filter(r=>r.isDir)[0];
-}
-
-function onTreeSelection(projecteditor) {
-  let def = promise.defer();
-  projecteditor.projectTree.on("selection", function selection() {
-    projecteditor.projectTree.off("focus", selection);
-    def.resolve();
-  });
-  return def.promise;
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/head.js
+++ /dev/null
@@ -1,389 +0,0 @@
-/* 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/. */
-
-var Cu = Components.utils;
-const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {TargetFactory} = require("devtools/client/framework/target");
-const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
-const promise = require("promise");
-const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
-const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
-const ProjectEditor = require("devtools/client/projecteditor/lib/projecteditor");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const flags = require("devtools/shared/flags");
-
-const TEST_URL_ROOT = "http://mochi.test:8888/browser/devtools/client/projecteditor/test/";
-const SAMPLE_WEBAPP_URL = TEST_URL_ROOT + "/helper_homepage.html";
-var TEMP_PATH;
-var TEMP_FOLDER_NAME = "ProjectEditor" + (new Date().getTime());
-
-// All test are asynchronous
-waitForExplicitFinish();
-
-// Uncomment this pref to dump all devtools emitted events to the console.
-// Services.prefs.setBoolPref("devtools.dump.emit", true);
-
-// Set the testing flag and reset it when the test ends
-flags.testing = true;
-registerCleanupFunction(() => flags.testing = false);
-
-// Clear preferences that may be set during the course of tests.
-registerCleanupFunction(() => {
-  // Services.prefs.clearUserPref("devtools.dump.emit");
-  TEMP_PATH = null;
-  TEMP_FOLDER_NAME = null;
-});
-
-// Auto close the toolbox and close the test tabs when the test ends
-registerCleanupFunction(() => {
-  try {
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.closeToolbox(target);
-  } catch (ex) {
-    dump(ex);
-  }
-  while (gBrowser.tabs.length > 1) {
-    gBrowser.removeCurrentTab();
-  }
-});
-
-/**
- * Add a new test tab in the browser and load the given url.
- * @param {String} url The url to be loaded in the new tab
- * @return a promise that resolves to the tab object when the url is loaded
- */
-function addTab(url) {
-  info("Adding a new tab with URL: '" + url + "'");
-  let def = promise.defer();
-
-  let tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url);
-  BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(function () {
-    info("URL '" + url + "' loading complete");
-    waitForFocus(() => {
-      def.resolve(tab);
-    }, content);
-  });
-
-  return def.promise;
-}
-
-/**
- * Some tests may need to import one or more of the test helper scripts.
- * A test helper script is simply a js file that contains common test code that
- * is either not common-enough to be in head.js, or that is located in a separate
- * directory.
- * The script will be loaded synchronously and in the test's scope.
- * @param {String} filePath The file path, relative to the current directory.
- *                 Examples:
- *                 - "helper_attributes_test_runner.js"
- *                 - "../../../commandline/test/helpers.js"
- */
-function loadHelperScript(filePath) {
-  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
-  Services.scriptloader.loadSubScript(testDir + "/" + filePath, this);
-}
-
-function addProjectEditorTabForTempDirectory(opts = {}) {
-  try {
-    TEMP_PATH = buildTempDirectoryStructure();
-  } catch (e) {
-    // Bug 1037292 - The test servers sometimes are unable to
-    // write to the temporary directory due to locked files
-    // or access denied errors.  Try again if this failed.
-    info("Project Editor temp directory creation failed.  Trying again.");
-    TEMP_PATH = buildTempDirectoryStructure();
-  }
-  let customOpts = {
-    name: "Test",
-    iconUrl: "chrome://devtools/skin/images/tool-options.svg",
-    projectOverviewURL: SAMPLE_WEBAPP_URL
-  };
-
-  info("Adding a project editor tab for editing at: " + TEMP_PATH);
-  return addProjectEditorTab(opts).then((projecteditor) => {
-    return projecteditor.setProjectToAppPath(TEMP_PATH, customOpts).then(() => {
-      return projecteditor;
-    });
-  });
-}
-
-function addProjectEditorTab(opts = {}) {
-  return addTab("chrome://mochitests/content/browser/devtools/client/projecteditor/test/projecteditor-test.xul").then(() => {
-    let iframe = content.document.getElementById("projecteditor-iframe");
-    if (opts.menubar !== false) {
-      opts.menubar = content.document.querySelector("menubar");
-    }
-    let projecteditor = ProjectEditor.ProjectEditor(iframe, opts);
-
-
-    ok(iframe, "Tab has placeholder iframe for projecteditor");
-    ok(projecteditor, "ProjectEditor has been initialized");
-
-    return projecteditor.loaded.then((projecteditor) => {
-      return projecteditor;
-    });
-  });
-}
-
-/**
- * Build a temporary directory as a workspace for this loader
- * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O
- */
-function buildTempDirectoryStructure() {
-
-  let dirName = TEMP_FOLDER_NAME;
-  info("Building a temporary directory at " + dirName);
-
-  // First create (and remove) the temp dir to discard any changes
-  let TEMP_DIR = FileUtils.getDir("TmpD", [dirName], true);
-  TEMP_DIR.remove(true);
-
-  // Now rebuild our fake project.
-  TEMP_DIR = FileUtils.getDir("TmpD", [dirName], true);
-
-  FileUtils.getDir("TmpD", [dirName, "css"], true);
-  FileUtils.getDir("TmpD", [dirName, "data"], true);
-  FileUtils.getDir("TmpD", [dirName, "img", "icons"], true);
-  FileUtils.getDir("TmpD", [dirName, "js"], true);
-
-  let htmlFile = FileUtils.getFile("TmpD", [dirName, "index.html"]);
-  htmlFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  writeToFileSync(htmlFile, [
-    "<!DOCTYPE html>",
-    '<html lang="en">',
-    " <head>",
-    '   <meta charset="utf-8" />',
-    "   <title>ProjectEditor Temp File</title>",
-    '   <link rel="stylesheet" href="style.css" />',
-    " </head>",
-    ' <body id="home">',
-    "   <p>ProjectEditor Temp File</p>",
-    " </body>",
-    "</html>"].join("\n")
-  );
-
-  let readmeFile = FileUtils.getFile("TmpD", [dirName, "README.md"]);
-  readmeFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  writeToFileSync(readmeFile, [
-    "## Readme"
-  ].join("\n")
-  );
-
-  let licenseFile = FileUtils.getFile("TmpD", [dirName, "LICENSE"]);
-  licenseFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  writeToFileSync(licenseFile, [
-    "/* 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/. */"
-  ].join("\n")
-  );
-
-  let cssFile = FileUtils.getFile("TmpD", [dirName, "css", "styles.css"]);
-  cssFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  writeToFileSync(cssFile, [
-    "body {",
-    " background: red;",
-    "}"
-  ].join("\n")
-  );
-
-  FileUtils.getFile("TmpD", [dirName, "js", "script.js"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-
-  FileUtils.getFile("TmpD", [dirName, "img", "fake.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  FileUtils.getFile("TmpD", [dirName, "img", "icons", "16x16.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  FileUtils.getFile("TmpD", [dirName, "img", "icons", "32x32.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  FileUtils.getFile("TmpD", [dirName, "img", "icons", "128x128.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-  FileUtils.getFile("TmpD", [dirName, "img", "icons", "vector.svg"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
-
-  return TEMP_DIR.path;
-}
-
-// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file
-function writeToFile(file, data) {
-  if (typeof file === "string") {
-    file = new FileUtils.File(file);
-  }
-  info("Writing to file: " + file.path + " (exists? " + file.exists() + ")");
-  let defer = promise.defer();
-  var ostream = FileUtils.openSafeFileOutputStream(file);
-
-  var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
-                  createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
-  converter.charset = "UTF-8";
-  var istream = converter.convertToInputStream(data);
-
-  // The last argument (the callback) is optional.
-  NetUtil.asyncCopy(istream, ostream, function (status) {
-    if (!Components.isSuccessCode(status)) {
-      // Handle error!
-      info("ERROR WRITING TEMP FILE", status);
-    }
-    defer.resolve();
-  });
-  return defer.promise;
-}
-
-// This is used when setting up the test.
-// You should typically use the async version of this, writeToFile.
-// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#More
-function writeToFileSync(file, data) {
-  // file is nsIFile, data is a string
-  var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
-                 createInstance(Components.interfaces.nsIFileOutputStream);
-
-  // use 0x02 | 0x10 to open file for appending.
-  foStream.init(file, 0x02 | 0x08 | 0x20, 0o666, 0);
-  // write, create, truncate
-  // In a c file operation, we have no need to set file mode with or operation,
-  // directly using "r" or "w" usually.
-
-  // if you are sure there will never ever be any non-ascii text in data you can
-  // also call foStream.write(data, data.length) directly
-  var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].
-                  createInstance(Components.interfaces.nsIConverterOutputStream);
-  converter.init(foStream, "UTF-8", 0, 0);
-  converter.writeString(data);
-  converter.close(); // this closes foStream
-}
-
-function getTempFile(path) {
-  let parts = [TEMP_FOLDER_NAME];
-  parts = parts.concat(path.split("/"));
-  return FileUtils.getFile("TmpD", parts);
-}
-
-// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file
-function* getFileData(file) {
-  if (typeof file === "string") {
-    file = new FileUtils.File(file);
-  }
-  let def = promise.defer();
-
-  NetUtil.asyncFetch({
-    uri: NetUtil.newURI(file),
-    loadUsingSystemPrincipal: true
-  }, function (inputStream, status) {
-    if (!Components.isSuccessCode(status)) {
-      info("ERROR READING TEMP FILE", status);
-    }
-
-    // Detect if an empty file is loaded
-    try {
-      inputStream.available();
-    } catch (e) {
-      def.resolve("");
-      return;
-    }
-
-    var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
-    def.resolve(data);
-  });
-
-  return def.promise;
-}
-
-/**
- * Rename the resource of the provided container using the context menu.
- *
- * @param {ProjectEditor} projecteditor the current project editor instance
- * @param {Shell} container for the resource to rename
- * @param {String} newName the name to use for renaming the resource
- * @return {Promise} a promise that resolves when the resource has been renamed
- */
-var renameWithContextMenu = Task.async(function* (projecteditor,
-                                                  container, newName) {
-  let popup = projecteditor.contextMenuPopup;
-  let resource = container.resource;
-  info("Going to attempt renaming for: " + resource.path);
-
-  let waitForPopupShow = onPopupShow(popup);
-  openContextMenu(container.label);
-  yield waitForPopupShow;
-
-  let renameCommand = popup.querySelector("[command=cmd-rename]");
-  ok(renameCommand, "Rename command exists in popup");
-  is(renameCommand.getAttribute("hidden"), "", "Rename command is visible");
-  is(renameCommand.getAttribute("disabled"), "", "Rename command is enabled");
-
-  renameCommand.click();
-  popup.hidePopup();
-  let input = container.elt.childNodes[0].childNodes[1];
-  input.value = resource.basename + newName;
-
-  let waitForProjectRefresh = onceProjectRefreshed(projecteditor);
-  EventUtils.synthesizeKey("VK_RETURN", {}, projecteditor.window);
-  yield waitForProjectRefresh;
-
-  try {
-    yield OS.File.stat(resource.path + newName);
-    ok(true, "File is renamed");
-  } catch (e) {
-    ok(false, "Failed to rename file");
-  }
-});
-
-function onceEditorCreated(projecteditor) {
-  let def = promise.defer();
-  projecteditor.once("onEditorCreated", (editor) => {
-    def.resolve(editor);
-  });
-  return def.promise;
-}
-
-function onceEditorLoad(projecteditor) {
-  let def = promise.defer();
-  projecteditor.once("onEditorLoad", (editor) => {
-    def.resolve(editor);
-  });
-  return def.promise;
-}
-
-function onceEditorActivated(projecteditor) {
-  let def = promise.defer();
-  projecteditor.once("onEditorActivated", (editor) => {
-    def.resolve(editor);
-  });
-  return def.promise;
-}
-
-function onceEditorSave(projecteditor) {
-  let def = promise.defer();
-  projecteditor.once("onEditorSave", (editor, resource) => {
-    def.resolve(resource);
-  });
-  return def.promise;
-}
-
-function onceProjectRefreshed(projecteditor) {
-  return new Promise(resolve => {
-    projecteditor.project.on("refresh-complete", function refreshComplete() {
-      projecteditor.project.off("refresh-complete", refreshComplete);
-      resolve();
-    });
-  });
-}
-
-function onPopupShow(menu) {
-  let defer = promise.defer();
-  menu.addEventListener("popupshown", function () {
-    defer.resolve();
-  }, {once: true});
-  return defer.promise;
-}
-
-function onPopupHidden(menu) {
-  let defer = promise.defer();
-  menu.addEventListener("popuphidden", function () {
-    defer.resolve();
-  }, {once: true});
-  return defer.promise;
-}
-
-function openContextMenu(node) {
-  EventUtils.synthesizeMouseAtCenter(
-    node,
-    {button: 2, type: "contextmenu"},
-    node.ownerDocument.defaultView
-  );
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/helper_edits.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/* 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 helperEditData = [
-  {
-    basename: "styles.css",
-    path: "css/styles.css",
-    newContent: "body,html { color: orange; }"
-  },
-  {
-    basename: "index.html",
-    path: "index.html",
-    newContent: "<h1>Changed Content Again</h1>"
-  },
-  {
-    basename: "LICENSE",
-    path: "LICENSE",
-    newContent: "My new license"
-  },
-  {
-    basename: "README.md",
-    path: "README.md",
-    newContent: "My awesome readme"
-  },
-  {
-    basename: "script.js",
-    path: "js/script.js",
-    newContent: "alert('hi')"
-  },
-  {
-    basename: "vector.svg",
-    path: "img/icons/vector.svg",
-    newContent: "<svg></svg>"
-  },
-];
-
-function* selectFile(projecteditor, resource) {
-  ok(resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
-  projecteditor.projectTree.selectResource(resource);
-
-  if (resource.isDir) {
-    return;
-  }
-
-  let [editorActivated] = yield promise.all([
-    onceEditorActivated(projecteditor)
-  ]);
-
-  is(editorActivated, projecteditor.currentEditor, "Editor has been activated for " + resource.path);
-}
deleted file mode 100644
--- a/devtools/client/projecteditor/test/helper_homepage.html
+++ /dev/null
@@ -1,1 +0,0 @@
-<h1>ProjectEditor tests</h1>
\ No newline at end of file
deleted file mode 100644
--- a/devtools/client/projecteditor/test/projecteditor-test.xul
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
-<?xml-stylesheet href="chrome://global/skin/global.css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml">
-
-  <script type="application/javascript" src="chrome://global/content/globalOverlay.js"></script>
-
-  <commandset id="mainCommandSet">
-    <commandset id="editMenuCommands"/>
-  </commandset>
-  <menubar></menubar>
-  <iframe id='projecteditor-iframe' flex="1"></iframe>
-</window>
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -153,23 +153,16 @@ Telemetry.prototype = {
     aboutdebugging: {
       histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
       timerHistogram: "DEVTOOLS_ABOUTDEBUGGING_TIME_ACTIVE_SECONDS"
     },
     webide: {
       histogram: "DEVTOOLS_WEBIDE_OPENED_COUNT",
       timerHistogram: "DEVTOOLS_WEBIDE_TIME_ACTIVE_SECONDS"
     },
-    webideProjectEditor: {
-      histogram: "DEVTOOLS_WEBIDE_PROJECT_EDITOR_OPENED_COUNT",
-      timerHistogram: "DEVTOOLS_WEBIDE_PROJECT_EDITOR_TIME_ACTIVE_SECONDS"
-    },
-    webideProjectEditorSave: {
-      histogram: "DEVTOOLS_WEBIDE_PROJECT_EDITOR_SAVE_COUNT",
-    },
     webideNewProject: {
       histogram: "DEVTOOLS_WEBIDE_NEW_PROJECT_COUNT",
     },
     webideImportProject: {
       histogram: "DEVTOOLS_WEBIDE_IMPORT_PROJECT_COUNT",
     },
     custom: {
       histogram: "DEVTOOLS_CUSTOM_OPENED_COUNT",
deleted file mode 100644
--- a/devtools/client/themes/projecteditor/projecteditor.css
+++ /dev/null
@@ -1,184 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-.view-project-detail {
-  overflow: auto;
-}
-
-.plugin-hidden {
-  display: none;
-}
-
-.arrow {
-  -moz-appearance: treetwisty;
-  width: 20px;
-  height: 20px;
-}
-
-.arrow[open] {
-  -moz-appearance: treetwistyopen;
-}
-
-.arrow[invisible] {
-  visibility: hidden;
-}
-
-#projecteditor-menubar {
-  display: none;
-}
-
-#projecteditor-toolbar,
-#projecteditor-toolbar-bottom {
-  display: none; /* For now don't show the status bars */
-  min-height: 22px;
-  height: 22px;
-  background: rgb(237, 237, 237);
-}
-
-#sources {
-  overflow: auto;
-}
-
-.sources-tree {
-  overflow:auto;
-  overflow-x: hidden;
-  -moz-user-focus: normal;
-
-  /* Allows this to expand inside of parent xul element, while
-     still supporting child flexbox elements, including ellipses. */
-  -moz-box-flex: 1;
-  display: block;
-}
-
-.sources-tree input {
-  margin: -1px;
-  border: 1px solid gray;
-}
-
-#main-deck .sources-tree {
-  background: rgb(225, 225, 225);
-  min-width: 100px;
-}
-
-.entry {
-  color: #18191A;
-  display: flex;
-  align-items: center;
-}
-
-.entry .file-label {
-  display: flex;
-  flex: 1;
-  align-items: center;
-}
-
-.entry {
-  border: none;
-  box-shadow: none;
-  white-space: nowrap;
-  cursor: pointer;
-}
-
-.entry:hover:not(.entry-group-title):not(.selected) {
-  background: rgba(0, 0, 0, .05);
-}
-
-.entry.selected {
-  background: rgba(56, 117, 215, 1);
-  color: #F5F7FA;
-  outline: none;
-}
-
-.entry-group-title {
-  background: rgba(56, 117, 215, 0.8);
-  color: #F5F7FA;
-  font-weight: bold;
-  font-size: 1.05em;
-  line-height: 35px;
-  padding: 0 10px;
-}
-
-.sources-tree .entry-group-title .expander {
-  display: none;
-}
-
-.entry .expander {
-  width: 16px;
-  padding: 0;
-}
-
-.tree-collapsed .children {
-  display: none;
-}
-
-/* Plugins */
-
-#projecteditor-toolbar textbox {
-  margin: 0;
-}
-
-.projecteditor-basic-display {
-  padding: 0 3px;
-}
-
-/* App Manager */
-.project-name-label {
-  font-weight: bold;
-  padding-left: 10px;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-
-.project-flex {
-  flex: 1;
-}
-
-.project-image {
-  max-height: 25px;
-  margin-left: -10px;
-}
-
-.project-image,
-.project-status,
-.project-options {
-  flex-shrink: 0;
-}
-
-.project-status {
-  width: 10px;
-  height: 10px;
-  border-radius: 50%;
-  border: solid 1px rgba(255, 255, 255, .5);
-  margin-right: 10px;
-  visibility: hidden;
-}
-
-.project-status[status=valid] {
-  background: #70bf53;
-  visibility: visible;
-}
-
-.project-status[status=warning] {
-  background: #d99b28;
-  visibility: visible;
-}
-
-.project-status[status=error] {
-  background: #ed2655;
-  visibility: visible;
-}
-
-/* Status Bar */
-.projecteditor-file-label {
-  font-weight: bold;
-  padding-left: 29px;
-  padding-right: 10px;
-  flex: 1;
-}
-
-/* Image View */
-.editor-image {
-  padding: 10px;
-}
--- a/devtools/client/webide/content/prefs.xhtml
+++ b/devtools/client/webide/content/prefs.xhtml
@@ -24,22 +24,16 @@
     </div>
 
     <h1>&prefs_title;</h1>
 
     <h2>&prefs_general_title;</h2>
 
     <ul>
       <li>
-        <label title="&prefs_options_showeditor_tooltip;">
-          <input type="checkbox" data-pref="devtools.webide.showProjectEditor"/>
-          <span>&prefs_options_showeditor;</span>
-        </label>
-      </li>
-      <li>
         <label title="&prefs_options_rememberlastproject_tooltip;">
           <input type="checkbox" data-pref="devtools.webide.restoreLastProject"/>
           <span>&prefs_options_rememberlastproject;</span>
         </label>
       </li>
       <li>
         <label title="&prefs_options_autoconnectruntime_tooltip;">
           <input type="checkbox" data-pref="devtools.webide.autoConnectRuntime"/>
@@ -49,64 +43,10 @@
       <li>
         <label class="text-input" title="&prefs_options_templatesurl_tooltip;">
           <span>&prefs_options_templatesurl;</span>
           <input data-pref="devtools.webide.templatesURL"/>
         </label>
       </li>
     </ul>
 
-    <h2>&prefs_editor_title;</h2>
-
-    <ul>
-      <li>
-        <label><span>&prefs_options_tabsize;</span>
-          <select data-pref="devtools.editor.tabsize">
-            <option value="2">2</option>
-            <option value="4">4</option>
-            <option value="8">8</option>
-          </select>
-        </label>
-      </li>
-      <li>
-        <label title="&prefs_options_expandtab_tooltip;">
-          <input type="checkbox" data-pref="devtools.editor.expandtab"/>
-          <span>&prefs_options_expandtab;</span>
-        </label>
-      </li>
-      <li>
-        <label title="&prefs_options_detectindentation_tooltip;">
-          <input type="checkbox" data-pref="devtools.editor.detectindentation"/>
-          <span>&prefs_options_detectindentation;</span>
-        </label>
-      </li>
-      <li>
-        <label title="&prefs_options_autocomplete_tooltip;">
-          <input type="checkbox" data-pref="devtools.editor.autocomplete"/>
-          <span>&prefs_options_autocomplete;</span>
-        </label>
-      </li>
-      <li>
-        <label title="&prefs_options_autoclosebrackets_tooltip;">
-          <input type="checkbox" data-pref="devtools.editor.autoclosebrackets"/>
-          <span>&prefs_options_autoclosebrackets;</span>
-        </label>
-      </li>
-      <li>
-        <label title="&prefs_options_autosavefiles_tooltip;">
-          <input type="checkbox" data-pref="devtools.webide.autosaveFiles"/>
-          <span>&prefs_options_autosavefiles;</span>
-        </label>
-      </li>
-      <li>
-        <label><span>&prefs_options_keybindings;</span>
-          <select data-pref="devtools.editor.keymap">
-            <option value="default">&prefs_options_keybindings_default;</option>
-            <option value="vim">Vim</option>
-            <option value="emacs">Emacs</option>
-            <option value="sublime">Sublime</option>
-          </select>
-        </label>
-      </li>
-    </ul>
-
   </body>
 </html>
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -1,27 +1,25 @@
 /* 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/. */
 
-var Cc = Components.classes;
 var Cu = Components.utils;
 var Ci = Components.interfaces;
 
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {gDevTools} = require("devtools/client/framework/devtools");
 const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
 const {Toolbox} = require("devtools/client/framework/toolbox");
 const Services = require("Services");
 const {AppProjects} = require("devtools/client/webide/modules/app-projects");
 const {Connection} = require("devtools/shared/client/connection-manager");
 const {AppManager} = require("devtools/client/webide/modules/app-manager");
 const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
-const ProjectEditor = require("devtools/client/projecteditor/lib/projecteditor");
 const {GetAvailableAddons} = require("devtools/client/webide/modules/addons");
 const {getJSON} = require("devtools/client/shared/getjson");
 const utils = require("devtools/client/webide/modules/utils");
 const Telemetry = require("devtools/client/shared/telemetry");
 const {RuntimeScanners} = require("devtools/client/webide/modules/runtimes");
 const {showDoorhanger} = require("devtools/client/shared/doorhanger");
 const {Simulators} = require("devtools/client/webide/modules/simulators");
 const {Task} = require("devtools/shared/task");
@@ -122,27 +120,19 @@ var UI = {
 
   destroy: function () {
     window.removeEventListener("focus", this.onfocus, true);
     AppManager.off("app-manager-update", this.appManagerUpdate);
     AppManager.destroy();
     Simulators.off("configure", this.configureSimulator);
     this.updateConnectionTelemetry();
     this._telemetry.toolClosed("webide");
-    this._telemetry.toolClosed("webideProjectEditor");
     this._telemetry.destroy();
   },
 
-  canCloseProject: function () {
-    if (this.projecteditor) {
-      return this.projecteditor.confirmUnsaved();
-    }
-    return true;
-  },
-
   onfocus: function () {
     // Because we can't track the activity in the folder project,
     // we need to validate the project regularly. Let's assume that
     // if a modification happened, it happened when the window was
     // not focused.
     if (AppManager.selectedProject &&
         AppManager.selectedProject.type != "mainProcess" &&
         AppManager.selectedProject.type != "runtimeApp" &&
@@ -163,21 +153,16 @@ var UI = {
       case "runtime-list":
         this.autoConnectRuntime();
         break;
       case "connection":
         this.updateRuntimeButton();
         this.updateCommands();
         this.updateConnectionTelemetry();
         break;
-      case "before-project":
-        if (!this.canCloseProject()) {
-          details.cancel();
-        }
-        break;
       case "project":
         this._updatePromise = Task.spawn(function* () {
           UI.updateTitle();
           yield UI.destroyToolbox();
           UI.updateCommands();
           UI.openProject();
           yield UI.autoStartProject();
           UI.autoOpenToolbox();
@@ -204,17 +189,16 @@ var UI = {
         break;
       case "runtime":
         this.updateRuntimeButton();
         this.saveLastConnectedRuntime();
         break;
       case "project-validated":
         this.updateTitle();
         this.updateCommands();
-        this.updateProjectEditorHeader();
         break;
       case "install-progress":
         this.updateProgress(Math.round(100 * details.bytesSent / details.totalBytes));
         break;
       case "runtime-targets":
         this.autoSelectProject();
         break;
       case "pre-package":
@@ -599,114 +583,25 @@ var UI = {
   logActionState: function (action, state) {
     let histogramId = "DEVTOOLS_WEBIDE_CONNECTION_" +
                       action.toUpperCase() + "_USED";
     this._telemetry.log(histogramId, state);
   },
 
   /** ******** PROJECTS **********/
 
-  // ProjectEditor & details screen
-
-  destroyProjectEditor: function () {
-    if (this.projecteditor) {
-      this.projecteditor.destroy();
-      this.projecteditor = null;
-    }
-  },
-
-  /**
-   * Called when selecting or deselecting the project editor panel.
-   */
-  onChangeProjectEditorSelected: function () {
-    if (this.projecteditor) {
-      let panel = document.querySelector("#deck").selectedPanel;
-      if (panel && panel.id == "deck-panel-projecteditor") {
-        this.projecteditor.menuEnabled = true;
-        this._telemetry.toolOpened("webideProjectEditor");
-      } else {
-        this.projecteditor.menuEnabled = false;
-        this._telemetry.toolClosed("webideProjectEditor");
-      }
-    }
-  },
-
-  getProjectEditor: function () {
-    if (this.projecteditor) {
-      return this.projecteditor.loaded;
-    }
-
-    let projecteditorIframe = document.querySelector("#deck-panel-projecteditor");
-    this.projecteditor = ProjectEditor.ProjectEditor(projecteditorIframe, {
-      menubar: document.querySelector("#main-menubar"),
-      menuindex: 1
-    });
-    this.projecteditor.on("onEditorSave", () => {
-      AppManager.validateAndUpdateProject(AppManager.selectedProject);
-      this._telemetry.actionOccurred("webideProjectEditorSave");
-    });
-    return this.projecteditor.loaded;
-  },
-
-  updateProjectEditorHeader: function () {
-    let project = AppManager.selectedProject;
-    if (!project || !this.projecteditor) {
-      return;
-    }
-    let status = project.validationStatus || "unknown";
-    if (status == "error warning") {
-      status = "error";
-    }
-    this.getProjectEditor().then((projecteditor) => {
-      projecteditor.setProjectToAppPath(project.location, {
-        name: project.name,
-        iconUrl: project.icon,
-        projectOverviewURL: "chrome://webide/content/details.xhtml",
-        validationStatus: status
-      }).then(null, console.error);
-    }, console.error);
-  },
-
-  isProjectEditorEnabled: function () {
-    return Services.prefs.getBoolPref("devtools.webide.showProjectEditor");
-  },
-
   openProject: function () {
     let project = AppManager.selectedProject;
 
-    // Nothing to show
-
     if (!project) {
       this.resetDeck();
       return;
     }
 
-    // Make sure the directory exist before we show Project Editor
-
-    let forceDetailsOnly = false;
-    if (project.type == "packaged") {
-      forceDetailsOnly = !utils.doesFileExist(project.location);
-    }
-
-    // Show only the details screen
-
-    if (project.type != "packaged" ||
-        !this.isProjectEditorEnabled() ||
-        forceDetailsOnly) {
-      this.selectDeckPanel("details");
-      return;
-    }
-
-    // Show ProjectEditor
-
-    this.getProjectEditor().then(() => {
-      this.updateProjectEditorHeader();
-    }, console.error);
-
-    this.selectDeckPanel("projecteditor");
+    this.selectDeckPanel("details");
   },
 
   autoStartProject: Task.async(function* () {
     let project = AppManager.selectedProject;
 
     if (!project) {
       return;
     }
@@ -864,24 +759,22 @@ var UI = {
     this.resetFocus();
     let panel = deck.querySelector("#deck-panel-" + id);
     let lazysrc = panel.getAttribute("lazysrc");
     if (lazysrc) {
       panel.removeAttribute("lazysrc");
       panel.setAttribute("src", lazysrc);
     }
     deck.selectedPanel = panel;
-    this.onChangeProjectEditorSelected();
   },
 
   resetDeck: function () {
     this.resetFocus();
     let deck = document.querySelector("#deck");
     deck.selectedPanel = null;
-    this.onChangeProjectEditorSelected();
   },
 
   buildIDToDate(buildID) {
     let fields = buildID.match(/(\d{4})(\d{2})(\d{2})/);
     // Date expects 0 - 11 for months
     return new Date(fields[1], Number.parseInt(fields[2]) - 1, fields[3]);
   },
 
@@ -995,19 +888,17 @@ var UI = {
     }
   }
 };
 
 EventEmitter.decorate(UI);
 
 var Cmds = {
   quit: function () {
-    if (UI.canCloseProject()) {
-      window.close();
-    }
+    window.close();
   },
 
   showProjectPanel: function () {
     ProjectPanel.toggleSidebar();
     return promise.resolve();
   },
 
   showRuntimePanel: function () {
@@ -1044,21 +935,16 @@ var Cmds = {
   showMonitor: function () {
     UI.selectDeckPanel("monitor");
   },
 
   play: Task.async(function* () {
     let busy;
     switch (AppManager.selectedProject.type) {
       case "packaged":
-        let autosave =
-          Services.prefs.getBoolPref("devtools.webide.autosaveFiles");
-        if (autosave && UI.projecteditor) {
-          yield UI.projecteditor.saveAllFiles();
-        }
         busy = UI.busyWithProgressUntil(AppManager.installAndRunProject(),
                                         "installing and running app");
         break;
       case "hosted":
         busy = UI.busyUntil(AppManager.installAndRunProject(),
                             "installing and running app");
         break;
       case "runtimeApp":
@@ -1088,25 +974,16 @@ var Cmds = {
       return UI.createToolbox();
     }
   },
 
   removeProject: function () {
     AppManager.removeSelectedProject();
   },
 
-  toggleEditors: function () {
-    let isNowEnabled = !UI.isProjectEditorEnabled();
-    Services.prefs.setBoolPref("devtools.webide.showProjectEditor", isNowEnabled);
-    if (!isNowEnabled) {
-      UI.destroyProjectEditor();
-    }
-    UI.openProject();
-  },
-
   showTroubleShooting: function () {
     UI.openInBrowser(HELP_URL);
   },
 
   showAddons: function () {
     UI.selectDeckPanel("addons");
   },
 
--- a/devtools/client/webide/content/webide.xul
+++ b/devtools/client/webide/content/webide.xul
@@ -42,17 +42,16 @@
       <command id="cmd_showSettings" label="&runtimeMenu_showSettings_label;" oncommand="Cmds.showSettings()"/>
       <command id="cmd_removeProject" oncommand="Cmds.removeProject()" label="&projectMenu_remove_label;"/>
       <command id="cmd_showProjectPanel" oncommand="Cmds.showProjectPanel()"/>
       <command id="cmd_showRuntimePanel" oncommand="Cmds.showRuntimePanel()"/>
       <command id="cmd_disconnectRuntime" oncommand="Cmds.disconnectRuntime()" label="&runtimeMenu_disconnect_label;"/>
       <command id="cmd_showMonitor" oncommand="Cmds.showMonitor()" label="&runtimeMenu_showMonitor_label;"/>
       <command id="cmd_showRuntimeDetails" oncommand="Cmds.showRuntimeDetails()" label="&runtimeMenu_showDetails_label;"/>
       <command id="cmd_takeScreenshot" oncommand="Cmds.takeScreenshot()" label="&runtimeMenu_takeScreenshot_label;"/>
-      <command id="cmd_toggleEditor" oncommand="Cmds.toggleEditors()" label="&viewMenu_toggleEditor_label;"/>
       <command id="cmd_showAddons" oncommand="Cmds.showAddons()"/>
       <command id="cmd_showPrefs" oncommand="Cmds.showPrefs()"/>
       <command id="cmd_showTroubleShooting" oncommand="Cmds.showTroubleShooting()"/>
       <command id="cmd_play" oncommand="Cmds.play()"/>
       <command id="cmd_stop" oncommand="Cmds.stop()" label="&projectMenu_stop_label;"/>
       <command id="cmd_toggleToolbox" oncommand="Cmds.toggleToolbox()"/>
       <command id="cmd_zoomin" label="&viewMenu_zoomin_label;" oncommand="Cmds.zoomIn()"/>
       <command id="cmd_zoomout" label="&viewMenu_zoomout_label;" oncommand="Cmds.zoomOut()"/>
@@ -88,31 +87,29 @@
         <menuitem command="cmd_showSettings" accesskey="&runtimeMenu_showSettings_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_disconnectRuntime" accesskey="&runtimeMenu_disconnect_accesskey;"/>
       </menupopup>
     </menu>
 
     <menu id="menu-view" label="&viewMenu_label;" accesskey="&viewMenu_accesskey;">
       <menupopup id="menu-ViewPopup">
-        <menuitem command="cmd_toggleEditor" key="key_toggleEditor" accesskey="&viewMenu_toggleEditor_accesskey;"/>
         <menuseparator/>
         <menuitem command="cmd_zoomin" key="key_zoomin" accesskey="&viewMenu_zoomin_accesskey;"/>
         <menuitem command="cmd_zoomout" key="key_zoomout" accesskey="&viewMenu_zoomout_accesskey;"/>
         <menuitem command="cmd_resetzoom" key="key_resetzoom" accesskey="&viewMenu_resetzoom_accesskey;"/>
       </menupopup>
     </menu>
 
   </menubar>
 
   <keyset id="mainKeyset">
     <key key="&key_quit;" id="key_quit" command="cmd_quit" modifiers="accel"/>
     <key key="&key_showProjectPanel;" id="key_showProjectPanel" command="cmd_showProjectPanel" modifiers="accel"/>
     <key key="&key_play;" id="key_play" command="cmd_play" modifiers="accel"/>
-    <key key="&key_toggleEditor;" id="key_toggleEditor" command="cmd_toggleEditor" modifiers="accel"/>
     <key keycode="&key_toggleToolbox;" id="key_toggleToolbox" command="cmd_toggleToolbox"/>
     <key key="&key_zoomin;" id="key_zoomin" command="cmd_zoomin" modifiers="accel"/>
     <key key="&key_zoomin2;" id="key_zoomin2" command="cmd_zoomin" modifiers="accel"/>
     <key key="&key_zoomout;" id="key_zoomout" command="cmd_zoomout" modifiers="accel"/>
     <key key="&key_resetzoom;" id="key_resetzoom" command="cmd_resetzoom" modifiers="accel"/>
   </keyset>
 
   <tooltip id="aHTMLTooltip" page="true"/>
@@ -146,17 +143,16 @@
       <vbox id="project-listing-panel" class="project-listing panel-list" flex="1">
         <div id="project-listing-wrapper" class="panel-list-wrapper">
           <iframe id="project-listing-panel-details" flex="1" src="project-listing.xhtml" tooltip="aHTMLTooltip"/>
         </div>
       </vbox>
       <splitter class="devtools-side-splitter" id="project-listing-splitter"/>
       <deck flex="1" id="deck" selectedIndex="-1">
         <iframe id="deck-panel-details" flex="1" src="details.xhtml"/>
-        <iframe id="deck-panel-projecteditor" flex="1"/>
         <iframe id="deck-panel-addons" flex="1" src="addons.xhtml"/>
         <iframe id="deck-panel-prefs" flex="1" src="prefs.xhtml"/>
         <iframe id="deck-panel-runtimedetails" flex="1" lazysrc="runtimedetails.xhtml"/>
         <iframe id="deck-panel-monitor" flex="1" lazysrc="monitor.xhtml"/>
         <iframe id="deck-panel-devicepreferences" flex="1" lazysrc="devicepreferences.xhtml"/>
         <iframe id="deck-panel-logs" flex="1" src="logs.xhtml"/>
         <iframe id="deck-panel-simulator" flex="1" lazysrc="simulator.xhtml"/>
       </deck>
--- a/devtools/client/webide/modules/build.js
+++ b/devtools/client/webide/modules/build.js
@@ -39,17 +39,17 @@ const ProjectBuilding = exports.ProjectB
     // Cordova
     let cordovaConfigPath = OS.Path.join(project.location, "config.xml");
     let exists = yield OS.File.exists(cordovaConfigPath);
     if (!exists) {
       return;
     }
     let data = yield OS.File.read(cordovaConfigPath);
     data = new TextDecoder().decode(data);
-    if (data.contains("cordova.apache.org")) {
+    if (data.includes("cordova.apache.org")) {
       return {
         "webide": {
           "prepackage": "cordova prepare",
           "packageDir": "./platforms/firefoxos/www"
         }
       };
     }
   }),
--- a/devtools/client/webide/webide-prefs.js
+++ b/devtools/client/webide/webide-prefs.js
@@ -1,13 +1,12 @@
 /* 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/. */
 
-pref("devtools.webide.showProjectEditor", true);
 pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
 pref("devtools.webide.autoinstallADBHelper", true);
 pref("devtools.webide.autoinstallFxdtAdapters", true);
 pref("devtools.webide.autoConnectRuntime", true);
 pref("devtools.webide.restoreLastProject", true);
 pref("devtools.webide.enableLocalRuntime", false);
 pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
 pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
@@ -18,9 +17,8 @@ pref("devtools.webide.adbAddonID", "adbh
 pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/valence/#OS#/valence-#OS#-latest.xpi");
 pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
 pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
 pref("devtools.webide.lastConnectedRuntime", "");
 pref("devtools.webide.lastSelectedProject", "");
 pref("devtools.webide.logSimulatorOutput", false);
 pref("devtools.webide.zoom", "1");
 pref("devtools.webide.busyTimeout", 10000);
-pref("devtools.webide.autosaveFiles", true);
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2418,17 +2418,17 @@ Element::SetAttr(int32_t aNamespaceID, n
   // changing, so just init our nsAttrValueOrString with aValue for the
   // OnlyNotifySameValueSet call.
   nsAttrValueOrString value(aValue);
   nsAttrValue oldValue;
   bool oldValueSet;
 
   if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
                              oldValue, &modType, &hasListeners, &oldValueSet)) {
-    return NS_OK;
+    return OnAttrSetButNotChanged(aNamespaceID, aName, value, aNotify);
   }
 
   nsAttrValue attrValue;
   nsAttrValue* preparsedAttrValue;
   if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::_class) {
     attrValue.ParseAtomArray(aValue);
     value.ResetToAttrValue(attrValue);
     preparsedAttrValue = &attrValue;
@@ -2436,30 +2436,31 @@ Element::SetAttr(int32_t aNamespaceID, n
     preparsedAttrValue = nullptr;
   }
 
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNamespaceID, aName, modType,
                                      preparsedAttrValue);
   }
 
-  nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // Hold a script blocker while calling ParseAttribute since that can call
   // out to id-observers
   nsIDocument* document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
-  // Even the value was pre-parsed, we still need to call ParseAttribute because
-  // it can have side effects.
-  if (!ParseAttribute(aNamespaceID, aName, aValue, attrValue)) {
+  nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!preparsedAttrValue &&
+      !ParseAttribute(aNamespaceID, aName, aValue, attrValue)) {
     attrValue.SetTo(aValue);
   }
 
+  PreIdMaybeChange(aNamespaceID, aName, &value);
+
   return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
                           oldValueSet ? &oldValue : nullptr,
                           attrValue, modType, hasListeners, aNotify,
                           kCallAfterSetAttr, document, updateBatch);
 }
 
 nsresult
 Element::SetParsedAttr(int32_t aNamespaceID, nsIAtom* aName,
@@ -2480,27 +2481,29 @@ Element::SetParsedAttr(int32_t aNamespac
   uint8_t modType;
   bool hasListeners;
   nsAttrValueOrString value(aParsedValue);
   nsAttrValue oldValue;
   bool oldValueSet;
 
   if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
                              oldValue, &modType, &hasListeners, &oldValueSet)) {
-    return NS_OK;
+    return OnAttrSetButNotChanged(aNamespaceID, aName, value, aNotify);
   }
 
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNamespaceID, aName, modType,
                                      &aParsedValue);
   }
 
   nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  PreIdMaybeChange(aNamespaceID, aName, &value);
+
   nsIDocument* document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
   return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
                           oldValueSet ? &oldValue : nullptr,
                           aParsedValue, modType, hasListeners, aNotify,
                           kCallAfterSetAttr, document, updateBatch);
 }
 
@@ -2549,16 +2552,18 @@ Element::SetAttrAndNotify(int32_t aNames
     ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(aName, aPrefix,
                                                    aNamespaceID,
                                                    nsIDOMNode::ATTRIBUTE_NODE);
 
     rv = mAttrsAndChildren.SetAndSwapAttr(ni, aParsedValue, &oldValueSet);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
+  PostIdMaybeChange(aNamespaceID, aName, &valueForAfterSetAttr);
+
   // If the old value owns its own data, we know it is OK to keep using it.
   // oldValue will be null if there was no previously set value
   const nsAttrValue* oldValue;
   if (aParsedValue.StoresOwnData()) {
     if (oldValueSet) {
       oldValue = &aParsedValue;
     } else {
       oldValue = nullptr;
@@ -2648,32 +2653,26 @@ Element::SetAttrAndNotify(int32_t aNames
 
 bool
 Element::ParseAttribute(int32_t aNamespaceID,
                         nsIAtom* aAttribute,
                         const nsAString& aValue,
                         nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
-    if (aAttribute == nsGkAtoms::_class) {
-      SetMayHaveClass();
-      // Result should have been preparsed above.
-      return true;
-    }
+    MOZ_ASSERT(aAttribute != nsGkAtoms::_class,
+               "The class attribute should be preparsed and therefore should "
+               "never be passed to Element::ParseAttribute");
     if (aAttribute == nsGkAtoms::id) {
       // Store id as an atom.  id="" means that the element has no id,
       // not that it has an emptystring as the id.
-      RemoveFromIdTable();
       if (aValue.IsEmpty()) {
-        ClearHasID();
         return false;
       }
       aResult.ParseAtom(aValue);
-      SetHasID();
-      AddToIdTable(aResult.GetAtomValue());
       return true;
     }
   }
 
   return false;
 }
 
 bool
@@ -2681,16 +2680,67 @@ Element::SetAndSwapMappedAttribute(nsIAt
                                    nsAttrValue& aValue,
                                    bool* aValueWasSet,
                                    nsresult* aRetval)
 {
   *aRetval = NS_OK;
   return false;
 }
 
+nsresult
+Element::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                       const nsAttrValueOrString* aValue, bool aNotify)
+{
+  if (aNamespaceID == kNameSpaceID_None) {
+    if (aName == nsGkAtoms::_class) {
+      if (aValue) {
+        // Note: This flag is asymmetrical. It is never unset and isn't exact.
+        // If it is ever made to be exact, we probably need to handle this
+        // similarly to how ids are handled in PreIdMaybeChange and
+        // PostIdMaybeChange.
+        SetMayHaveClass();
+      }
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+Element::PreIdMaybeChange(int32_t aNamespaceID, nsIAtom* aName,
+                          const nsAttrValueOrString* aValue)
+{
+  if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::id) {
+    return;
+  }
+  RemoveFromIdTable();
+
+  return;
+}
+
+void
+Element::PostIdMaybeChange(int32_t aNamespaceID, nsIAtom* aName,
+                           const nsAttrValue* aValue)
+{
+  if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::id) {
+    return;
+  }
+
+  // id="" means that the element has no id, not that it has an empty
+  // string as the id.
+  if (aValue && !aValue->IsEmptyString()) {
+    SetHasID();
+    AddToIdTable(aValue->GetAtomValue());
+  } else {
+    ClearHasID();
+  }
+
+  return;
+}
+
 EventListenerManager*
 Element::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
                                         bool* aDefer)
 {
   *aDefer = true;
   return GetOrCreateListenerManager();
 }
 
@@ -2774,16 +2824,18 @@ Element::UnsetAttr(int32_t aNameSpaceID,
   nsresult rv = BeforeSetAttr(aNameSpaceID, aName, nullptr, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool hasMutationListeners = aNotify &&
     nsContentUtils::HasMutationListeners(this,
                                          NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
                                          this);
 
+  PreIdMaybeChange(aNameSpaceID, aName, nullptr);
+
   // Grab the attr node if needed before we remove it from the attr map
   RefPtr<Attr> attrNode;
   if (hasMutationListeners) {
     nsAutoString ns;
     nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, ns);
     attrNode = GetAttributeNodeNSInternal(ns, nsDependentAtomString(aName));
   }
 
@@ -2792,34 +2844,30 @@ Element::UnsetAttr(int32_t aNameSpaceID,
   if (slots && slots->mAttributeMap) {
     slots->mAttributeMap->DropAttribute(aNameSpaceID, aName);
   }
 
   // The id-handling code, and in the future possibly other code, need to
   // react to unexpected attribute changes.
   nsMutationGuard::DidMutate();
 
-  if (aName == nsGkAtoms::id && aNameSpaceID == kNameSpaceID_None) {
-    // Have to do this before clearing flag. See RemoveFromIdTable
-    RemoveFromIdTable();
-    ClearHasID();
-  }
-
   bool hadValidDir = false;
   bool hadDirAuto = false;
 
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
     hadValidDir = HasValidDir() || IsHTMLElement(nsGkAtoms::bdi);
     hadDirAuto = HasDirAuto(); // already takes bdi into account
   }
 
   nsAttrValue oldValue;
   rv = mAttrsAndChildren.RemoveAttrAt(index, oldValue);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  PostIdMaybeChange(aNameSpaceID, aName, nullptr);
+
   if (document || HasFlag(NODE_FORCE_XBL_BINDINGS)) {
     RefPtr<nsXBLBinding> binding = GetXBLBinding();
     if (binding) {
       binding->AttributeChanged(aName, aNameSpaceID, true, aNotify);
     }
   }
 
   nsIDocument* ownerDoc = OwnerDoc();
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -613,16 +613,17 @@ public:
    * null otherwise.
    *
    * @param aStr the unparsed attribute string
    * @return the node info. May be nullptr.
    */
   already_AddRefed<mozilla::dom::NodeInfo>
   GetExistingAttrNameFromQName(const nsAString& aStr) const;
 
+  MOZ_ALWAYS_INLINE  // Avoid a crashy hook from Avast 10 Beta (Bug 1058131)
   nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                    const nsAString& aValue, bool aNotify)
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
   }
 
   /**
    * Helper for SetAttr/SetParsedAttr. This method will return true if aNotify
@@ -1466,24 +1467,19 @@ protected:
    *
    * @param aNamespaceID the namespace of the attr being set
    * @param aName the localname of the attribute being set
    * @param aValue the value it's being set to represented as either a string or
    *        a parsed nsAttrValue. Alternatively, if the attr is being removed it
    *        will be null.
    * @param aNotify Whether we plan to notify document observers.
    */
-  // Note that this is inlined so that when subclasses call it it gets
-  // inlined.  Those calls don't go through a vtable.
   virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
-                                 bool aNotify)
-  {
-    return NS_OK;
-  }
+                                 bool aNotify);
 
   /**
    * Hook that is called by Element::SetAttr to allow subclasses to
    * deal with attribute sets.  This will only be called after we have called
    * SetAndSwapAttr (that is, after we have actually set the attr).  It will
    * always be called under a scriptblocker.
    *
    * @param aNamespaceID the namespace of the attr being set
@@ -1501,16 +1497,74 @@ protected:
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
                                 const nsAttrValue* aOldValue, bool aNotify)
   {
     return NS_OK;
   }
 
   /**
+   * This function shall be called just before the id attribute changes. It will
+   * be called after BeforeSetAttr. If the attribute being changed is not the id
+   * attribute, this function does nothing. Otherwise, it will remove the old id
+   * from the document's id cache.
+   *
+   * This must happen after BeforeSetAttr (rather than during) because the
+   * the subclasses' calls to BeforeSetAttr may notify on state changes. If they
+   * incorrectly determine whether the element had an id, the element may not be
+   * restyled properly.
+   *
+   * @param aNamespaceID the namespace of the attr being set
+   * @param aName the localname of the attribute being set
+   * @param aValue the new id value. Will be null if the id is being unset.
+   */
+  void PreIdMaybeChange(int32_t aNamespaceID, nsIAtom* aName,
+                        const nsAttrValueOrString* aValue);
+
+  /**
+   * This function shall be called just after the id attribute changes. It will
+   * be called before AfterSetAttr. If the attribute being changed is not the id
+   * attribute, this function does nothing. Otherwise, it will add the new id to
+   * the document's id cache and properly set the ElementHasID flag.
+   *
+   * This must happen before AfterSetAttr (rather than during) because the
+   * the subclasses' calls to AfterSetAttr may notify on state changes. If they
+   * incorrectly determine whether the element now has an id, the element may
+   * not be restyled properly.
+   *
+   * @param aNamespaceID the namespace of the attr being set
+   * @param aName the localname of the attribute being set
+   * @param aValue the new id value. Will be null if the id is being unset.
+   */
+  void PostIdMaybeChange(int32_t aNamespaceID, nsIAtom* aName,
+                         const nsAttrValue* aValue);
+
+  /**
+   * Usually, setting an attribute to the value that it already has results in
+   * no action. However, in some cases, setting an attribute to its current
+   * value should have the effect of, for example, forcing a reload of
+   * network data. To address that, this function will be called in this
+   * situation to allow the handling of such a case.
+   *
+   * @param aNamespaceID the namespace of the attr being set
+   * @param aName the localname of the attribute being set
+   * @param aValue the value it's being set to represented as either a string or
+   *        a parsed nsAttrValue.
+   * @param aNotify Whether we plan to notify document observers.
+   */
+  // Note that this is inlined so that when subclasses call it it gets
+  // inlined.  Those calls don't go through a vtable.
+  virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsIAtom* aName,
+                                          const nsAttrValueOrString& aValue,
+                                          bool aNotify)
+  {
+    return NS_OK;
+  }
+
+  /**
    * Hook to allow subclasses to produce a different EventListenerManager if
    * needed for attachment of attribute-defined handlers
    */
   virtual EventListenerManager*
     GetEventListenerManagerForAttr(nsIAtom* aAttrName, bool* aDefer);
 
   /**
    * Internal hook for converting an attribute name-string to nsAttrName in
--- a/dom/base/nsAttrValueOrString.h
+++ b/dom/base/nsAttrValueOrString.h
@@ -73,15 +73,29 @@ public:
   bool EqualsAsStrings(const nsAttrValue& aOther) const
   {
     if (mStringPtr) {
       return aOther.Equals(*mStringPtr, eCaseMatters);
     }
     return aOther.EqualsAsStrings(*mAttrValue);
   }
 
+  /*
+   * Returns true if the value stored is empty
+   */
+  bool IsEmpty() const
+  {
+    if (mStringPtr) {
+      return mStringPtr->IsEmpty();
+    }
+    if (mAttrValue) {
+      return mAttrValue->IsEmptyString();
+    }
+    return true;
+  }
+
 protected:
   const nsAttrValue*       mAttrValue;
   mutable const nsAString* mStringPtr;
   mutable nsCheapString    mCheapString;
 };
 
 #endif // nsAttrValueOrString_h___
--- a/dom/base/nsStyledElement.cpp
+++ b/dom/base/nsStyledElement.cpp
@@ -35,26 +35,41 @@ NS_IMPL_QUERY_INTERFACE_INHERITED(nsStyl
 
 bool
 nsStyledElement::ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult)
 {
   if (aAttribute == nsGkAtoms::style && aNamespaceID == kNameSpaceID_None) {
-    SetMayHaveStyle();
     ParseStyleAttribute(aValue, aResult, false);
     return true;
   }
 
   return nsStyledElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                              aResult);
 }
 
 nsresult
+nsStyledElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                               const nsAttrValueOrString* aValue, bool aNotify)
+{
+  if (aNamespaceID == kNameSpaceID_None) {
+    if (aName == nsGkAtoms::style) {
+      if (aValue) {
+        SetMayHaveStyle();
+      }
+    }
+  }
+
+  return nsStyledElementBase::BeforeSetAttr(aNamespaceID, aName, aValue,
+                                            aNotify);
+}
+
+nsresult
 nsStyledElement::SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
                                            const nsAString* aSerialized,
                                            bool aNotify)
 {
   SetMayHaveStyle();
   bool modification = false;
   nsAttrValue oldValue;
   bool oldValueSet = false;
--- a/dom/base/nsStyledElement.h
+++ b/dom/base/nsStyledElement.h
@@ -77,12 +77,16 @@ protected:
    * first put into a document.  Only has an effect if the old value is a
    * string.  If aForceInDataDoc is true, will reparse even if we're in a data
    * document. If aForceIfAlreadyParsed is set, this will always reparse even
    * if the value has already been parsed.
    */
   nsresult ReparseStyleAttribute(bool aForceInDataDoc, bool aForceIfAlreadyParsed);
 
   virtual void NodeInfoChanged(nsIDocument* aOldDoc) override;
+
+  virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                 const nsAttrValueOrString* aValue,
+                                 bool aNotify) override;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsStyledElement, NS_STYLED_ELEMENT_IID)
 #endif // __NS_STYLEDELEMENT_H_
--- a/dom/html/HTMLAnchorElement.cpp
+++ b/dom/html/HTMLAnchorElement.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/dom/HTMLAnchorElementBinding.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsGkAtoms.h"
 #include "nsHTMLDNSPrefetch.h"
+#include "nsAttrValueOrString.h"
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "nsIURI.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Anchor)
 
 namespace mozilla {
@@ -360,94 +361,47 @@ HTMLAnchorElement::GetHrefURI() const
   if (uri) {
     return uri.forget();
   }
 
   return GetHrefURIForAnchors();
 }
 
 nsresult
-HTMLAnchorElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                           nsIAtom* aPrefix, const nsAString& aValue,
-                           bool aNotify)
+HTMLAnchorElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                 const nsAttrValueOrString* aValue,
+                                 bool aNotify)
 {
-  bool reset = false;
-  if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
-    // If we do not have a cached URI, we have some value here so we must reset
-    // our link state after calling the parent.
-    if (!Link::HasCachedURI()) {
-      reset = true;
-    }
-    // However, if we have a cached URI, we'll want to see if the value changed.
-    else {
-      nsAutoString val;
-      GetHref(val);
-      if (!val.Equals(aValue)) {
-        reset = true;
-      }
-    }
-    if (reset) {
+  if (aNamespaceID == kNameSpaceID_None) {
+    if (aName == nsGkAtoms::href) {
       CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
                         HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
     }
   }
 
-  nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
-                                              aValue, aNotify);
-
-  // The ordering of the parent class's SetAttr call and Link::ResetLinkState
-  // is important here!  The attribute is not set until SetAttr returns, and
-  // we will need the updated attribute value because notifying the document
-  // that content states have changed will call IntrinsicState, which will try
-  // to get updated information about the visitedness from Link.
-  if (reset) {
-    Link::ResetLinkState(!!aNotify, true);
-    if (IsInComposedDoc()) {
-      TryDNSPrefetch();
-    }
-  }
-
-  return rv;
+  return nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName, aValue,
+                                             aNotify);
 }
 
 nsresult
-HTMLAnchorElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                             bool aNotify)
+HTMLAnchorElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
-  bool href =
-    (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID);
-
-  if (href) {
-    CancelDNSPrefetch(HTML_ANCHOR_DNS_PREFETCH_DEFERRED,
-                      HTML_ANCHOR_DNS_PREFETCH_REQUESTED);
+  if (aNamespaceID == kNameSpaceID_None) {
+    if (aName == nsGkAtoms::href) {
+      Link::ResetLinkState(aNotify, !!aValue);
+      if (aValue && IsInComposedDoc()) {
+        TryDNSPrefetch();
+      }
+    }
   }
 
-  nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
-                                                aNotify);
-
-  // The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
-  // is important here!  The attribute is not unset until UnsetAttr returns, and
-  // we will need the updated attribute value because notifying the document
-  // that content states have changed will call IntrinsicState, which will try
-  // to get updated information about the visitedness from Link.
-  if (href) {
-    Link::ResetLinkState(!!aNotify, false);
-  }
-
-  return rv;
-}
-
-bool
-HTMLAnchorElement::ParseAttribute(int32_t aNamespaceID,
-                                  nsIAtom* aAttribute,
-                                  const nsAString& aValue,
-                                  nsAttrValue& aResult)
-{
-  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
-                                              aResult);
+  return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName,
+                                            aValue, aOldValue, aNotify);
 }
 
 EventStates
 HTMLAnchorElement::IntrinsicState() const
 {
   return Link::LinkState() | nsGenericHTMLElement::IntrinsicState();
 }
 
--- a/dom/html/HTMLAnchorElement.h
+++ b/dom/html/HTMLAnchorElement.h
@@ -61,30 +61,23 @@ public:
   virtual nsresult GetEventTargetParent(
                      EventChainPreVisitor& aVisitor) override;
   virtual nsresult PostHandleEvent(
                      EventChainPostVisitor& aVisitor) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual void GetLinkTarget(nsAString& aTarget) override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
 
-  nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                   const nsAString& aValue, bool aNotify)
-  {
-    return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
-  }
-  virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                           nsIAtom* aPrefix, const nsAString& aValue,
-                           bool aNotify) override;
-  virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                             bool aNotify) override;
-  virtual bool ParseAttribute(int32_t aNamespaceID,
-                                nsIAtom* aAttribute,
-                                const nsAString& aValue,
-                                nsAttrValue& aResult) override;
+  virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                 const nsAttrValueOrString* aValue,
+                                 bool aNotify) override;
+  virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
   virtual EventStates IntrinsicState() const override;
 
   virtual void OnDNSPrefetchDeferred() override;
   virtual void OnDNSPrefetchRequested() override;
--- a/dom/html/HTMLAreaElement.cpp
+++ b/dom/html/HTMLAreaElement.cpp
@@ -141,52 +141,32 @@ HTMLAreaElement::UnbindFromTree(bool aDe
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 nsresult
-HTMLAreaElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                         nsIAtom* aPrefix, const nsAString& aValue,
-                         bool aNotify)
+HTMLAreaElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                              const nsAttrValue* aValue,
+                              const nsAttrValue* aOldValue, bool aNotify)
 {
-  nsresult rv =
-    nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue, aNotify);
-
-  // The ordering of the parent class's SetAttr call and Link::ResetLinkState
-  // is important here!  The attribute is not set until SetAttr returns, and
-  // we will need the updated attribute value because notifying the document
-  // that content states have changed will call IntrinsicState, which will try
-  // to get updated information about the visitedness from Link.
-  if (aName == nsGkAtoms::href && aNameSpaceID == kNameSpaceID_None) {
-    Link::ResetLinkState(!!aNotify, true);
+  if (aNamespaceID == kNameSpaceID_None) {
+    // This must happen after the attribute is set. We will need the updated
+    // attribute value because notifying the document that content states have
+    // changed will call IntrinsicState, which will try to get updated
+    // information about the visitedness from Link.
+    if (aName == nsGkAtoms::href) {
+      Link::ResetLinkState(aNotify, !!aValue);
+    }
   }
 
-  return rv;
-}
-
-nsresult
-HTMLAreaElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                           bool aNotify)
-{
-  nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttribute,
-                                                aNotify);
-
-  // The ordering of the parent class's UnsetAttr call and Link::ResetLinkState
-  // is important here!  The attribute is not unset until UnsetAttr returns, and
-  // we will need the updated attribute value because notifying the document
-  // that content states have changed will call IntrinsicState, which will try
-  // to get updated information about the visitedness from Link.
-  if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
-    Link::ResetLinkState(!!aNotify, false);
-  }
-
-  return rv;
+  return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName, aValue,
+                                            aOldValue, aNotify);
 }
 
 #define IMPL_URI_PART(_part)                                 \
   NS_IMETHODIMP                                              \
   HTMLAreaElement::Get##_part(nsAString& a##_part)           \
   {                                                          \
     Link::Get##_part(a##_part);                              \
     return NS_OK;                                            \
--- a/dom/html/HTMLAreaElement.h
+++ b/dom/html/HTMLAreaElement.h
@@ -51,26 +51,16 @@ public:
   virtual void GetLinkTarget(nsAString& aTarget) override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
-  nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                   const nsAString& aValue, bool aNotify)
-  {
-    return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
-  }
-  virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                           nsIAtom* aPrefix, const nsAString& aValue,
-                           bool aNotify) override;
-  virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
-                             bool aNotify) override;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult,
                          bool aPreallocateChildren) const override;
 
   virtual EventStates IntrinsicState() const override;
 
   // WebIDL
 
@@ -188,15 +178,20 @@ public:
     nsGenericHTMLElement::NodeInfoChanged(aOldDoc);
   }
 
 protected:
   virtual ~HTMLAreaElement();
 
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
+  virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
+
   RefPtr<nsDOMTokenList > mRelList;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_HTMLAreaElement_h */
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -456,48 +456,47 @@ HTMLCanvasElement::GetWidthHeight()
   return size;
 }
 
 NS_IMPL_UINT_ATTR_DEFAULT_VALUE(HTMLCanvasElement, Width, width, DEFAULT_CANVAS_WIDTH)
 NS_IMPL_UINT_ATTR_DEFAULT_VALUE(HTMLCanvasElement, Height, height, DEFAULT_CANVAS_HEIGHT)
 NS_IMPL_BOOL_ATTR(HTMLCanvasElement, MozOpaque, moz_opaque)
 
 nsresult
-HTMLCanvasElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                           nsIAtom* aPrefix, const nsAString& aValue,
-                           bool aNotify)
+HTMLCanvasElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
-  nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
-                                              aNotify);
-  if (NS_SUCCEEDED(rv) && mCurrentContext &&
-      aNameSpaceID == kNameSpaceID_None &&
-      (aName == nsGkAtoms::width || aName == nsGkAtoms::height || aName == nsGkAtoms::moz_opaque))
-  {
-    ErrorResult dummy;
-    rv = UpdateContext(nullptr, JS::NullHandleValue, dummy);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
 
-  return rv;
+  return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName, aValue,
+                                            aOldValue, aNotify);
 }
 
 nsresult
-HTMLCanvasElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                             bool aNotify)
+HTMLCanvasElement::OnAttrSetButNotChanged(int32_t aNamespaceID, nsIAtom* aName,
+                                          const nsAttrValueOrString& aValue,
+                                          bool aNotify)
 {
-  nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aName, aNotify);
-  if (NS_SUCCEEDED(rv) && mCurrentContext &&
-      aNameSpaceID == kNameSpaceID_None &&
-      (aName == nsGkAtoms::width || aName == nsGkAtoms::height || aName == nsGkAtoms::moz_opaque))
-  {
+  AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
+
+  return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName,
+                                                      aValue, aNotify);
+}
+
+void
+HTMLCanvasElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                        bool aNotify)
+{
+  if (mCurrentContext && aNamespaceID == kNameSpaceID_None &&
+      (aName == nsGkAtoms::width || aName == nsGkAtoms::height ||