Merge fxteam to m-c
authorWes Kocher <wkocher@mozilla.com>
Thu, 10 Oct 2013 18:40:22 -0700
changeset 164144 d28d34e013000f68f3781791105ac3fb11ae92f2
parent 164135 40c7c53fe9b065bbd8ab6a70fdc8c755d349e33d (current diff)
parent 164143 2618d8170c70e7eed683854a8e66fe983c42b9bd (diff)
child 164167 1209f651c2d59a53479a83b3fb7d250184fb41fb
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.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 fxteam to m-c
mobile/android/base/GeckoView.java.frag
--- a/browser/devtools/framework/test/browser_toolbox_options.js
+++ b/browser/devtools/framework/test/browser_toolbox_options.js
@@ -1,163 +1,216 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let doc = null, toolbox = null, panelWin = null, index = 0, prefValues = [], prefNodes = [];
+let doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-    gDevTools.showToolbox(target).then(testSelectTool);
+    gDevTools.showToolbox(target)
+      .then(testSelectTool)
+      .then(testOptionsShortcut)
+      .then(testOptions)
+      .then(testToggleTools)
+      .then(cleanup, errorHandler);
   }, true);
 
   content.location = "data:text/html;charset=utf8,test for dynamically registering and unregistering tools";
 }
 
 function testSelectTool(aToolbox) {
+  let deferred = promise.defer();
+
   toolbox = aToolbox;
   doc = toolbox.doc;
-  toolbox.once("options-selected", testOptionsShortcut);
+  toolbox.once("options-selected", () => {
+    ok(true, "Toolbox selected via selectTool method");
+    deferred.resolve();
+  });
   toolbox.selectTool("options");
+
+  return deferred.promise;
 }
 
 function testOptionsShortcut() {
-  ok(true, "Toolbox selected via selectTool method");
-  toolbox.once("options-selected", testOptions);
+  let deferred = promise.defer();
+
+  toolbox.once("options-selected", (event, tool) => {
+    ok(true, "Toolbox selected via shortcut key");
+    deferred.resolve(tool);
+  });
   toolbox.selectTool("webconsole")
          .then(() => synthesizeKeyFromKeyTag("toolbox-options-key", doc));
+
+  return deferred.promise;
 }
 
-function testOptions(event, tool) {
-  ok(true, "Toolbox selected via button click");
+function testOptions(tool) {
   panelWin = tool.panelWin;
-  // Testing pref changes
-  let prefCheckboxes = tool.panelDoc.querySelectorAll("checkbox[data-pref]");
-  for (let checkbox of prefCheckboxes) {
-    prefNodes.push(checkbox);
-    prefValues.push(Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")));
+  let prefNodes = tool.panelDoc.querySelectorAll("checkbox[data-pref]");
+
+  // Store modified pref names so that they can be cleared on error.
+  for (let node of prefNodes) {
+    let pref = node.getAttribute("data-pref");
+    modifiedPrefs.push(pref);
+  }
+
+  // Test each options pref
+  let p = promise.resolve();
+  for (let node of prefNodes) {
+    let prefValue = Services.prefs.getBoolPref(node.getAttribute("data-pref"));
+    p = p.then(testMouseClick.bind(null, node, prefValue));
   }
   // Do again with opposite values to reset prefs
-  for (let checkbox of prefCheckboxes) {
-    prefNodes.push(checkbox);
-    prefValues.push(!Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")));
+  for (let node of prefNodes) {
+    let prefValue = !Services.prefs.getBoolPref(node.getAttribute("data-pref"));
+    p = p.then(testMouseClick.bind(null, node, prefValue));
   }
-  testMouseClicks();
+
+  return p;
 }
 
-function testMouseClicks() {
-  if (index == prefValues.length) {
-    checkTools();
-    return;
-  }
-  gDevTools.once("pref-changed", prefChanged);
-  info("Click event synthesized for index " + index);
-  prefNodes[index].scrollIntoView();
+function testMouseClick(node, prefValue) {
+  let deferred = promise.defer();
+
+  let pref = node.getAttribute("data-pref");
+  gDevTools.once("pref-changed", (event, data) => {
+    if (data.pref == pref) {
+      ok(true, "Correct pref was changed");
+      is(data.oldValue, prefValue, "Previous value is correct");
+      is(data.newValue, !prefValue, "New value is correct");
+    } else {
+      ok(false, "Pref " + pref + " was not changed correctly");
+    }
+    deferred.resolve();
+  });
+
+  node.scrollIntoView();
 
   // We use executeSoon here to ensure that the element is in view and
   // clickable.
   executeSoon(function() {
-    EventUtils.synthesizeMouseAtCenter(prefNodes[index], {}, panelWin);
+    info("Click event synthesized for pref " + pref);
+    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
   });
-}
 
-function prefChanged(event, data) {
-  if (data.pref == prefNodes[index].getAttribute("data-pref")) {
-    ok(true, "Correct pref was changed");
-    is(data.oldValue, prefValues[index], "Previous value is correct");
-    is(data.newValue, !prefValues[index], "New value is correct");
-    index++;
-    testMouseClicks();
-    return;
-  }
-  ok(false, "Pref was not changed correctly");
-  cleanup();
-}
-
-function checkTools() {
-  let toolsPref = panelWin.document.querySelectorAll("#default-tools-box > checkbox:not([unsupported])");
-  prefNodes = [];
-  index = 0;
-  for (let tool of toolsPref) {
-    prefNodes.push(tool);
-  }
-  // Randomize the order in which we remove the tool and then add them back so
-  // that we get to know if the tabs are correctly placed as per their ordinals.
-  prefNodes = prefNodes.sort(() => Math.random() > 0.5 ? 1: -1);
-
-  // Wait for the next turn of the event loop to avoid stack overflow errors.
-  executeSoon(toggleTools);
+  return deferred.promise;
 }
 
-function toggleTools() {
-  if (index < prefNodes.length) {
-    gDevTools.once("tool-unregistered", checkUnregistered);
-    let node = prefNodes[index];
-    node.scrollIntoView();
-    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
+function testToggleTools() {
+  let toolNodes = panelWin.document.querySelectorAll("#default-tools-box > checkbox:not([unsupported])");
+  let enabledTools = Array.prototype.filter.call(toolNodes, node => node.checked);
+
+  // Store modified pref names so that they can be cleared on error.
+  for (let tool of gDevTools.getDefaultTools()) {
+    let pref = tool.visibilityswitch;
+    modifiedPrefs.push(pref);
+  }
+
+  // Toggle each tool
+  let p = promise.resolve();
+  for (let node of toolNodes) {
+    p = p.then(toggleTool.bind(null, node));
+  }
+  // Toggle again to reset tool enablement state
+  for (let node of toolNodes) {
+    p = p.then(toggleTool.bind(null, node));
   }
-  else if (index < 2*prefNodes.length) {
-    gDevTools.once("tool-registered", checkRegistered);
-    let node = prefNodes[index - prefNodes.length];
-    node.scrollIntoView();
-    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
+
+  // Test that a tool can still be added when no tabs are present:
+  // Disable all tools
+  for (let node of enabledTools) {
+    p = p.then(toggleTool.bind(null, node));
+  }
+  // Re-enable the tools which are enabled by default
+  for (let node of enabledTools) {
+    p = p.then(toggleTool.bind(null, node));
   }
-  else {
-    cleanup();
-  }
+
+  // Toggle first, middle, and last tools to ensure that toolbox tabs are
+  // inserted in order
+  let firstTool  = toolNodes[0],
+      middleTool = toolNodes[(toolNodes.length / 2) | 0],
+      lastTool   = toolNodes[toolNodes.length - 1];
+
+  p = p.then(toggleTool.bind(null, firstTool))
+       .then(toggleTool.bind(null, firstTool))
+       .then(toggleTool.bind(null, middleTool))
+       .then(toggleTool.bind(null, middleTool))
+       .then(toggleTool.bind(null, lastTool))
+       .then(toggleTool.bind(null, lastTool));
+
+  return p;
 }
 
-function checkUnregistered(event, data) {
-  if (data.id == prefNodes[index].getAttribute("id")) {
+function toggleTool(node) {
+  let deferred = promise.defer();
+
+  let toolId = node.getAttribute("id");
+  if (node.checked) {
+    gDevTools.once("tool-unregistered", checkUnregistered.bind(null, toolId, deferred));
+  } else {
+    gDevTools.once("tool-registered", checkRegistered.bind(null, toolId, deferred));
+  }
+  node.scrollIntoView();
+  EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
+
+  return deferred.promise;
+}
+
+function checkUnregistered(toolId, deferred, event, data) {
+  if (data.id == toolId) {
     ok(true, "Correct tool removed");
     // checking tab on the toolbox
-    ok(!doc.getElementById("toolbox-tab-" + data.id), "Tab removed for " +
-       data.id);
-    index++;
-    // Wait for the next turn of the event loop to avoid stack overflow errors.
-    executeSoon(toggleTools);
-    return;
+    ok(!doc.getElementById("toolbox-tab-" + toolId), "Tab removed for " + toolId);
+  } else {
+    ok(false, "Something went wrong, " + toolId + " was not unregistered");
   }
-  ok(false, "Something went wrong, " + data.id + " was not unregistered");
-  cleanup();
+  deferred.resolve();
 }
 
-function checkRegistered(event, data) {
-  if (data == prefNodes[index - prefNodes.length].getAttribute("id")) {
+function checkRegistered(toolId, deferred, event, data) {
+  if (data == toolId) {
     ok(true, "Correct tool added back");
     // checking tab on the toolbox
-    let radio = doc.getElementById("toolbox-tab-" + data);
-    ok(radio, "Tab added back for " + data);
+    let radio = doc.getElementById("toolbox-tab-" + toolId);
+    ok(radio, "Tab added back for " + toolId);
     if (radio.previousSibling) {
       ok(+radio.getAttribute("ordinal") >=
          +radio.previousSibling.getAttribute("ordinal"),
          "Inserted tab's ordinal is greater than equal to its previous tab." +
          "Expected " + radio.getAttribute("ordinal") + " >= " +
          radio.previousSibling.getAttribute("ordinal"));
     }
     if (radio.nextSibling) {
       ok(+radio.getAttribute("ordinal") <
          +radio.nextSibling.getAttribute("ordinal"),
          "Inserted tab's ordinal is less than its next tab. Expected " +
          radio.getAttribute("ordinal") + " < " +
          radio.nextSibling.getAttribute("ordinal"));
     }
-    index++;
-    // Wait for the next turn of the event loop to avoid stack overflow errors.
-    executeSoon(toggleTools);
-    return;
+  } else {
+    ok(false, "Something went wrong, " + toolId + " was not registered");
   }
-  ok(false, "Something went wrong, " + data + " was not registered back");
-  cleanup();
+  deferred.resolve();
 }
 
 function cleanup() {
   toolbox.destroy().then(function() {
     gBrowser.removeCurrentTab();
-    toolbox = doc = prefNodes = prefValues = panelWin = null;
+    for (let pref of modifiedPrefs) {
+      Services.prefs.clearUserPref(pref);
+    }
+    toolbox = doc = panelWin = modifiedPrefs = null;
     finish();
   });
 }
+
+function errorHandler(error) {
+  ok(false, "Unexpected error: " + error);
+  cleanup();
+}
--- a/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
@@ -1,29 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let Toolbox = devtools.Toolbox;
 
-let toolbox, toolIDs, idIndex;
+let toolbox, toolIDs, idIndex, modifiedPrefs = [];
 
 function test() {
   waitForExplicitFinish();
 
   if (window.navigator.userAgent.indexOf("Mac OS X 10.8") != -1 ||
       window.navigator.userAgent.indexOf("Windows NT 5.1") != -1) {
     info("Skipping Mac OSX 10.8 and Windows xp, see bug 838069");
     finish();
     return;
   }
   addTab("about:blank", function() {
     toolIDs = [];
     for (let [id, definition] of gDevTools._tools) {
       if (definition.key) {
         toolIDs.push(id);
+
+        // Enable disabled tools
+        let pref = definition.visibilityswitch, prefValue;
+        try {
+          prefValue = Services.prefs.getBoolPref(pref);
+        } catch (e) {
+          continue;
+        }
+        if (!prefValue) {
+          modifiedPrefs.push(pref);
+          Services.prefs.setBoolPref(pref, true);
+        }
       }
     }
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     idIndex = 0;
     gDevTools.showToolbox(target, toolIDs[0], Toolbox.HostType.WINDOW)
              .then(testShortcuts);
   });
 }
@@ -66,12 +78,15 @@ function selectCB(event, id) {
 
   testShortcuts(toolbox, idIndex + 1);
 }
 
 function tidyUp() {
   toolbox.destroy().then(function() {
     gBrowser.removeCurrentTab();
 
-    toolbox = toolIDs = idIndex = Toolbox = null;
+    for (let pref of modifiedPrefs) {
+      Services.prefs.clearUserPref(pref);
+    }
+    toolbox = toolIDs = idIndex = modifiedPrefs = Toolbox = null;
     finish();
   });
 }
--- a/browser/metro/base/content/ContextCommands.js
+++ b/browser/metro/base/content/ContextCommands.js
@@ -254,16 +254,34 @@ var ContextCommands = {
   findInPage: function cc_findInPage() {
     FindHelperUI.show();
   },
 
   viewOnDesktop: function cc_viewOnDesktop() {
     Appbar.onViewOnDesktop();
   },
 
+  // Checks for MS app store specific meta data, and if present opens
+  // the Windows Store to the appropriate app
+  openWindowsStoreLink: function cc_openWindowsStoreLink() {
+    let storeLink = this.getStoreLink();
+    if (storeLink) {
+      Browser.selectedBrowser.contentWindow.document.location = storeLink;
+    }
+  },
+
+  getStoreLink: function cc_getStoreLink() {
+    let metaData = Browser.selectedBrowser.contentWindow.document.getElementsByTagName("meta");
+    let msApplicationName = metaData.namedItem("msApplication-PackageFamilyName");
+    if (msApplicationName) {
+      return "ms-windows-store:PDP?PFN=" + msApplicationName.getAttribute("content");
+    }
+    return null;
+  },
+
   /*
    * Utilities
    */
 
   saveToWinLibrary: function cc_saveToWinLibrary(aType) {
     let popupState = ContextMenuUI.popupState;
     let browser = popupState.target;
 
--- a/browser/metro/base/content/appbar.js
+++ b/browser/metro/base/content/appbar.js
@@ -112,16 +112,18 @@ var Appbar = {
     }
   },
 
   onMenuButton: function(aEvent) {
       let typesArray = [];
 
       if (!BrowserUI.isStartTabVisible)
         typesArray.push("find-in-page");
+      if (ContextCommands.getStoreLink())
+        typesArray.push("ms-meta-data");
       if (ConsolePanelView.enabled)
         typesArray.push("open-error-console");
       if (!Services.metro.immersive)
         typesArray.push("open-jsshell");
 
       try {
         // If we have a valid http or https URI then show the view on desktop
         // menu item.
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -783,16 +783,19 @@
 
           <!-- standard buttons -->
           <richlistitem id="context-findinpage" type="find-in-page" onclick="ContextCommands.findInPage();">
             <label value="&appbarFindInPage2.label;"/>
           </richlistitem>
           <richlistitem id="context-viewondesktop" type="view-on-desktop" onclick="ContextCommands.viewOnDesktop();">
             <label value="&appbarViewOnDesktop2.label;"/>
           </richlistitem>
+          <richlistitem id="context-msmetadata" type="ms-meta-data" onclick="ContextCommands.openWindowsStoreLink();">
+            <label value="&appbarMSMetaData2.label;"/>
+          </richlistitem>
         </richlistbox>
       </vbox>
     </box>
 
     <vbox id="select-container" class="menu-container" hidden="true">
       <vbox id="select-popup" class="select-popup">
         <richlistbox id="select-commands" flex="1"/>
       </vbox>
--- a/browser/metro/locales/en-US/chrome/browser.dtd
+++ b/browser/metro/locales/en-US/chrome/browser.dtd
@@ -12,16 +12,17 @@
 <!ENTITY closetab.label        "Close Tab">
 
 <!ENTITY autocompleteResultsHeader.label  "Your Results">
 
 <!ENTITY appbarErrorConsole.label   "Open error console">
 <!ENTITY appbarJSShell.label        "Open JavaScript shell">
 <!ENTITY appbarFindInPage2.label    "Find in page">
 <!ENTITY appbarViewOnDesktop2.label "View on desktop">
+<!ENTITY appbarMSMetaData2.label    "Get app for this site">
 
 <!ENTITY topSitesHeader.label        "Top Sites">
 <!ENTITY bookmarksHeader.label       "Bookmarks">
 <!ENTITY recentHistoryHeader.label   "Recent History">
 <!ENTITY remoteTabsHeader.label      "Tabs from Other Devices">
 
 <!-- LOCALIZATION NOTE (narrowTopSitesHeader.label,
                         narrowBookmarksHeader.label,
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -16,16 +16,20 @@ import org.mozilla.gecko.PageActionLayou
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.StringUtils;
+import org.mozilla.gecko.widget.GeckoImageButton;
+import org.mozilla.gecko.widget.GeckoImageView;
+import org.mozilla.gecko.widget.GeckoRelativeLayout;
+import org.mozilla.gecko.widget.GeckoTextView;
 
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
--- a/mobile/android/base/BrowserToolbarBackground.java
+++ b/mobile/android/base/BrowserToolbarBackground.java
@@ -1,14 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.widget.GeckoLinearLayout;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
--- a/mobile/android/base/CustomEditText.java
+++ b/mobile/android/base/CustomEditText.java
@@ -1,15 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.widget.GeckoEditText;
+
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
 
 public class CustomEditText extends GeckoEditText {
     private OnKeyPreImeListener mOnKeyPreImeListener;
     private OnSelectionChangedListener mOnSelectionChangedListener;
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -293,31 +293,26 @@ WEBRTC_JAVA_FILES = \
   $(addprefix $(DEPTH)/media/webrtc/trunk/webrtc/modules/audio_device/android/org/webrtc/voiceengine/, $(WEBRTC_AUDIO_CAPTURE_JAVA_FILES)) \
   $(NULL)
 endif
 
 ifdef MOZ_ANDROID_ANR_REPORTER
 DEFINES += -DMOZ_ANDROID_ANR_REPORTER=1
 endif
 
-FENNEC_PP_JAVA_VIEW_FILES = \
-  GeckoButton.java \
-  GeckoImageButton.java \
-  GeckoImageView.java \
-  GeckoEditText.java \
-  GeckoFrameLayout.java \
-  GeckoLinearLayout.java \
-  GeckoRelativeLayout.java \
-  GeckoTextSwitcher.java \
-  GeckoTextView.java \
-  $(NULL)
-
-FENNEC_PP_JAVA_FILES = \
+FENNEC_PP_JAVA_FILES := \
   App.java \
   AppConstants.java \
+  widget/GeckoEditText.java \
+  widget/GeckoImageButton.java \
+  widget/GeckoImageView.java \
+  widget/GeckoLinearLayout.java \
+  widget/GeckoRelativeLayout.java \
+  widget/GeckoTextSwitcher.java \
+  widget/GeckoTextView.java \
   SysInfo.java \
   WebApp.java \
   WebApps.java \
   $(NULL)
 
 FENNEC_PP_XML_FILES = \
   res/xml/preferences.xml \
   res/xml/preferences_customize.xml \
@@ -390,17 +385,16 @@ ifdef MOZ_LINKER_EXTRACT
 DEFINES += -DMOZ_LINKER_EXTRACT=1
 endif
 
 GARBAGE += \
   AndroidManifest.xml  \
   classes.dex  \
   $(MOZGLUE_PP_JAVA_FILES) \
   $(FENNEC_PP_JAVA_FILES) \
-  $(FENNEC_PP_JAVA_VIEW_FILES) \
   $(SYNC_PP_JAVA_FILES) \
   gecko.ap_  \
   res/values/strings.xml \
   R.java \
   $(FENNEC_PP_XML_FILES) \
   $(SYNC_PP_RES_XML) \
   package-name.txt \
   fennec_ids.txt \
@@ -1240,17 +1234,16 @@ websockets_JAVAC_FLAGS := -Xlint:all,-se
 JAVA_JAR_TARGETS += gecko-browser
 gecko-browser_DEST := jars/gecko-browser.jar
 gecko-browser_JAVAFILES := \
   $(FENNEC_JAVA_FILES) \
   $(SYNC_JAVA_FILES) \
   $(NULL)
 gecko-browser_PP_JAVAFILES := \
   $(FENNEC_PP_JAVA_FILES) \
-  $(FENNEC_PP_JAVA_VIEW_FILES) \
   $(SYNC_PP_JAVA_FILES) \
   R.java \
   $(NULL)
 gecko-browser_EXTRA_JARS := \
   jars/gecko-mozglue.jar \
   jars/gecko-util.jar \
   jars/sync-thirdparty.jar \
   jars/websockets.jar \
@@ -1266,17 +1259,16 @@ gecko-mozglue_PP_JAVAFILES := \
   $(MOZGLUE_PP_JAVA_FILES) \
   $(NULL)
 gecko-mozglue_JAVAC_FLAGS := -Xlint:all
 
 JAVA_JAR_TARGETS += gecko-util
 gecko-util_DEST := jars/gecko-util.jar
 gecko-util_JAVAFILES := \
   $(UTIL_JAVA_FILES) \
-  $(UTIL_PP_JAVA_FILES) \
   $(NULL)
 gecko-util_EXTRA_JARS := \
   jars/gecko-mozglue.jar \
   $(NULL)
 gecko-util_JAVAC_FLAGS := -Xlint:all,-deprecation
 
 JAVA_JAR_TARGETS += sync-thirdparty
 sync-thirdparty_DEST := jars/sync-thirdparty.jar
@@ -1359,31 +1351,27 @@ android-tgts = \
   $(MOZGLUE_PP_JAVA_FILES) \
   $(FENNEC_PP_JAVA_FILES) \
   $(SYNC_PP_JAVA_FILES) \
   package-name.txt \
   $(NULL)
 
 android-preqs = \
   Makefile.in \
+  widget/GeckoView.java.frag \
   $(call mkdir_deps,$(sort $(dir $(MOZGLUE_PP_JAVA_FILES)))) \
   $(call mkdir_deps,$(sort $(dir $(FENNEC_PP_JAVA_FILES)))) \
   $(call mkdir_deps,$(sort $(dir $(SYNC_PP_JAVA_FILES)))) \
   $(SERVICES_MANIFEST_FRAGMENTS) \
   $(NULL)
 
 $(android-tgts): % : %.in $(android-preqs)
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
              $(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $< > $@
 
-# Replace @VIEWTYPE@ with different View names.
-$(FENNEC_PP_JAVA_VIEW_FILES): GeckoView.java.frag
-	@rm -f $@
-	cat $< | sed s,@VIEWTYPE@,$(patsubst Gecko%.java,%,$@),g >> $@ ; \
-
 res/drawable-mdpi/icon.png: $(ICON_PATH)
 	$(NSINSTALL) -D res/drawable-mdpi
 	cp $(ICON_PATH) $@
 
 res/drawable-hdpi/icon.png: $(ICON_PATH_HDPI)
 	$(NSINSTALL) -D res/drawable-hdpi
 	cp $(ICON_PATH_HDPI) $@
 
--- a/mobile/android/base/ShapedButton.java
+++ b/mobile/android/base/ShapedButton.java
@@ -1,14 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.widget.GeckoImageButton;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
--- a/mobile/android/base/TabCounter.java
+++ b/mobile/android/base/TabCounter.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.animation.Rotate3DAnimation;
+import org.mozilla.gecko.widget.GeckoTextSwitcher;
 
 import android.content.Context;
 import android.os.Build;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AlphaAnimation;
 import android.view.LayoutInflater;
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -123,29 +123,29 @@
                      style="@style/UrlBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_lock_width"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="-4dip"
                      android:src="@drawable/site_security_level"
                      android:contentDescription="@string/site_security"
                      android:visibility="gone"/>
 
-        <org.mozilla.gecko.GeckoTextView android:id="@+id/url_bar_title"
-                                         style="@style/UrlBar.Button"
-                                         android:layout_width="fill_parent"
-                                         android:layout_height="fill_parent"
-                                         android:layout_weight="1.0"
-                                         android:singleLine="true"
-                                         android:paddingRight="8dp"
-                                         android:textColor="@color/url_bar_title"
-                                         android:textColorHint="@color/url_bar_title_hint"
-                                         android:gravity="center_vertical|left"
-                                         android:hint="@string/url_bar_default_text"
-                                         android:layout_gravity="center_vertical"
-                                         gecko:autoUpdateTheme="false"/>
+        <org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
+                                                style="@style/UrlBar.Button"
+                                                android:layout_width="fill_parent"
+                                                android:layout_height="fill_parent"
+                                                android:layout_weight="1.0"
+                                                android:singleLine="true"
+                                                android:paddingRight="8dp"
+                                                android:textColor="@color/url_bar_title"
+                                                android:textColorHint="@color/url_bar_title_hint"
+                                                android:gravity="center_vertical|left"
+                                                android:hint="@string/url_bar_default_text"
+                                                android:layout_gravity="center_vertical"
+                                                gecko:autoUpdateTheme="false"/>
 
         <org.mozilla.gecko.PageActionLayout android:id="@+id/page_action_layout"
                                             android:layout_width="wrap_content"
                                             android:layout_height="match_parent"
                                             android:layout_marginRight="@dimen/browser_toolbar_button_padding"
                                             android:visibility="gone"
                                             android:orientation="horizontal"/>
 
@@ -160,31 +160,31 @@
     <LinearLayout android:id="@+id/menu_items"
                   android:layout_width="wrap_content"
                   android:layout_height="fill_parent"
                   android:layout_marginLeft="3dp"
                   android:orientation="horizontal"
                   android:layout_toLeftOf="@id/menu"
                   android:layout_alignWithParentIfMissing="true"/>
 
-    <org.mozilla.gecko.GeckoImageButton android:id="@+id/menu"
-                                        style="@style/UrlBar.ImageButton"
-                                        android:layout_width="56dip"
-                                        android:layout_alignParentRight="true"
-                                        android:contentDescription="@string/menu"
-                                        android:background="@drawable/action_bar_button"
-                                        android:visibility="gone"/>
+    <org.mozilla.gecko.widget.GeckoImageButton android:id="@+id/menu"
+                                               style="@style/UrlBar.ImageButton"
+                                               android:layout_width="56dip"
+                                               android:layout_alignParentRight="true"
+                                               android:contentDescription="@string/menu"
+                                               android:background="@drawable/action_bar_button"
+                                               android:visibility="gone"/>
 
-    <org.mozilla.gecko.GeckoImageView android:id="@+id/menu_icon"
-                                      style="@style/UrlBar.ImageButton"
-                                      android:layout_alignLeft="@id/menu"
-                                      android:layout_alignRight="@id/menu"
-                                      android:gravity="center_vertical"
-                                      android:src="@drawable/menu_level"
-                                      android:visibility="gone"/>
+    <org.mozilla.gecko.widget.GeckoImageView android:id="@+id/menu_icon"
+                                             style="@style/UrlBar.ImageButton"
+                                             android:layout_alignLeft="@id/menu"
+                                             android:layout_alignRight="@id/menu"
+                                             android:gravity="center_vertical"
+                                             android:src="@drawable/menu_level"
+                                             android:visibility="gone"/>
 
     <ImageView android:id="@+id/shadow"
                android:layout_width="fill_parent"
                android:layout_height="2dp"
                android:layout_alignParentBottom="true"
                android:background="@color/url_bar_shadow"
                android:contentDescription="@null"/>
 
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -51,23 +51,23 @@
     <org.mozilla.gecko.ShapedButton android:id="@+id/menu"
                                     style="@style/UrlBar.ImageButton"
                                     android:layout_width="48dip"
                                     android:layout_alignParentRight="true"
                                     android:contentDescription="@string/menu"
                                     android:background="@drawable/shaped_button"
                                     android:visibility="gone"/>
 
-    <org.mozilla.gecko.GeckoImageView android:id="@+id/menu_icon"
-                                      style="@style/UrlBar.ImageButton"
-                                      android:layout_alignLeft="@id/menu"
-                                      android:layout_alignRight="@id/menu"
-                                      android:gravity="center_vertical"
-                                      android:src="@drawable/menu_level"
-                                      android:visibility="gone"/>
+    <org.mozilla.gecko.widget.GeckoImageView android:id="@+id/menu_icon"
+                                             style="@style/UrlBar.ImageButton"
+                                             android:layout_alignLeft="@id/menu"
+                                             android:layout_alignRight="@id/menu"
+                                             android:gravity="center_vertical"
+                                             android:src="@drawable/menu_level"
+                                             android:visibility="gone"/>
 
     <org.mozilla.gecko.ShapedButton android:id="@+id/tabs"
                                     style="@style/UrlBar.ImageButton"
                                     android:layout_width="72dip"
                                     android:layout_toLeftOf="@id/menu"
                                     android:layout_alignWithParentIfMissing="true"
                                     gecko:curveTowards="right"
                                     android:background="@drawable/shaped_button"
@@ -148,29 +148,29 @@
                      style="@style/UrlBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_lock_width"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="-4dip"
                      android:src="@drawable/site_security_level"
                      android:contentDescription="@string/site_security"
                      android:visibility="gone"/>
 
-        <org.mozilla.gecko.GeckoTextView android:id="@+id/url_bar_title"
-                                         style="@style/UrlBar.Button"
-                                         android:layout_width="fill_parent"
-                                         android:layout_height="fill_parent"
-                                         android:layout_weight="1.0"
-                                         android:singleLine="true"
-                                         android:paddingRight="8dp"
-                                         android:textColor="@color/url_bar_title"
-                                         android:textColorHint="@color/url_bar_title_hint"
-                                         android:gravity="center_vertical|left"
-                                         android:hint="@string/url_bar_default_text"
-                                         android:layout_gravity="center_vertical"
-                                         gecko:autoUpdateTheme="false"/>
+        <org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
+                                                style="@style/UrlBar.Button"
+                                                android:layout_width="fill_parent"
+                                                android:layout_height="fill_parent"
+                                                android:layout_weight="1.0"
+                                                android:singleLine="true"
+                                                android:paddingRight="8dp"
+                                                android:textColor="@color/url_bar_title"
+                                                android:textColorHint="@color/url_bar_title_hint"
+                                                android:gravity="center_vertical|left"
+                                                android:hint="@string/url_bar_default_text"
+                                                android:layout_gravity="center_vertical"
+                                                gecko:autoUpdateTheme="false"/>
 
         <org.mozilla.gecko.PageActionLayout android:id="@+id/page_action_layout"
                                             android:layout_width="wrap_content"
                                             android:layout_height="match_parent"
                                             android:layout_marginRight="12dp"
                                             android:visibility="gone"
                                             android:orientation="horizontal"/>
 
--- a/mobile/android/base/resources/layout/tabs_counter.xml
+++ b/mobile/android/base/resources/layout/tabs_counter.xml
@@ -1,17 +1,17 @@
 <?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/. -->
 
-<org.mozilla.gecko.GeckoTextView xmlns:android="http://schemas.android.com/apk/res/android"
-                                 android:layout_width="24dip"
-                                 android:layout_height="24dip"
-                                 android:layout_margin="12dip"
-                                 android:paddingTop="2dip"
-                                 android:paddingLeft="4dip"
-                                 android:background="@drawable/tabs_count_foreground"
-                                 android:textAppearance="@style/TextAppearance.Micro"
-                                 android:textColor="#FF43484E"
-                                 android:textStyle="bold"
-                                 android:duplicateParentState="true"
-                                 android:gravity="center"/>
+<org.mozilla.gecko.widget.GeckoTextView xmlns:android="http://schemas.android.com/apk/res/android"
+                                        android:layout_width="24dip"
+                                        android:layout_height="24dip"
+                                        android:layout_margin="12dip"
+                                        android:paddingTop="2dip"
+                                        android:paddingLeft="4dip"
+                                        android:background="@drawable/tabs_count_foreground"
+                                        android:textAppearance="@style/TextAppearance.Micro"
+                                        android:textColor="#FF43484E"
+                                        android:textStyle="bold"
+                                        android:duplicateParentState="true"
+                                        android:gravity="center"/>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoEditText.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE EditText
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoImageButton.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE ImageButton
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoImageView.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE ImageView
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoLinearLayout.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE LinearLayout
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoRelativeLayout.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE RelativeLayout
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoTextSwitcher.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE TextSwitcher
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoTextView.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE TextView
+#include GeckoView.java.frag
rename from mobile/android/base/GeckoView.java.frag
rename to mobile/android/base/widget/GeckoView.java.frag
--- a/mobile/android/base/GeckoView.java.frag
+++ b/mobile/android/base/widget/GeckoView.java.frag
@@ -1,13 +1,17 @@
 /* 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/. */
 
-package org.mozilla.gecko;
+package org.mozilla.gecko.widget;
+
+import org.mozilla.gecko.GeckoActivity;
+import org.mozilla.gecko.LightweightTheme;
+import org.mozilla.gecko.R;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.widget.@VIEWTYPE@;
 
 public class Gecko@VIEWTYPE@ extends @VIEWTYPE@
                              implements LightweightTheme.OnChangeListener { 
--- a/toolkit/components/jsdownloads/src/DownloadCore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm
@@ -1666,17 +1666,26 @@ DownloadCopySaver.prototype = {
         // up the chain of objects for the download.
         yield deferSaveComplete.promise;
       } catch (ex) {
         // Ensure we always remove the placeholder for the final target file on
         // failure, independently of which code path failed.  In some cases, the
         // background file saver may have already removed the file.
         try {
           yield OS.File.remove(targetPath);
-        } catch (e2 if e2 instanceof OS.File.Error && e2.becauseNoSuchFile) { }
+        } catch (e2) {
+          // If we failed during the operation, we report the error but use the
+          // original one as the failure reason of the download.  Note that on
+          // Windows we may get an access denied error instead of a no such file
+          // error if the file existed before, and was recently deleted.
+          if (!(e2 instanceof OS.File.Error &&
+                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
+            Cu.reportError(e2);
+          }
+        }
         throw ex;
       }
     }.bind(this));
   },
 
   /**
    * Implements "DownloadSaver.cancel".
    */
@@ -1942,16 +1951,33 @@ DownloadLegacySaver.prototype = {
           // empty file is created as expected.
           try {
             // This atomic operation is more efficient than an existence check.
             let file = yield OS.File.open(this.download.target.path,
                                           { create: true });
             yield file.close();
           } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { }
         }
+      } catch (ex) {
+        // Ensure we always remove the final target file on failure,
+        // independently of which code path failed.  In some cases, the
+        // component executing the download may have already removed the file.
+        try {
+          yield OS.File.remove(this.download.target.path);
+        } catch (e2) {
+          // If we failed during the operation, we report the error but use the
+          // original one as the failure reason of the download.  Note that on
+          // Windows we may get an access denied error instead of a no such file
+          // error if the file existed before, and was recently deleted.
+          if (!(e2 instanceof OS.File.Error &&
+                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
+            Cu.reportError(e2);
+          }
+        }
+        throw ex;
       } finally {
         // We don't need the reference to the request anymore.
         this.request = null;
         // Allow the download to restart through a DownloadCopySaver.
         this.firstExecutionFinished = true;
       }
     }.bind(this));
   },
--- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js
+++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js
@@ -535,16 +535,38 @@ add_task(function test_cancel_midway()
     } catch (ex if ex instanceof Downloads.Error) {
       do_check_false(ex.becauseSourceFailed);
       do_check_false(ex.becauseTargetFailed);
     }
   }
 });
 
 /**
+ * Cancels a download while keeping partially downloaded data, and verifies that
+ * both the target file and the ".part" file are deleted.
+ */
+add_task(function test_cancel_midway_tryToKeepPartialData()
+{
+  let download = yield promiseStartDownload_tryToKeepPartialData();
+
+  do_check_true(yield OS.File.exists(download.target.path));
+  do_check_true(yield OS.File.exists(download.target.partFilePath));
+
+  yield download.cancel();
+  yield download.removePartialData();
+
+  do_check_true(download.stopped);
+  do_check_true(download.canceled);
+  do_check_true(download.error === null);
+
+  do_check_false(yield OS.File.exists(download.target.path));
+  do_check_false(yield OS.File.exists(download.target.partFilePath));
+});
+
+/**
  * Cancels a download right after starting it.
  */
 add_task(function test_cancel_immediately()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
@@ -1027,16 +1049,18 @@ add_task(function test_error_source()
     }
 
     // Check the properties now that the download stopped.
     do_check_true(download.stopped);
     do_check_false(download.canceled);
     do_check_true(download.error !== null);
     do_check_true(download.error.becauseSourceFailed);
     do_check_false(download.error.becauseTargetFailed);
+
+    do_check_false(yield OS.File.exists(download.target.path));
   } finally {
     serverSocket.close();
   }
 });
 
 /**
  * Ensures download error details are reported on local writing failures.
  */
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -810,17 +810,19 @@ add_task(function test_common_initialize
                              "Synchronous promptForSaveToFile not implemented.",
                              Cr.NS_ERROR_NOT_AVAILABLE);
         },
         promptForSaveToFileAsync: function (aLauncher, aWindowContext,
                                             aDefaultFileName,
                                             aSuggestedFileExtension,
                                             aForcePrompt)
         {
+          // The dialog should create the empty placeholder file.
           let file = getTempFile(TEST_TARGET_FILE_NAME);
+          file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
           aLauncher.saveDestinationAvailable(file);
         },
       }.QueryInterface(aIid);
     }
   };
 
   let contractID = "@mozilla.org/helperapplauncherdialog;1";
   let cid = registrar.contractIDToCID(contractID);
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -766,16 +766,21 @@ File.writeAtomic = function writeAtomic(
   //   threads;
   // - we take care of any |byteOffset|.
   return Scheduler.post("writeAtomic",
     [Type.path.toMsg(path),
      Type.void_t.in_ptr.toMsg(buffer),
      options], [options, buffer]);
 };
 
+File.removeDir = function(path, options = {}) {
+  return Scheduler.post("removeDir",
+    [Type.path.toMsg(path), options], path);
+};
+
 /**
  * Information on a file, as returned by OS.File.stat or
  * OS.File.prototype.stat
  *
  * @constructor
  */
 File.Info = function Info(value) {
   // Note that we can't just do this[k] = value[k] because our
--- a/toolkit/components/osfile/modules/osfile_async_worker.js
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -281,16 +281,19 @@ if (this.Components) {
      if (options.tmpPath) {
        options.tmpPath = Type.path.fromMsg(options.tmpPath);
      }
      return File.writeAtomic(Type.path.fromMsg(path),
                              Type.voidptr_t.fromMsg(buffer),
                              options
                             );
    },
+   removeDir: function(path, options) {
+     return File.removeDir(Type.path.fromMsg(path), options);
+   },
    new_DirectoryIterator: function new_DirectoryIterator(path, options) {
      let directoryPath = Type.path.fromMsg(path);
      let iterator = new File.DirectoryIterator(directoryPath, options);
      return OpenedDirectoryIterators.add(iterator, {
        // Adding path information to keep track of opened directory
        // iterators to report leaks when debugging.
        path: directoryPath
      });
--- a/toolkit/components/osfile/modules/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -389,10 +389,44 @@ AbstractFile.writeAtomic =
   } finally {
     tmpFile.close();
   }
 
   OS.File.move(options.tmpPath, path, {noCopy: true});
   return bytesWritten;
 };
 
+/**
+  * Remove an existing directory and its contents.
+  *
+  * @param {string} path The name of the directory.
+  * @param {*=} options Additional options.
+  *   - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+  *     exist. |true| by default.
+  *   - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+  *     permission.
+  *
+  * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+            not a directory.
+  */
+AbstractFile.removeDir = function(path, options = {}) {
+  let iterator = new OS.File.DirectoryIterator(path);
+  if (!iterator.exists() && options.ignoreAbsent) {
+    return;
+  }
+
+  try {
+    for (let entry in iterator) {
+      if (entry.isDir) {
+        OS.File.removeDir(entry.path, options);
+      } else {
+        OS.File.remove(entry.path, options);
+      }
+    }
+  } finally {
+    iterator.close();
+  }
+
+  OS.File.removeEmptyDir(path);
+};
+
    exports.OS.Shared.AbstractFile = AbstractFile;
 })(this);
--- a/toolkit/components/osfile/modules/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -812,16 +812,17 @@
        } else {
          throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr));
        }
        return new File.Info(gStatData);
      };
 
      File.read = exports.OS.Shared.AbstractFile.read;
      File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+     File.removeDir = exports.OS.Shared.AbstractFile.removeDir;
 
      /**
       * Get the current directory by getCurrentDirectory.
       */
      File.getCurrentDirectory = function getCurrentDirectory() {
        let path = UnixFile.get_current_dir_name?UnixFile.get_current_dir_name():
          UnixFile.getwd_auto(null);
        throw_on_null("getCurrentDirectory",path);
--- a/toolkit/components/osfile/modules/osfile_win_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_win_front.jsm
@@ -751,16 +751,17 @@
        winAccess: 0,
        // Directories can only be opened with backup semantics(!)
        winFlags: OS.Constants.Win.FILE_FLAG_BACKUP_SEMANTICS,
        winDisposition: OS.Constants.Win.OPEN_EXISTING
      };
 
      File.read = exports.OS.Shared.AbstractFile.read;
      File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+     File.removeDir = exports.OS.Shared.AbstractFile.removeDir;
 
      /**
       * Get the current directory by getCurrentDirectory.
       */
      File.getCurrentDirectory = function getCurrentDirectory() {
            // This function is more complicated than one could hope.
            //
            // This is due to two facts:
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/tests/xpcshell/test_removeDir.js
@@ -0,0 +1,88 @@
+/* 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";
+
+Components.utils.import("resource://gre/modules/osfile.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+do_register_cleanup(function() {
+  Services.prefs.setBoolPref("toolkit.osfile.log", false);
+});
+
+function run_test() {
+  Services.prefs.setBoolPref("toolkit.osfile.log", true);
+
+  run_next_test();
+}
+
+add_task(function() {
+  // Set up profile. We create the directory in the profile, because the profile
+  // is removed after every test run.
+  do_get_profile();
+
+  let file = OS.Path.join(OS.Constants.Path.profileDir, "file");
+  let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory");
+  let file1 = OS.Path.join(dir, "file1");
+  let file2 = OS.Path.join(dir, "file2");
+  let subDir = OS.Path.join(dir, "subdir");
+  let fileInSubDir = OS.Path.join(subDir, "file");
+
+  // Sanity checking for the test
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove non-existent directory
+  let exception = null;
+  try {
+    yield OS.File.removeDir(dir);
+  } catch (ex) {
+    exception = ex;
+  }
+
+  do_check_true(!!exception);
+  do_check_true(exception instanceof OS.File.Error);
+
+  // Remove non-existent directory with ignoreAbsent
+  yield OS.File.removeDir(dir, {ignoreAbsent: true});
+
+  // Remove file
+  yield OS.File.writeAtomic(file, "content", { tmpPath: file + ".tmp" });
+  exception = null;
+  try {
+    yield OS.File.removeDir(file);
+  } catch (ex) {
+    exception = ex;
+  }
+
+  do_check_true(!!exception);
+  do_check_true(exception instanceof OS.File.Error);
+
+  // Remove empty directory
+  yield OS.File.makeDir(dir);
+  yield OS.File.removeDir(dir);
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove directory that contains one file
+  yield OS.File.makeDir(dir);
+  yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" });
+  //yield OS.File.open(file1, {create:true});
+  yield OS.File.removeDir(dir)
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove directory that contains multiple files
+  yield OS.File.makeDir(dir);
+  yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" });
+  yield OS.File.writeAtomic(file2, "content", { tmpPath: file2 + ".tmp" });
+  yield OS.File.removeDir(dir)
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove directory that contains a file and a directory
+  yield OS.File.makeDir(dir);
+  yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" });
+  yield OS.File.makeDir(subDir);
+  yield OS.File.writeAtomic(fileInSubDir, "content", { tmpPath: fileInSubDir + ".tmp" });
+  yield OS.File.removeDir(dir);
+  do_check_false((yield OS.File.exists(dir)));
+});
--- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
@@ -4,8 +4,9 @@ tail =
 
 [test_osfile_closed.js]
 [test_path.js]
 [test_osfile_async.js]
 [test_profiledir.js]
 [test_logging.js]
 [test_creationDate.js]
 [test_path_constants.js]
+[test_removeDir.js]
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -363,42 +363,22 @@ LayoutHelpers.prototype = {
       return win.parent;
     }
   },
 
   /**
    * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
    *
    * @param DOMWindow win The window to get the frame for
-   * @return DOMElement The element(only <iframe> for now) in which the window
-   * is embedded. A null return from this function does not imply that it is a
-   * top level window. Use isTopLevelWindow(win) if needed.
+   * @return DOMElement The element in which the window is embedded.
    */
   getFrameElement: function LH_getFrameElement(win) {
     if (this.isTopLevelWindow(win)) {
       return null;
     }
 
-    // Get the docShell for that window
-    let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIWebNavigation)
-                   .QueryInterface(Ci.nsIDocShell);
+    let winUtils = win.
+      QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+      getInterface(Components.interfaces.nsIDOMWindowUtils);
 
-    if (docShell.isBrowserOrApp) {
-      // Get the docShell's same-type parent ignoring mozBrowser and mozApp
-      // boundaries
-      let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
-      // Once we have a parent, get all its iframes and loop through them
-      // to find `win`. If we do, it means win is a nested iframe
-      let parentDoc = parentDocShell.contentViewer.DOMDocument;
-      let allIframes = parentDoc.querySelectorAll("iframe");
-      for (let f of allIframes) {
-        if (f.contentWindow === win) {
-          return f;
-        }
-      }
-
-      return null;
-    } else {
-      return win.frameElement;
-    }
+    return winUtils.containerElement;
   },
 };