Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 14 Dec 2013 18:37:06 -0800
changeset 160535 6ccc188b79a4d373d58a02e16e95ff1e847cc1ae
parent 160534 d475976b1462493343c343ef03259d6717a693ff (current diff)
parent 160528 c049cb230d77f7b68dc38175a01e7fcf0f6c8bb2 (diff)
child 160536 47595f181b623aa3b69cb55b363a8b4c504b4adf
push id25836
push userphilringnalda@gmail.com
push dateMon, 16 Dec 2013 01:58:54 +0000
treeherdermozilla-central@f8fea5ea69a1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to m-i
browser/base/content/test/general/browser_bug887515.js
browser/base/content/test/general/browser_bug896291_closeMaxSessionStoreTabs.js
mobile/android/base/tests/helpers/GestureHelper.java
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1386360478000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1386891443000">
   <emItems>
       <emItem  blockID="i454" id="sqlmoz@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                                 <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
@@ -101,16 +101,20 @@
       <emItem  blockID="i64" id="royal@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i72" os="WINNT" id="{4ED1F68A-5463-4931-9384-8FFF5ED91D92}">
                         <versionRange  minVersion="3.4.1" maxVersion="3.4.1.194" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i506" id="ext@bettersurfplus.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i100" id="{394DCBA4-1F92-4f8e-8EC9-8D2CB90CB69B}">
                         <versionRange  minVersion="2.5.0" maxVersion="2.5.0" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i40" id="{28387537-e3f9-4ed7-860c-11e69af4a8a0}">
                         <versionRange  minVersion="0.1" maxVersion="4.3.1.00" severity="1">
                     </versionRange>
                   </emItem>
@@ -448,16 +452,20 @@
       <emItem  blockID="i477" id="mbrnovone@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i446" id="{E90FA778-C2B7-41D0-9FA9-3FEC1CA54D66}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i507" id="4zffxtbr-bs@VideoDownloadConverter_4z.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i7" id="{2224e955-00e9-4613-a844-ce69fccaae91}">
                         </emItem>
       <emItem  blockID="i485" id="/^brasilescape.*\@facebook\.com$//">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i52" id="ff-ext@youtube">
                         <versionRange  minVersion="0" maxVersion="*">
@@ -688,16 +696,20 @@
       <emItem  blockID="i354" id="{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i106" os="WINNT" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
                         <versionRange  minVersion="0" maxVersion="15.0.5" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i505" id="extacylife@a.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i15" id="personas@christopher.beard">
                         <versionRange  minVersion="1.6" maxVersion="1.6">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="3.6" maxVersion="3.6.*" />
                           </targetApplication>
                     </versionRange>
                   </emItem>
       <emItem  blockID="i304" id="{f0e59437-6148-4a98-b0a6-60d557ef57f4}">
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6096,43 +6096,26 @@ function convertFromUnicode(charset, str
  * @returns a reference to the reopened tab.
  */
 function undoCloseTab(aIndex) {
   // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
   var blankTabToRemove = null;
   if (gBrowser.tabs.length == 1 && isTabEmpty(gBrowser.selectedTab))
     blankTabToRemove = gBrowser.selectedTab;
 
-  let numberOfTabsToUndoClose = 0;
-  let index = Number(aIndex);
-
-
-  if (isNaN(index)) {
-    index = 0;
-    numberOfTabsToUndoClose = SessionStore.getNumberOfTabsClosedLast(window);
-  } else {
-    if (0 > index || index >= SessionStore.getClosedTabCount(window))
-      return null;
-    numberOfTabsToUndoClose = 1;
+  var tab = null;
+  if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) {
+    TabView.prepareUndoCloseTab(blankTabToRemove);
+    tab = SessionStore.undoCloseTab(window, aIndex || 0);
+    TabView.afterUndoCloseTab();
+
+    if (blankTabToRemove)
+      gBrowser.removeTab(blankTabToRemove);
   }
 
-  let tab = null;
-  while (numberOfTabsToUndoClose > 0 &&
-         numberOfTabsToUndoClose--) {
-    TabView.prepareUndoCloseTab(blankTabToRemove);
-    tab = SessionStore.undoCloseTab(window, index);
-    TabView.afterUndoCloseTab();
-    if (blankTabToRemove) {
-      gBrowser.removeTab(blankTabToRemove);
-      blankTabToRemove = null;
-    }
-  }
-
-  // Reset the number of tabs closed last time to the default.
-  SessionStore.setNumberOfTabsClosedLast(window, 1);
   return tab;
 }
 
 /**
  * Re-open a closed window.
  * @param aIndex
  *        The index of the window (via SessionStore.getClosedWindowData)
  * @returns a reference to the reopened window.
@@ -6945,23 +6928,18 @@ var TabContextMenu = {
       menuItem.disabled = disabled;
 
     disabled = gBrowser.visibleTabs.length == 1;
     menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
 
     // Session store
-    let undoCloseTabElement = document.getElementById("context_undoCloseTab");
-    let closedTabCount = SessionStore.getNumberOfTabsClosedLast(window);
-    undoCloseTabElement.disabled = closedTabCount == 0;
-    // Change the label of "Undo Close Tab" to specify if it will undo a batch-close
-    // or a single close.
-    let visibleLabel = closedTabCount <= 1 ? "singletablabel" : "multipletablabel";
-    undoCloseTabElement.setAttribute("label", undoCloseTabElement.getAttribute(visibleLabel));
+    document.getElementById("context_undoCloseTab").disabled =
+      SessionStore.getClosedTabCount(window) == 0;
 
     // Only one of pin/unpin should be visible
     document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
     document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
 
     // Disable "Close Tabs to the Right" if there are no tabs
     // following it and hide it when the user rightclicked on a pinned
     // tab.
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -110,18 +110,17 @@
                 accesskey="&bookmarkAllTabs.accesskey;"
                 command="Browser:BookmarkAllTabs"/>
       <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
                 oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab);"/>
       <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
                 oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
       <menuseparator/>
       <menuitem id="context_undoCloseTab"
-                singletablabel="&undoCloseTab.label;"
-                multipletablabel="&undoCloseTabs.label;"
+                label="&undoCloseTab.label;"
                 accesskey="&undoCloseTab.accesskey;"
                 observes="History:UndoCloseTab"/>
       <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
                 oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>
     </menupopup>
 
     <!-- bug 415444/582485: event.stopPropagation is here for the cloned version
          of this menupopup -->
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1674,36 +1674,23 @@
                 throw new Error("Required argument missing: aTab");
 
               tabsToClose = this.getTabsToTheEndFrom(aTab).length;
               break;
             default:
               throw new Error("Invalid argument: " + aCloseTabs);
           }
 
-          let maxUndo =
-            Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
-          let warnOnCloseOtherTabs =
-            Services.prefs.getBoolPref("browser.tabs.warnOnCloseOtherTabs");
-          let warnOnCloseWindow =
-            Services.prefs.getBoolPref("browser.tabs.warnOnClose");
-          let isWindowClosing = aCloseTabs == this.closingTabsEnum.ALL;
-
-          let skipWarning =
-            // 1) If there is only one tab to close, we'll never warn the user.
-            tabsToClose <= 1 ||
-            // 2) If the whole window is going to be closed, don't warn the
-            //    user if the user has browser.tabs.warnOnClose set to false.
-            (isWindowClosing && !warnOnCloseWindow) ||
-            // 3) If the number of tabs are less than the undo threshold
-            //    or if the user has specifically opted-in to ignoring
-            //    this warning via the warnOnCloseOtherTabs pref.
-            (!isWindowClosing && (!warnOnCloseOtherTabs ||
-                                            tabsToClose <= maxUndo));
-          if (skipWarning)
+          if (tabsToClose <= 1)
+            return true;
+
+          const pref = aCloseTabs == this.closingTabsEnum.ALL ?
+                       "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
+          var shouldPrompt = Services.prefs.getBoolPref(pref);
+          if (!shouldPrompt)
             return true;
 
           var ps = Services.prompt;
 
           // default to true: if it were false, we wouldn't get this far
           var warnOnClose = { value: true };
           var bundle = this.mStringBundle;
 
@@ -1717,26 +1704,24 @@
             ps.confirmEx(window,
                          bundle.getString("tabs.closeWarningTitle"),
                          bundle.getFormattedString("tabs.closeWarningMultipleTabs",
                                                    [tabsToClose]),
                          (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
                          + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
                          bundle.getString("tabs.closeButtonMultiple"),
                          null, null,
-                         bundle.getString("tabs.closeWarningPromptMe"),
+                         aCloseTabs == this.closingTabsEnum.ALL ?
+                           bundle.getString("tabs.closeWarningPromptMe") : null,
                          warnOnClose);
           var reallyClose = (buttonPressed == 0);
 
           // don't set the pref unless they press OK and it's false
-          if (reallyClose && !warnOnClose.value) {
-            let pref = isWindowClosing ? "browser.tabs.warnOnClose" :
-                                         "browser.tabs.warnOnCloseOtherTabs";
+          if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
             Services.prefs.setBoolPref(pref, false);
-          }
 
           return reallyClose;
         ]]>
       </body>
       </method>
 
       <method name="getTabsToTheEndFrom">
         <parameter name="aTab"/>
@@ -1753,45 +1738,39 @@
       </method>
 
       <method name="removeTabsToTheEndFrom">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
               let tabs = this.getTabsToTheEndFrom(aTab);
-              let numberOfTabsToClose = tabs.length;
-              for (let i = numberOfTabsToClose - 1; i >= 0; --i) {
+              for (let i = tabs.length - 1; i >= 0; --i) {
                 this.removeTab(tabs[i], {animate: true});
               }
-              SessionStore.setNumberOfTabsClosedLast(window, numberOfTabsToClose);
             }
           ]]>
         </body>
       </method>
 
       <method name="removeAllTabsBut">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             if (aTab.pinned)
               return;
 
             if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
               let tabs = this.visibleTabs;
               this.selectedTab = aTab;
 
-              let closedTabs = 0;
               for (let i = tabs.length - 1; i >= 0; --i) {
-                if (tabs[i] != aTab && !tabs[i].pinned) {
+                if (tabs[i] != aTab && !tabs[i].pinned)
                   this.removeTab(tabs[i], {animate: true});
-                  closedTabs++;
-                }
               }
-              SessionStore.setNumberOfTabsClosedLast(window, closedTabs);
             }
           ]]>
         </body>
       </method>
 
       <method name="removeCurrentTab">
         <parameter name="aParams"/>
         <body>
@@ -1810,18 +1789,16 @@
         <parameter name="aParams"/>
         <body>
           <![CDATA[
             if (aParams) {
               var animate = aParams.animate;
               var byMouse = aParams.byMouse;
             }
 
-            SessionStore.setNumberOfTabsClosedLast(window, 1);
-
             // Handle requests for synchronously removing an already
             // asynchronously closing tab.
             if (!animate &&
                 aTab.closing) {
               this._endRemoveTab(aTab);
               return;
             }
 
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -224,18 +224,16 @@ run-if = toolkit == "cocoa"
 [browser_bug817947.js]
 [browser_bug818118.js]
 [browser_bug820497.js]
 [browser_bug822367.js]
 [browser_bug832435.js]
 [browser_bug839103.js]
 [browser_bug880101.js]
 [browser_bug882977.js]
-[browser_bug887515.js]
-[browser_bug896291_closeMaxSessionStoreTabs.js]
 [browser_bug902156.js]
 [browser_bug906190.js]
 [browser_canonizeURL.js]
 [browser_clearplugindata.js]
 [browser_contentAreaClick.js]
 [browser_contextSearchTabPosition.js]
 [browser_ctrlTab.js]
 [browser_customize_popupNotification.js]
deleted file mode 100644
--- a/browser/base/content/test/general/browser_bug887515.js
+++ /dev/null
@@ -1,75 +0,0 @@
-function numClosedTabs()
-  SessionStore.getNumberOfTabsClosedLast(window);
-
-var originalTab;
-var tab1Loaded = false;
-var tab2Loaded = false;
-
-function verifyUndoMultipleClose() {
-  if (!tab1Loaded || !tab2Loaded)
-    return;
-
-  gBrowser.removeAllTabsBut(originalTab);
-  updateTabContextMenu();
-  let undoCloseTabElement = document.getElementById("context_undoCloseTab");
-  ok(!undoCloseTabElement.disabled, "Undo Close Tabs should be enabled.");
-  is(numClosedTabs(), 2, "There should be 2 closed tabs.");
-  is(gBrowser.tabs.length, 1, "There should only be 1 open tab");
-  updateTabContextMenu();
-  is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("multipletablabel"),
-     "The label should be showing that the command will restore multiple tabs");
-  undoCloseTab();
-
-  is(gBrowser.tabs.length, 3, "There should be 3 open tabs");
-  updateTabContextMenu();
-  is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("singletablabel"),
-     "The label should be showing that the command will restore a single tab");
-
-  gBrowser.removeTabsToTheEndFrom(originalTab);
-  updateTabContextMenu();
-  ok(!undoCloseTabElement.disabled, "Undo Close Tabs should be enabled.");
-  is(numClosedTabs(), 2, "There should be 2 closed tabs.");
-  is(gBrowser.tabs.length, 1, "There should only be 1 open tab");
-  updateTabContextMenu();
-  is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("multipletablabel"),
-     "The label should be showing that the command will restore multiple tabs");
-
-  finish();
-}
-
-function test() {
-  waitForExplicitFinish();
-
-  Services.prefs.setBoolPref("browser.tabs.animate", false);
-  registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("browser.tabs.animate");
-    originalTab.linkedBrowser.loadURI("about:blank");
-    originalTab = null;
-  });
-
-  let undoCloseTabElement = document.getElementById("context_undoCloseTab");
-  updateTabContextMenu();
-  is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("singletablabel"),
-     "The label should be showing that the command will restore a single tab");
-
-  originalTab = gBrowser.selectedTab;
-  gBrowser.selectedBrowser.loadURI("http://mochi.test:8888/");
-  var tab1 = gBrowser.addTab("http://mochi.test:8888/");
-  var tab2 = gBrowser.addTab("http://mochi.test:8888/");
-  var browser1 = gBrowser.getBrowserForTab(tab1);
-  browser1.addEventListener("load", function onLoad1() {
-    browser1.removeEventListener("load", onLoad1, true);
-    tab1Loaded = true;
-    tab1 = null;
-
-    verifyUndoMultipleClose();
-  }, true);
-  var browser2 = gBrowser.getBrowserForTab(tab2);
-  browser2.addEventListener("load", function onLoad2() {
-    browser2.removeEventListener("load", onLoad2, true);
-    tab2Loaded = true;
-    tab2 = null;
-
-    verifyUndoMultipleClose();
-  }, true);
-}
deleted file mode 100644
--- a/browser/base/content/test/general/browser_bug896291_closeMaxSessionStoreTabs.js
+++ /dev/null
@@ -1,108 +0,0 @@
-function numClosedTabs()
-  Cc["@mozilla.org/browser/sessionstore;1"].
-    getService(Ci.nsISessionStore).
-    getNumberOfTabsClosedLast(window);
-
-let originalTab;
-let maxTabsUndo;
-let maxTabsUndoPlusOne;
-let acceptRemoveAllTabsDialogListener;
-let cancelRemoveAllTabsDialogListener;
-
-function test() {
-  waitForExplicitFinish();
-  Services.prefs.setBoolPref("browser.tabs.animate", false);
-
-  registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("browser.tabs.animate");
-
-    originalTab.linkedBrowser.loadURI("about:blank");
-    originalTab = null;
-  });
-
-  // Creating and throwing away this tab guarantees that the
-  // number of tabs closed in the previous tab-close operation is 1.
-  let throwaway_tab = gBrowser.addTab("http://mochi.test:8888/");
-  gBrowser.removeTab(throwaway_tab);
-
-  let undoCloseTabElement = document.getElementById("context_undoCloseTab");
-  updateTabContextMenu();
-  is(undoCloseTabElement.label, undoCloseTabElement.getAttribute("singletablabel"),
-     "The label should be showing that the command will restore a single tab");
-
-  originalTab = gBrowser.selectedTab;
-  gBrowser.selectedBrowser.loadURI("http://mochi.test:8888/");
-
-  maxTabsUndo = Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
-  maxTabsUndoPlusOne = maxTabsUndo + 1;
-  let numberOfTabsLoaded = 0;
-  for (let i = 0; i < maxTabsUndoPlusOne; i++) {
-    let tab = gBrowser.addTab("http://mochi.test:8888/");
-    let browser = gBrowser.getBrowserForTab(tab);
-    browser.addEventListener("load", function onLoad() {
-      browser.removeEventListener("load", onLoad, true);
-
-      if (++numberOfTabsLoaded == maxTabsUndoPlusOne)
-        verifyUndoMultipleClose();
-    }, true);
-  }
-}
-
-function verifyUndoMultipleClose() {
-  info("all tabs opened and loaded");
-  cancelRemoveAllTabsDialogListener = new WindowListener("chrome://global/content/commonDialog.xul", cancelRemoveAllTabsDialog);
-  Services.wm.addListener(cancelRemoveAllTabsDialogListener);
-  gBrowser.removeAllTabsBut(originalTab);
-}
-
-function cancelRemoveAllTabsDialog(domWindow) {
-  ok(true, "dialog appeared in response to multiple tab close action");
-  domWindow.document.documentElement.cancelDialog();
-  Services.wm.removeListener(cancelRemoveAllTabsDialogListener);
-
-  acceptRemoveAllTabsDialogListener = new WindowListener("chrome://global/content/commonDialog.xul", acceptRemoveAllTabsDialog);
-  Services.wm.addListener(acceptRemoveAllTabsDialogListener);
-  waitForCondition(function () gBrowser.tabs.length == 1 + maxTabsUndoPlusOne, function verifyCancel() {
-    is(gBrowser.tabs.length, 1 + maxTabsUndoPlusOne, /* The '1 +' is for the original tab */
-       "All tabs should still be open after the 'Cancel' option on the prompt is chosen");
-    gBrowser.removeAllTabsBut(originalTab);
-  }, "Waited too long to find that no tabs were closed.");
-}
-
-function acceptRemoveAllTabsDialog(domWindow) {
-  ok(true, "dialog appeared in response to multiple tab close action");
-  domWindow.document.documentElement.acceptDialog();
-  Services.wm.removeListener(acceptRemoveAllTabsDialogListener);
-
-  waitForCondition(function () gBrowser.tabs.length == 1, function verifyAccept() {
-    is(gBrowser.tabs.length, 1,
-       "All other tabs should be closed after the 'OK' option on the prompt is chosen");
-    finish();
-  }, "Waited too long for the other tabs to be closed.");
-}
-
-function WindowListener(aURL, aCallback) {
-  this.callback = aCallback;
-  this.url = aURL;
-}
-WindowListener.prototype = {
-  onOpenWindow: function(aXULWindow) {
-    var domWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                              .getInterface(Ci.nsIDOMWindow);
-    var self = this;
-    domWindow.addEventListener("load", function() {
-      domWindow.removeEventListener("load", arguments.callee, false);
-
-      info("domWindow.document.location.href: " + domWindow.document.location.href);
-      if (domWindow.document.location.href != self.url)
-        return;
-
-      // Allow other window load listeners to execute before passing to callback
-      executeSoon(function() {
-        self.callback(domWindow);
-      });
-    }, false);
-  },
-  onCloseWindow: function(aXULWindow) {},
-  onWindowTitleChange: function(aXULWindow, aNewTitle) {}
-}
--- a/browser/base/content/test/general/browser_bug906190.js
+++ b/browser/base/content/test/general/browser_bug906190.js
@@ -207,17 +207,18 @@ function test1C() {
   // mixed content blocker is persistent across tabs.
   var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestWin.gBrowser.selectedBrowser);
   ok(!notification, "OK: Mixed Content Doorhanger did not appear again in Test 1C!");
 
   var actual = gTestWin.content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1C");
 
   // remove tabs
-  gTestWin.gBrowser.removeAllTabsBut(mainTab);
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[2], {animate: false});
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[1], {animate: false});
   gTestWin.gBrowser.selectTabAtIndex(0);
 
   var childTabLink = gHttpTestRoot2 + "file_bug906190_2.html";
   setUpTest("Test2", "linkForTest2", test2, childTabLink);
 }
 
 //------------------------ Test 2 ------------------------------
 
@@ -264,17 +265,18 @@ function test2C() {
   // mixed content blocker should only persist if pages are from the same domain.
   var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestWin.gBrowser.selectedBrowser);
   ok(notification, "OK: Mixed Content Doorhanger did appear again in Test 2C!");
 
   var actual = gTestWin.content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker enabled", "OK: Blocked mixed script in Test 2C");
 
   // remove tabs
-  gTestWin.gBrowser.removeAllTabsBut(mainTab);
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[2], {animate: false});
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[1], {animate: false});
   gTestWin.gBrowser.selectTabAtIndex(0);
 
   // file_bug906190_3_4.html redirects to page test1.example.com/* using meta-refresh
   var childTabLink = gHttpTestRoot1 + "file_bug906190_3_4.html";
   setUpTest("Test3", "linkForTest3", test3, childTabLink);
 }
 
 //------------------------ Test 3 ------------------------------
@@ -331,17 +333,18 @@ function test3E() {
   // The Doorhanger should >> NOT << appear!
   var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestWin.gBrowser.selectedBrowser);
   ok(!notification, "OK: Mixed Content Doorhanger did appear again in Test 3E!");
 
   var actual = gTestWin.content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 3E");
 
   // remove tabs
-  gTestWin.gBrowser.removeAllTabsBut(mainTab);
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[2], {animate: false});
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[1], {animate: false});
   gTestWin.gBrowser.selectTabAtIndex(0);
 
   var childTabLink = gHttpTestRoot1 + "file_bug906190_3_4.html";
   setUpTest("Test4", "linkForTest4", test4, childTabLink);
 }
 
 //------------------------ Test 4 ------------------------------
 
@@ -398,17 +401,18 @@ function test4E() {
   // The Doorhanger >> SHOULD << appear!
   var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestWin.gBrowser.selectedBrowser);
   ok(notification, "OK: Mixed Content Doorhanger did appear again in Test 4E!");
 
   var actual = gTestWin.content.document.getElementById('mctestdiv').innerHTML;
   is(actual, "Mixed Content Blocker enabled", "OK: Blocked mixed script in Test 4E");
 
   // remove tabs
-  gTestWin.gBrowser.removeAllTabsBut(mainTab);
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[2], {animate: false});
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[1], {animate: false});
   gTestWin.gBrowser.selectTabAtIndex(0);
 
   // the sjs files returns a 302 redirect- note, same origins
   var childTabLink = gHttpTestRoot1 + "file_bug906190.sjs";
   setUpTest("Test5", "linkForTest5", test5, childTabLink);
 }
 
 //------------------------ Test 5 ------------------------------
@@ -457,17 +461,18 @@ function test5C() {
   // Currently it >> APPEARS << - see follow up bug 914860
   var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestWin.gBrowser.selectedBrowser);
   todo(!notification, "OK: Mixed Content Doorhanger did not appear again in Test 5C!");
 
   var actual = gTestWin.content.document.getElementById('mctestdiv').innerHTML;
   todo_is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 5C!");
 
   // remove tabs
-  gTestWin.gBrowser.removeAllTabsBut(mainTab);
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[2], {animate: false});
+  gTestWin.gBrowser.removeTab(gTestWin.gBrowser.tabs[1], {animate: false});
   gTestWin.gBrowser.selectTabAtIndex(0);
 
   // the sjs files returns a 302 redirect - note, different origins
   var childTabLink = gHttpTestRoot2 + "file_bug906190.sjs";
   setUpTest("Test6", "linkForTest6", test6, childTabLink);
 }
 
 //------------------------ Test 6 ------------------------------
--- a/browser/components/sessionstore/nsISessionStore.idl
+++ b/browser/components/sessionstore/nsISessionStore.idl
@@ -20,17 +20,17 @@ interface nsIDOMNode;
  * global |window| object to the API, though (or |top| from a sidebar).
  * From elsewhere you can get browser windows through the nsIWindowMediator
  * by looking for "navigator:browser" windows.
  *
  * * "Tabbrowser tabs" are all the child nodes of a browser window's
  * |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
  */
 
-[scriptable, uuid(63a4d9f4-373f-11e3-a237-fa91a24410d2)]
+[scriptable, uuid(0c99811f-6c5f-4a78-9c31-2d266d714175)]
 interface nsISessionStore : nsISupports
 {
   /**
    * Is it possible to restore the previous session. Will always be false when
    * in Private Browsing mode.
    */
   attribute boolean canRestoreLastSession;
 
@@ -96,30 +96,16 @@ interface nsISessionStore : nsISupports
    * @param aTab    is the tabbrowser tab to duplicate (can be from a different window).
    * @param aDelta  is the offset to the history entry to load in the duplicated tab.
    * @returns a reference to the newly created tab.
    */
   nsIDOMNode duplicateTab(in nsIDOMWindow aWindow, in nsIDOMNode aTab,
                           [optional] in long aDelta);
 
   /**
-   * Set the number of tabs that was closed during the last close-tabs
-   * operation. This helps us keep track of batch-close operations so
-   * we can restore multiple tabs at once.
-   */
-  void setNumberOfTabsClosedLast(in nsIDOMWindow aWindow, in unsigned long aNumber);
-
-  /**
-   * Get the number of tabs that was closed during the last close-tabs
-   * operation. This helps us keep track of batch-close operations so
-   * we can restore multiple tabs at once.
-   */
-  unsigned long getNumberOfTabsClosedLast(in nsIDOMWindow aWindow);
-
-  /**
    * Get the number of restore-able tabs for a browser window
    */
   unsigned long getClosedTabCount(in nsIDOMWindow aWindow);
 
   /**
    * Get closed tab data
    *
    * @param aWindow is the browser window for which to get closed tab data
--- a/browser/components/sessionstore/src/RecentlyClosedTabsAndWindowsMenuUtils.jsm
+++ b/browser/components/sessionstore/src/RecentlyClosedTabsAndWindowsMenuUtils.jsm
@@ -61,17 +61,17 @@ this.RecentlyClosedTabsAndWindowsMenuUti
           element.setAttribute("key", "key_undoCloseTab");
         fragment.appendChild(element);
       }
 
       fragment.appendChild(doc.createElementNS(kNSXUL, "menuseparator"));
       let restoreAllTabs = fragment.appendChild(doc.createElementNS(kNSXUL, aTagName));
       restoreAllTabs.setAttribute("label", navigatorBundle.GetStringFromName("menuRestoreAllTabs.label"));
       restoreAllTabs.setAttribute("oncommand",
-              "for (var i = 0; i < " + closedTabs.length + "; i++) undoCloseTab(0);");
+              "for (var i = 0; i < " + closedTabs.length + "; i++) undoCloseTab();");
     }
     return fragment;
   },
 
   /**
   * Builds up a document fragment of UI items for the recently closed windows.
   * @param   aWindow
   *          A window that can be used to create the elements and document fragment.
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -188,24 +188,16 @@ this.SessionStore = {
   setTabState: function ss_setTabState(aTab, aState) {
     SessionStoreInternal.setTabState(aTab, aState);
   },
 
   duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta = 0) {
     return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
   },
 
-  getNumberOfTabsClosedLast: function ss_getNumberOfTabsClosedLast(aWindow) {
-    return SessionStoreInternal.getNumberOfTabsClosedLast(aWindow);
-  },
-
-  setNumberOfTabsClosedLast: function ss_setNumberOfTabsClosedLast(aWindow, aNumber) {
-    return SessionStoreInternal.setNumberOfTabsClosedLast(aWindow, aNumber);
-  },
-
   getClosedTabCount: function ss_getClosedTabCount(aWindow) {
     return SessionStoreInternal.getClosedTabCount(aWindow);
   },
 
   getClosedTabData: function ss_getClosedTabDataAt(aWindow) {
     return SessionStoreInternal.getClosedTabData(aWindow);
   },
 
@@ -1611,45 +1603,16 @@ let SessionStoreInternal = {
       aWindow.gBrowser.addTab();
 
     this.restoreTabs(aWindow, [newTab], [tabState], 0,
                      true /* Load this tab right away. */);
 
     return newTab;
   },
 
-  setNumberOfTabsClosedLast: function ssi_setNumberOfTabsClosedLast(aWindow, aNumber) {
-    if (this._disabledForMultiProcess) {
-      return;
-    }
-
-    if (!("__SSi" in aWindow)) {
-      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    return NumberOfTabsClosedLastPerWindow.set(aWindow, aNumber);
-  },
-
-  /* Used to undo batch tab-close operations. Defaults to 1. */
-  getNumberOfTabsClosedLast: function ssi_getNumberOfTabsClosedLast(aWindow) {
-    if (this._disabledForMultiProcess) {
-      return 0;
-    }
-
-    if (!("__SSi" in aWindow)) {
-      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
-    }
-    // Blank tabs cannot be undo-closed, so the number returned by
-    // the NumberOfTabsClosedLastPerWindow can be greater than the
-    // return value of getClosedTabCount. We won't restore blank
-    // tabs, so we return the minimum of these two values.
-    return Math.min(NumberOfTabsClosedLastPerWindow.get(aWindow) || 1,
-                    this.getClosedTabCount(aWindow));
-  },
-
   getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
     if ("__SSi" in aWindow) {
       return this._windows[aWindow.__SSi]._closedTabs.length;
     }
 
     if (!DyingWindowCache.has(aWindow)) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
@@ -4024,21 +3987,16 @@ let DirtyWindows = {
     this._data.delete(window);
   },
 
   clear: function (window) {
     this._data.clear();
   }
 };
 
-// A map storing the number of tabs last closed per windoow. This only
-// stores the most recent tab-close operation, and is used to undo
-// batch tab-closing operations.
-let NumberOfTabsClosedLastPerWindow = new WeakMap();
-
 // This is used to help meter the number of restoring tabs. This is the control
 // point for telling the next tab to restore. It gets attached to each gBrowser
 // via gBrowser.addTabsProgressListener
 let gRestoreTabsProgressListener = {
   onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
     // Ignore state changes on browsers that we've already restored and state
     // changes that aren't applicable.
     if (aBrowser.__SS_restoreState &&
--- a/browser/components/sessionstore/test/browser_345898.js
+++ b/browser/components/sessionstore/test/browser_345898.js
@@ -35,13 +35,9 @@ function test() {
   ok(test(function() ss.undoCloseTab({}, 0)),
      "Invalid window for undoCloseTab throws");
   ok(test(function() ss.undoCloseTab(window, -1)),
      "Invalid index for undoCloseTab throws");
   ok(test(function() ss.getWindowValue({}, "")),
      "Invalid window for getWindowValue throws");
   ok(test(function() ss.setWindowValue({}, "", "")),
      "Invalid window for setWindowValue throws");
-  ok(test(function() ss.getNumberOfTabsClosedLast({})),
-     "Invalid window for getNumberOfTabsClosedLast  throws");
-  ok(test(function() ss.setNumberOfTabsClosedLast({}, 1)),
-     "Invalid window for setNumberOfTabsClosedLast throws");
 }
--- a/browser/components/tabview/test/browser_tabview_bug608037.js
+++ b/browser/components/tabview/test/browser_tabview_bug608037.js
@@ -33,10 +33,10 @@ function onTabViewWindowLoaded() {
     is(groupItems[0].getChildren().length, 3, "The group still has three tab items");
 
     // clean up and finish
     hideTabView(function () {
       gBrowser.removeTab(tabOne);
       gBrowser.removeTab(tabTwo);
       finish();
     });
-  }, 0);
+  });
 }
--- a/browser/components/tabview/test/browser_tabview_bug624847.js
+++ b/browser/components/tabview/test/browser_tabview_bug624847.js
@@ -59,17 +59,17 @@ function test() {
 
       restoreTab(function () {
         prefix = 'unpinned-restored';
         assertValidPrerequisites();
         assertGroupItemPreserved();
 
         createBlankTab();
         afterAllTabsLoaded(testUndoCloseWithSelectedBlankPinnedTab);
-      }, 0);
+      });
     });
   }
 
   let testUndoCloseWithSelectedBlankPinnedTab = function () {
     prefix = 'pinned';
     assertNumberOfTabs(2);
 
     afterAllTabsLoaded(function () {
@@ -89,17 +89,17 @@ function test() {
         prefix = 'pinned-restored';
         assertValidPrerequisites();
         assertGroupItemPreserved();
 
         createBlankTab();
         gBrowser.removeTab(gBrowser.tabs[0]);
 
         afterAllTabsLoaded(finishTest);
-      }, 0);
+      });
     });
   }
 
   waitForExplicitFinish();
   registerCleanupFunction(function () TabView.hide());
 
   showTabView(function () {
     hideTabView(function () {
--- a/browser/components/tabview/test/browser_tabview_bug628270.js
+++ b/browser/components/tabview/test/browser_tabview_bug628270.js
@@ -76,17 +76,17 @@ function test() {
 
     restoreTab(function () {
       assertNumberOfTabsInGroup(groupItem, 2);
 
       activateFirstGroupItem();
       gBrowser.removeTab(gBrowser.tabs[1]);
       gBrowser.removeTab(gBrowser.tabs[1]);
       hideTabView(finishTest);
-    }, 0);
+    });
   }
 
   waitForExplicitFinish();
   assertTabViewIsHidden();
   registerCleanupFunction(function () TabView.hide());
 
   showTabView(function () {
     cw = TabView.getContentWindow();
--- a/browser/components/tabview/test/browser_tabview_bug706736.js
+++ b/browser/components/tabview/test/browser_tabview_bug706736.js
@@ -15,17 +15,17 @@ function test() {
     is(groupItemOne.getChildren().length, 1, "Group one has 1 tab item");
 
     let groupItemTwo = createGroupItemWithBlankTabs(win, 300, 300, 40, 1);
     is(groupItemTwo.getChildren().length, 1, "Group two has 1 tab items");
 
     whenTabViewIsHidden(function() {
       win.gBrowser.removeTab(win.gBrowser.selectedTab);
       executeSoon(function() {
-        win.undoCloseTab(0);
+        win.undoCloseTab();
 
         groupItemTwo.addSubscriber("childAdded", function onChildAdded(data) {
           groupItemTwo.removeSubscriber("childAdded", onChildAdded);
 
           is(groupItemOne.getChildren().length, 1, "Group one still has 1 tab item");
           is(groupItemTwo.getChildren().length, 1, "Group two still has 1 tab item");
         });
 
--- a/browser/components/tabview/test/head.js
+++ b/browser/components/tabview/test/head.js
@@ -357,17 +357,17 @@ function newWindowWithState(state, callb
     });
   });
 }
 
 // ----------
 function restoreTab(callback, index, win) {
   win = win || window;
 
-  let tab = win.undoCloseTab(index);
+  let tab = win.undoCloseTab(index || 0);
   let tabItem = tab._tabViewTabItem;
 
   let finalize = function () {
     afterAllTabsLoaded(function () callback(tab), win);
   };
 
   if (tabItem._reconnected) {
     finalize();
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -1352,17 +1352,21 @@ VariableBubbleView.prototype = {
     // Using the script offset, determine the actual line and column inside the
     // script, to use when finding identifiers.
     let scriptStart = editor.getPosition(scriptInfo.start);
     let scriptLineOffset = scriptStart.line;
     let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
 
     let scriptLine = hoveredLine - scriptLineOffset;
     let scriptColumn = hoveredColumn - scriptColumnOffset;
-    let identifierInfo = parsedSource.getIdentifierAt(scriptLine + 1, scriptColumn);
+    let identifierInfo = parsedSource.getIdentifierAt({
+      line: scriptLine + 1,
+      column: scriptColumn,
+      scriptIndex: scriptInfo.index
+    });
 
     // If the info is null, we're not hovering any identifier.
     if (!identifierInfo) {
       return;
     }
 
     // Transform the line and column relative to the parsed script back
     // to the context of the parent source.
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -47,16 +47,17 @@ support-files =
   doc_minified_bogus_map.html
   doc_pause-exceptions.html
   doc_pretty-print.html
   doc_pretty-print-2.html
   doc_random-javascript.html
   doc_recursion-stack.html
   doc_scope-variable.html
   doc_scope-variable-2.html
+  doc_scope-variable-3.html
   doc_script-switching-01.html
   doc_script-switching-02.html
   doc_step-out.html
   doc_watch-expressions.html
   doc_with-frame.html
   head.js
   sjs_random-javascript.sjs
   testactors.js
@@ -217,16 +218,17 @@ support-files =
 [browser_dbg_variables-view-popup-01.js]
 [browser_dbg_variables-view-popup-02.js]
 [browser_dbg_variables-view-popup-03.js]
 [browser_dbg_variables-view-popup-04.js]
 [browser_dbg_variables-view-popup-05.js]
 [browser_dbg_variables-view-popup-06.js]
 [browser_dbg_variables-view-popup-07.js]
 [browser_dbg_variables-view-popup-08.js]
+[browser_dbg_variables-view-popup-09.js]
 [browser_dbg_variables-view-reexpand-01.js]
 [browser_dbg_variables-view-reexpand-02.js]
 [browser_dbg_variables-view-webidl.js]
 [browser_dbg_watch-expressions-01.js]
 [browser_dbg_watch-expressions-02.js]
 [browser_dbg_chrome-create.js]
 skip-if = os == "linux" # Bug 847558
 [browser_dbg_on-pause-raise.js]
--- a/browser/devtools/debugger/test/browser_dbg_parser-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_parser-03.js
@@ -28,50 +28,50 @@ function test() {
   ok(parsed,
     "HTML code should be parsed correctly.");
   is(parser.errors.length, 0,
     "There should be no errors logged when parsing.");
 
   is(parsed.scriptCount, 3,
     "There should be 3 scripts parsed in the parent HTML source.");
 
-  is(parsed.getScriptInfo(0).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(0).toSource(), "({start:-1, length:-1, index:-1})",
     "There is no script at the beginning of the parent source.");
-  is(parsed.getScriptInfo(source.length - 1).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.length - 1).toSource(), "({start:-1, length:-1, index:-1})",
     "There is no script at the end of the parent source.");
 
-  is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:31, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:31, length:13, index:0})",
     "The first script was located correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13, index:1})",
     "The second script was located correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:151, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:151, length:13, index:2})",
     "The third script was located correctly.");
 
-  is(parsed.getScriptInfo(source.indexOf("let a") - 1).toSource(), "({start:31, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let a") - 1).toSource(), "({start:31, length:13, index:0})",
     "The left edge of the first script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let b") - 1).toSource(), "({start:85, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let b") - 1).toSource(), "({start:85, length:13, index:1})",
     "The left edge of the second script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let c") - 1).toSource(), "({start:151, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let c") - 1).toSource(), "({start:151, length:13, index:2})",
     "The left edge of the third script was interpreted correctly.");
 
-  is(parsed.getScriptInfo(source.indexOf("let a") - 2).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let a") - 2).toSource(), "({start:-1, length:-1, index:-1})",
     "The left outside of the first script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let b") - 2).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let b") - 2).toSource(), "({start:-1, length:-1, index:-1})",
     "The left outside of the second script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let c") - 2).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let c") - 2).toSource(), "({start:-1, length:-1, index:-1})",
     "The left outside of the third script was interpreted correctly.");
 
-  is(parsed.getScriptInfo(source.indexOf("let a") + 12).toSource(), "({start:31, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let a") + 12).toSource(), "({start:31, length:13, index:0})",
     "The right edge of the first script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let b") + 12).toSource(), "({start:85, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let b") + 12).toSource(), "({start:85, length:13, index:1})",
     "The right edge of the second script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let c") + 12).toSource(), "({start:151, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let c") + 12).toSource(), "({start:151, length:13, index:2})",
     "The right edge of the third script was interpreted correctly.");
 
-  is(parsed.getScriptInfo(source.indexOf("let a") + 13).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let a") + 13).toSource(), "({start:-1, length:-1, index:-1})",
     "The right outside of the first script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let b") + 13).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let b") + 13).toSource(), "({start:-1, length:-1, index:-1})",
     "The right outside of the second script was interpreted correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let c") + 13).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let c") + 13).toSource(), "({start:-1, length:-1, index:-1})",
     "The right outside of the third script was interpreted correctly.");
 
   finish();
 }
--- a/browser/devtools/debugger/test/browser_dbg_parser-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_parser-04.js
@@ -38,17 +38,17 @@ function test() {
   is(parser.errors[1].name, "SyntaxError",
     "The correct second exception was caught.");
   is(parser.errors[1].message, "missing ; before statement",
     "The correct second exception was caught.");
 
   is(parsed.scriptCount, 1,
     "There should be 1 script parsed in the parent HTML source.");
 
-  is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:-1, length:-1, index:-1})",
     "The first script shouldn't be considered valid.");
-  is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13})",
+  is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13, index:0})",
     "The second script was located correctly.");
-  is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:-1, length:-1})",
+  is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:-1, length:-1, index:-1})",
     "The third script shouldn't be considered valid.");
 
   finish();
 }
--- a/browser/devtools/debugger/test/browser_dbg_parser-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_parser-05.js
@@ -27,17 +27,17 @@ function test() {
   ok(parsed,
     "The javascript code should be parsed correctly.");
   is(parser.errors.length, 0,
     "There should be no errors logged when parsing.");
 
   is(parsed.scriptCount, 1,
     "There should be 1 script parsed in the parent source.");
 
-  is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:0, length:261})",
+  is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:0, length:261, index:0})",
     "The script location is correct (1).");
-  is(parsed.getScriptInfo(source.indexOf("<script>")).toSource(), "({start:0, length:261})",
+  is(parsed.getScriptInfo(source.indexOf("<script>")).toSource(), "({start:0, length:261, index:0})",
     "The script location is correct (2).");
-  is(parsed.getScriptInfo(source.indexOf("</script>")).toSource(), "({start:0, length:261})",
+  is(parsed.getScriptInfo(source.indexOf("</script>")).toSource(), "({start:0, length:261, index:0})",
     "The script location is correct (3).");
 
   finish();
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-popup-09.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening inspecting variables works across scopes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_scope-variable-3.html";
+
+function test() {
+  Task.spawn(function() {
+    let [tab, debuggee, panel] = yield initDebugger(TAB_URL);
+    let win = panel.panelWin;
+    let bubble = win.DebuggerView.VariableBubble;
+    let tooltip = bubble._tooltip.panel;
+
+    // Allow this generator function to yield first.
+    executeSoon(() => debuggee.test());
+    yield waitForSourceAndCaretAndScopes(panel, ".html", 15);
+
+    yield openVarPopup(panel, { line: 12, ch: 10 });
+    ok(true, "The variable inspection popup was shown for the real variable.");
+
+    once(tooltip, "popupshown").then(() => {
+      ok(false, "The variable inspection popup shouldn't have been opened.");
+    });
+
+    reopenVarPopup(panel, { line: 18, ch: 10 });
+    yield waitForTime(1000);
+
+    yield resumeDebuggerThenCloseAndFinish(panel);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_scope-variable-3.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger test page</title>
+  </head>
+
+  <body>
+    <script type="text/javascript">
+      var trap = "first script";
+      function test() {
+        debugger;
+      }
+    </script>
+    <script type="text/javascript">/*
+          trololol
+  */</script>
+  </body>
+
+</html>
--- a/browser/devtools/shared/Parser.jsm
+++ b/browser/devtools/shared/Parser.jsm
@@ -127,25 +127,25 @@ function SyntaxTreesPool(aSyntaxTrees) {
   this._trees = aSyntaxTrees;
   this._cache = new Map();
 }
 
 SyntaxTreesPool.prototype = {
   /**
    * @see SyntaxTree.prototype.getIdentifierAt
    */
-  getIdentifierAt: function(aLine, aColumn) {
-    return this._first(this._call("getIdentifierAt", aLine, aColumn));
+  getIdentifierAt: function({ line, column, scriptIndex }) {
+    return this._first(this._call("getIdentifierAt", scriptIndex, line, column));
   },
 
   /**
    * @see SyntaxTree.prototype.getNamedFunctionDefinitions
    */
   getNamedFunctionDefinitions: function(aSubstring) {
-    return this._call("getNamedFunctionDefinitions", aSubstring);
+    return this._call("getNamedFunctionDefinitions", -1, aSubstring);
   },
 
   /**
    * Gets the total number of scripts in the parent source.
    * @return number
    */
   get scriptCount() {
     return this._trees.length;
@@ -156,54 +156,69 @@ SyntaxTreesPool.prototype = {
    * relative to its parent source.
    *
    * @param number aOffset
    *        The offset relative to the parent source.
    * @return object
    *         The offset and length relative to the enclosing script.
    */
   getScriptInfo: function(aOffset) {
+    let info = { start: -1, length: -1, index: -1 };
+
     for (let { offset, length } of this._trees) {
-      if (offset <= aOffset &&  offset + length >= aOffset) {
-        return { start: offset, length: length };
+      info.index++;
+      if (offset <= aOffset && offset + length >= aOffset) {
+        info.start = offset;
+        info.length = length;
+        return info;
       }
     }
-    return { start: -1, length: -1 };
+
+    info.index = -1;
+    return info;
   },
 
   /**
    * Gets the first script results from a source results set.
    * If no results are found, null is returned.
    *
    * @return array
    *         A collection of parse results for the first script in a source.
    */
   _first: function(aSourceResults) {
     let scriptResult = aSourceResults.filter(e => !!e.parseResults)[0];
     return scriptResult ? scriptResult.parseResults : null;
   },
 
   /**
-   * Handles a request for all known syntax trees.
+   * Handles a request for a specific or all known syntax trees.
    *
    * @param string aFunction
    *        The function name to call on the SyntaxTree instances.
+   * @param number aSyntaxTreeIndex
+   *        The syntax tree for which to handle the request. If the tree at
+   *        the specified index isn't found, the accumulated results for all
+   *        syntax trees are returned.
    * @param any aParams
    *        Any kind params to pass to the request function.
    * @return array
    *         The results given by all known syntax trees.
    */
-  _call: function(aFunction, ...aParams) {
+  _call: function(aFunction, aSyntaxTreeIndex, ...aParams) {
     let results = [];
-    let requestId = aFunction + aParams.toSource(); // Cache all the things!
+    let requestId = [aFunction, aSyntaxTreeIndex, aParams].toSource();
 
     if (this._cache.has(requestId)) {
       return this._cache.get(requestId);
     }
-    for (let syntaxTree of this._trees) {
+
+    let requestedTree = this._trees[aSyntaxTreeIndex];
+    let targettedTrees = requestedTree ? [requestedTree] : this._trees;
+
+    for (let syntaxTree of targettedTrees) {
       try {
         results.push({
           sourceUrl: syntaxTree.url,
           scriptLength: syntaxTree.length,
           scriptOffset: syntaxTree.offset,
           parseResults: syntaxTree[aFunction].apply(syntaxTree, aParams)
         });
       } catch (e) {
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -44,21 +44,16 @@ can reach it easily. -->
 <!ENTITY  moveToGroup.accesskey              "M">
 <!ENTITY  moveToNewGroup.label               "New Group">
 <!ENTITY  moveToNewWindow.label              "Move to New Window">
 <!ENTITY  moveToNewWindow.accesskey          "W">
 <!ENTITY  bookmarkAllTabs.label              "Bookmark All Tabs…">
 <!ENTITY  bookmarkAllTabs.accesskey          "T">
 <!ENTITY  undoCloseTab.label                 "Undo Close Tab">
 <!ENTITY  undoCloseTab.accesskey             "U">
-<!-- LOCALIZATION NOTE (undoCloseTabs.label) : This label is used
-when the previous tab-closing operation closed more than one tab. It
-replaces the undoCloseTab.label and will use the same accesskey as the
-undoCloseTab.label so users will not need to learn new keyboard controls. -->
-<!ENTITY  undoCloseTabs.label                "Undo Close Tabs">
 <!ENTITY  closeTab.label                     "Close Tab">
 <!ENTITY  closeTab.accesskey                 "c">
 
 <!ENTITY  listAllTabs.label      "List all tabs">
 
 <!ENTITY tabCmd.label "New Tab">
 <!ENTITY tabCmd.accesskey "T">
 <!ENTITY tabCmd.commandkey "t">
--- a/browser/metro/base/content/ContextCommands.js
+++ b/browser/metro/base/content/ContextCommands.js
@@ -283,17 +283,17 @@ var ContextCommands = {
       return "view-source:" + Browser.selectedBrowser.currentURI.spec;
     }
     return null;
   },
 
   viewPageSource: function cc_viewPageSource() {
     let uri = this.getPageSource();
     if (uri) {
-      BrowserUI.addAndShowTab(uri);
+      BrowserUI.addAndShowTab(uri, Browser.selectedTab);
     }
   },
 
   /*
    * Utilities
    */
 
   saveToWinLibrary: function cc_saveToWinLibrary(aType) {
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_apzc_basic.js
@@ -0,0 +1,117 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+  if (!isLandscapeMode()) {
+    todo(false, "browser_snapped_tests need landscape mode to run.");
+    return;
+  }
+
+  runTests();
+}
+
+let kTransformTimeout = 5000;
+
+let gEdit = null;
+let tabAdded = false;
+
+function setUp() {
+  if (!tabAdded) {
+    yield addTab(chromeRoot + "res/textdivs01.html");
+    tabAdded = true;
+  }
+  yield hideContextUI();
+}
+
+/*
+gTests.push({
+  desc: "soft keyboard reliability",
+  setUp: setUp,
+  run: function() {
+    yield waitForMs(3000);
+
+    let edit = Browser.selectedBrowser.contentDocument.getElementById("textinput");
+    // show the soft keyboard
+    let keyboardPromise = waitForObserver("metro_softkeyboard_shown", 20000);
+    sendNativeTap(edit);
+    yield waitForMs(5000);
+    sendNativeTap(edit);
+    yield keyboardPromise;
+    yield waitForMs(5000);
+
+    // hide the soft keyboard / navbar
+    keyboardPromise = waitForObserver("metro_softkeyboard_hidden", 20000);
+    sendNativeTap(Browser.selectedBrowser.contentDocument.getElementById("first"));
+    yield keyboardPromise;
+    yield waitForMs(5000);
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
+*/
+
+gTests.push({
+  desc: "native long tap works",
+  setUp: setUp,
+  run: function() {
+    let edit = Browser.selectedBrowser.contentDocument.getElementById("textinput");
+    let promise = waitForEvent(document, "popupshown");
+    sendNativeLongTap(edit);
+    yield promise;
+    ContextMenuUI.hide();
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
+
+gTests.push({
+  desc: "double tap transforms",
+  setUp: setUp,
+  run: function() {
+    let beginPromise = waitForObserver("apzc-transform-begin", kTransformTimeout);
+    let endPromise = waitForObserver("apzc-transform-end", kTransformTimeout);
+
+    sendNativeDoubleTap(Browser.selectedBrowser.contentDocument.getElementById("second"));
+
+    yield beginPromise;
+    yield endPromise;
+
+    beginPromise = waitForObserver("apzc-transform-begin", kTransformTimeout);
+    endPromise = waitForObserver("apzc-transform-end", kTransformTimeout);
+
+    sendNativeDoubleTap(Browser.selectedBrowser.contentDocument.getElementById("second"));
+
+    yield beginPromise;
+    yield endPromise;
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
+
+gTests.push({
+  desc: "scroll transforms",
+  setUp: setUp,
+  run: function() {
+    let beginPromise = waitForObserver("apzc-transform-begin", kTransformTimeout);
+    let endPromise = waitForObserver("apzc-transform-end", kTransformTimeout);
+
+    var touchdrag = new TouchDragAndHold();
+    touchdrag.useNativeEvents = true;
+    touchdrag.nativePointerId = 1;
+    yield touchdrag.start(Browser.selectedTab.browser.contentWindow,
+                          10, 100, 10, 10);
+    touchdrag.end();
+
+    yield beginPromise;
+    yield endPromise;
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_menu_hoverstate.js
@@ -0,0 +1,147 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+  if (!isLandscapeMode()) {
+    todo(false, "browser_snapped_tests need landscape mode to run.");
+    return;
+  }
+
+  runTests();
+}
+let tabAdded = false;
+
+function setUp() {
+  if (!tabAdded) {
+    yield addTab(chromeRoot + "res/textdivs01.html");
+    tabAdded = true;
+  }
+  yield hideContextUI();
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDOMUtils",
+  "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
+
+const kActiveState = 0x00000001;
+const kHoverState = 0x00000004;
+
+gTests.push({
+  desc: "hover states of menus",
+  setUp: setUp,
+  run: function() {
+    // Clicking on menu items should not leave the clicked menu item
+    // in the :active or :hover state.
+
+    let typesArray = [
+      "copy",
+      "paste"
+    ];
+
+    let promise = waitForEvent(document, "popupshown");
+    ContextMenuUI.showContextMenu({
+      target: null,
+      json: {
+        types: typesArray,
+        string: '',
+        xPos: 1,
+        yPos: 1,
+        leftAligned: true,
+        bottomAligned: true
+    }});
+    yield promise;
+
+    // should be visible
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
+
+    let menuItem = document.getElementById("context-copy");
+    promise = waitForEvent(document, "popuphidden");
+    sendNativeTap(menuItem);
+    yield promise;
+
+    for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+      let item = ContextMenuUI.commands.childNodes[idx];
+      let state = gDOMUtils.getContentState(item);
+      if ((state & kHoverState) || (state & kActiveState)) {
+        ok(false, "found invalid state on context menu item (" + state.toString(2) + ")");
+      }
+    }
+
+    // Do it again, but this time check the visible menu too and
+    // click a different menu item.
+    promise = waitForEvent(document, "popupshown");
+    ContextMenuUI.showContextMenu({
+      target: null,
+      json: {
+        types: typesArray,
+        string: '',
+        xPos: 1,
+        yPos: 1,
+        leftAligned: true,
+        bottomAligned: true
+    }});
+    yield promise;
+
+    // should be visible
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
+
+    for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+      let item = ContextMenuUI.commands.childNodes[idx];
+      let state = gDOMUtils.getContentState(item);
+      if ((state & kHoverState) || (state & kActiveState)) {
+        ok(false, "found invalid state on context menu item (" + state.toString(2) + ")");
+      }
+    }
+
+    menuItem = document.getElementById("context-paste");
+    promise = waitForEvent(document, "popuphidden");
+    sendNativeTap(menuItem);
+    yield promise;
+
+    for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+      let item = ContextMenuUI.commands.childNodes[idx];
+      let state = gDOMUtils.getContentState(item);
+      if ((state & kHoverState) || (state & kActiveState)) {
+        ok(false, "found invalid state on context menu item (" + state.toString(2) + ")");
+      }
+    }
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
+
+gTests.push({
+  desc: "hover states of nav bar buttons",
+  setUp: setUp,
+  run: function() {
+    // show nav bar
+    yield showNavBar();
+
+    // tap bookmark button
+    sendNativeTap(Appbar.starButton);
+    yield waitForMs(100);
+    
+    // check hover state
+    let state = gDOMUtils.getContentState(Appbar.starButton);
+    if ((state & kHoverState) || (state & kActiveState)) {
+      ok(false, "found invalid state on star button (" + state.toString(2) + ")");
+    }
+
+    // tap bookmark button
+    sendNativeTap(Appbar.starButton);
+    yield waitForMs(100);
+    
+    // check hover state
+    let state = gDOMUtils.getContentState(Appbar.starButton);
+    if ((state & kHoverState) || (state & kActiveState)) {
+      ok(false, "found invalid state on star button (" + state.toString(2) + ")");
+    }
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
+
--- a/browser/metro/base/tests/mochitest/head.js
+++ b/browser/metro/base/tests/mochitest/head.js
@@ -552,18 +552,21 @@ function waitForObserver(aObsEvent, aTim
   return deferred.promise;
 
   } catch (ex) {
     info(ex.message);
   }
 }
 
 /*=============================================================================
-  Native input synthesis helpers
-=============================================================================*/
+ * Native input helpers - these helpers send input directly to the os
+ * generating os level input events that get processed by widget and
+ * apzc logic.
+ *===========================================================================*/
+
 // Keyboard layouts for use with synthesizeNativeKey
 const usEnglish = 0x409;
 const arSpanish = 0x2C0A;
 
 // Modifiers for use with synthesizeNativeKey
 const leftShift = 0x100;
 const rightShift = 0x200;
 const leftControl = 0x400;
@@ -635,16 +638,46 @@ function synthesizeNativeMouseMDown(aEle
 
 function synthesizeNativeMouseMUp(aElement, aOffsetX, aOffsetY) {
   synthesizeNativeMouse(aElement,
                         aOffsetX,
                         aOffsetY,
                         0x0040);  // MOUSEEVENTF_MIDDLEUP
 }
 
+// WARNING: these calls can trigger the soft keyboard on tablets, but not
+// on test slaves (bug 947428).
+// WARNING: When testing the apzc, be careful of bug 933990. Events sent
+// shortly after loading a page may get ignored.
+
+function sendNativeLongTap(aElement, aX, aY) {
+  let coords = logicalCoordsForElement(aElement, aX, aY);
+  Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, true);
+}
+
+function sendNativeTap(aElement, aX, aY) {
+  let coords = logicalCoordsForElement(aElement, aX, aY);
+  Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
+}
+
+function sendNativeDoubleTap(aElement, aX, aY) {
+  let coords = logicalCoordsForElement(aElement, aX, aY);
+  Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
+  Browser.windowUtils.sendNativeTouchTap(coords.x, coords.y, false);
+}
+
+function clearNativeTouchSequence() {
+  Browser.windowUtils.clearNativeTouchSequence();
+}
+
+/*=============================================================================
+ * Synthesized event helpers - these helpers synthesize input events that get
+ * dispatched directly to the dom. As such widget and apzc logic is bypassed.
+ *===========================================================================*/
+
 /*
  * logicalCoordsForElement - given coordinates relative to top-left of
  * given element, returns logical coordinates for window. If a non-numeric
  * X or Y value is given, a value for the center of the element in that
  * dimension is used.
  *
  * @param aElement element coordinates are relative to.
  * @param aX, aY relative coordinates.
@@ -779,53 +812,85 @@ function sendTouchDrag(aWindow, aStartX,
 function TouchDragAndHold() {
 }
 
 TouchDragAndHold.prototype = {
   _timeoutStep: 2,
   _numSteps: 50,
   _debug: false,
   _win: null,
+  _native: false,
+  _pointerId: 1,
+  _dui: Components.interfaces.nsIDOMWindowUtils,
+
+  set useNativeEvents(aValue) {
+    this._native = aValue;
+  },
+
+  set nativePointerId(aValue) {
+    this._pointerId = aValue;
+  },
 
   callback: function callback() {
     if (this._win == null)
       return;
 
     if (this._debug) {
       SelectionHelperUI.debugDisplayDebugPoint(this._currentPoint.xPos,
         this._currentPoint.yPos, 5, "#FF0000", true);
     }
 
     if (++this._step.steps >= this._numSteps) {
-      EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
-                                        { type: "touchmove" }, this._win);
+      if (this._native) {
+        this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
+                                         this._endPoint.xPos, this._endPoint.yPos,
+                                         1, 90);
+      } else {
+        EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
+                                          { type: "touchmove" }, this._win);
+      }
       this._defer.resolve();
       return;
     }
     this._currentPoint.xPos += this._step.x;
     this._currentPoint.yPos += this._step.y;
     if (this._debug) {
       info("[" + this._step.steps + "] touchmove " + this._currentPoint.xPos + " x " + this._currentPoint.yPos);
     }
-    EventUtils.synthesizeTouchAtPoint(this._currentPoint.xPos, this._currentPoint.yPos,
-                                      { type: "touchmove" }, this._win);
+
+    if (this._native) {
+      this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
+                                       this._currentPoint.xPos, this._currentPoint.yPos,
+                                       1, 90);
+    } else {
+      EventUtils.synthesizeTouchAtPoint(this._currentPoint.xPos, this._currentPoint.yPos,
+                                        { type: "touchmove" }, this._win);
+    }
+
     let self = this;
     setTimeout(function () { self.callback(); }, this._timeoutStep);
   },
 
   start: function start(aWindow, aStartX, aStartY, aEndX, aEndY) {
     this._defer = Promise.defer();
     this._win = aWindow;
+    this._utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIDOMWindowUtils);
     this._endPoint = { xPos: aEndX, yPos: aEndY };
     this._currentPoint = { xPos: aStartX, yPos: aStartY };
     this._step = { steps: 0, x: (aEndX - aStartX) / this._numSteps, y: (aEndY - aStartY) / this._numSteps };
     if (this._debug) {
       info("[0] touchstart " + aStartX + " x " + aStartY);
     }
-    EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
+    if (this._native) {
+      this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_CONTACT,
+                                       aStartX, aStartY, 1, 90);
+    } else {
+      EventUtils.synthesizeTouchAtPoint(aStartX, aStartY, { type: "touchstart" }, aWindow);
+    }
     let self = this;
     setTimeout(function () { self.callback(); }, this._timeoutStep);
     return this._defer.promise;
   },
 
   move: function move(aEndX, aEndY) {
     if (this._win == null)
       return;
@@ -842,18 +907,24 @@ TouchDragAndHold.prototype = {
     return this._defer.promise;
   },
 
   end: function start() {
     if (this._debug) {
       info("[" + this._step.steps + "] touchend " + this._endPoint.xPos + " x " + this._endPoint.yPos);
       SelectionHelperUI.debugClearDebugPoints();
     }
-    EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
-                                      { type: "touchend" }, this._win);
+    if (this._native) {
+      this._utils.sendNativeTouchPoint(this._pointerId, this._dui.TOUCH_REMOVE,
+                                       this._endPoint.xPos, this._endPoint.yPos,
+                                       1, 90);
+    } else {
+      EventUtils.synthesizeTouchAtPoint(this._endPoint.xPos, this._endPoint.yPos,
+                                        { type: "touchend" }, this._win);
+    }
     this._win = null;
   },
 };
 
 /*=============================================================================
   System utilities
 =============================================================================*/
 
--- a/browser/metro/base/tests/mochitest/metro.ini
+++ b/browser/metro/base/tests/mochitest/metro.ini
@@ -19,16 +19,17 @@ support-files =
   browser_selection_textarea.html
   browser_tilegrid.xul
   head.js
   helpers/BookmarksHelper.js
   helpers/HistoryHelper.js
   helpers/ViewStateHelper.js
   res/image01.png
   res/textblock01.html
+  res/textdivs01.html
   res/textinput01.html
   res/textarea01.html
   res/testEngine.xml
   res/blankpage1.html
   res/blankpage2.html
   res/blankpage3.html
 
 [browser_bookmarks.js]
@@ -50,16 +51,18 @@ support-files =
 [browser_snappedState.js]
 [browser_tabs.js]
 [browser_test.js]
 [browser_tiles.js]
 [browser_topsites.js]
 [browser_urlbar.js]
 [browser_urlbar_highlightURLs.js]
 [browser_urlbar_trimURLs.js]
+[browser_apzc_basic.js]
+[browser_menu_hoverstate.js]
 
 # These tests have known failures in debug builds
 [browser_selection_basic.js]
 skip-if = debug
 [browser_selection_textarea.js]
 skip-if = debug
 [browser_selection_frame_content.js]
 skip-if = debug
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/res/textdivs01.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8" />
+<style>
+div
+{
+  padding: 25px;
+  width: 300px;
+  display: block;
+}
+</style>
+</head>
+<body>
+<br />
+<input id="textinput" style="width:100px; height:25px;" value="The rabbit-hole went straight on like a tunnel for some way and then dipped suddenly down" type="text">
+<div id="first">
+    Alice was beginning to get very tired of sitting by her sister on the bank, and of having
+    nothing to do: once or twice she had peeped into the book her sister was reading 
+    but it had no pictures or conversations in it, `and what is the use of a book,' thought
+    Alice `without pictures or conversation?'
+</div>
+<div id="second">
+    Alice was beginning to get very tired of sitting by her sister on the bank, and of having
+    nothing to do: once or twice she had peeped into the book her sister was reading, but it
+    had no pictures or conversations in it, `and what is the use of a book,' thought Alice
+    `without pictures or conversation?'
+</div>
+<div id="third">
+    So she was considering in her own mind (as well as she could, for the hot day made her
+    feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth
+    the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink
+    eyes ran close by her.
+</div>
+<div style="height:1000px;"></div>
+</body></html>
\ No newline at end of file
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1145,16 +1145,73 @@ nsDOMWindowUtils::SendNativeMouseScrollE
                                                              aScreenY),
                                                   aNativeMessage,
                                                   aDeltaX, aDeltaY, aDeltaZ,
                                                   aModifierFlags,
                                                   aAdditionalFlags);
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeTouchPoint(uint32_t aPointerId,
+                                       uint32_t aTouchState,
+                                       int32_t aScreenX,
+                                       int32_t aScreenY,
+                                       double aPressure,
+                                       uint32_t aOrientation)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (aPressure < 0 || aPressure > 1 || aOrientation > 359) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return widget->SynthesizeNativeTouchPoint(aPointerId,
+                                            (nsIWidget::TouchPointerState)aTouchState,
+                                            nsIntPoint(aScreenX, aScreenY),
+                                            aPressure, aOrientation);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::SendNativeTouchTap(int32_t aScreenX,
+                                     int32_t aScreenY,
+                                     bool aLongTap)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget) {
+    return NS_ERROR_FAILURE;
+  }
+  return widget->SynthesizeNativeTouchTap(nsIntPoint(aScreenX, aScreenY), aLongTap);
+}
+
+NS_IMETHODIMP
+nsDOMWindowUtils::ClearNativeTouchSequence()
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (!widget) {
+    return NS_ERROR_FAILURE;
+  }
+  return widget->ClearNativeTouchSequence();
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -38,17 +38,17 @@ interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsICompositionStringSynthesizer;
 
-[scriptable, uuid(3d9bf70c-5b83-11e3-9090-3c970e9f4238)]
+[scriptable, uuid(38740b7e-095e-4198-a012-cf5f9e102a6a)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -438,16 +438,38 @@ interface nsIDOMWindowUtils : nsISupport
    */
   void sendNativeMouseEvent(in long aScreenX,
                             in long aScreenY,
                             in long aNativeMessage,
                             in long aModifierFlags,
                             in nsIDOMElement aElement);
 
   /**
+   * The values for sendNativeMouseScrollEvent's aAdditionalFlags.
+   */
+
+  /**
+   * If MOUSESCROLL_PREFER_WIDGET_AT_POINT is set, widget will dispatch
+   * the event to a widget which is under the cursor.  Otherwise, dispatch to
+   * a default target on the platform.  E.g., on Windows, it's focused window.
+   */
+  const unsigned long MOUSESCROLL_PREFER_WIDGET_AT_POINT = 0x00000001;
+
+  /**
+   * The platform specific values of aAdditionalFlags.  Must be over 0x00010000.
+   */
+
+  /**
+   * If MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL is set and aNativeMessage is
+   * WM_VSCROLL or WM_HSCROLL, widget will set the window handle to the lParam
+   * instead of NULL.
+   */
+  const unsigned long MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL = 0x00010000;
+
+  /**
    * See nsIWidget::SynthesizeNativeMouseScrollEvent
    *
    * Will be called on the widget that contains aElement.
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    *
    * NOTE: The synthesized native event may be fired asynchronously.
    *
@@ -461,36 +483,89 @@ interface nsIDOMWindowUtils : nsISupport
                                   in double aDeltaX,
                                   in double aDeltaY,
                                   in double aDeltaZ,
                                   in unsigned long aModifierFlags,
                                   in unsigned long aAdditionalFlags,
                                   in nsIDOMElement aElement);
 
   /**
-   * The values of aAdditionalFlags.
+   * Touch states for sendNativeTouchPoint. These values match
+   * nsIWidget's TouchPointerState.
    */
 
+  // The pointer is in a hover state above the digitizer
+  const long TOUCH_HOVER   = 0x01;
+  // The pointer is in contact with the digitizer
+  const long TOUCH_CONTACT = 0x02;
+  // The pointer has been removed from the digitizer detection area
+  const long TOUCH_REMOVE  = 0x04;
+  // The pointer has been canceled. Will cancel any pending os level
+  // gestures that would be triggered as a result of completion of the
+  // input sequence. This may not cancel moz platform related events
+  // that might get tirggered by input already delivered.
+  const long TOUCH_CANCEL  = 0x08;
+
   /**
-   * If MOUSESCROLL_PREFER_WIDGET_AT_POINT is set, widget will dispatch
-   * the event to a widget which is under the cursor.  Otherwise, dispatch to
-   * a default target on the platform.  E.g., on Windows, it's focused window.
+   * Create a new or update an existing touch point on the digitizer.
+   * To trigger os level gestures, individual touch points should
+   * transition through a complete set of touch states which should be
+   * sent as individual calls. For example:
+   * tap - msg1:TOUCH_CONTACT, msg2:TOUCH_REMOVE
+   * drag - msg1-n:TOUCH_CONTACT (moving), msgn+1:TOUCH_REMOVE
+   * hover drag - msg1-n:TOUCH_HOVER (moving), msgn+1:TOUCH_REMOVE
+   *
+   * Widget support: Windows 8.0+, Winrt/Win32. Other widgets will
+   * throw.
+   *
+   * @param aPointerId The touch point id to create or update.
+   * @param aTouchState one or more of the touch states listed above
+   * @param aScreenX, aScreenY screen coords of this event
+   * @param aPressure 0.0 -> 1.0 float val indicating pressure
+   * @param aOrientation 0 -> 359 degree value indicating the
+   * orientation of the pointer. Use 90 for normal taps.
    */
-  const unsigned long MOUSESCROLL_PREFER_WIDGET_AT_POINT = 0x00000001;
+  void sendNativeTouchPoint(in unsigned long aPointerId,
+                            in unsigned long aTouchState,
+                            in long aScreenX,
+                            in long aScreenY,
+                            in double aPressure,
+                            in unsigned long aOrientation);
 
   /**
-   * The platform specific values of aAdditionalFlags.  Must be over 0x00010000.
+   * Simulates native touch based taps on the input digitizer. Events
+   * triggered by this call are injected at the os level. Events do not
+   * bypass widget level input processing and as such can be used to
+   * test widget event logic and async pan-zoom controller functionality.
+   * Cannot be accessed from an unprivileged context.
+   *
+   * Long taps (based on the aLongTap parameter) will be completed
+   * asynchrnously after the call returns. Long tap delay is based on
+   * the ui.click_hold_context_menus.delay pref or 1500 msec if pref
+   * is not set.
+   *
+   * Widget support: Windows 8.0+, Winrt/Win32. Other widgets will
+   * throw.
+   *
+   * @param aScreenX, aScreenY screen coords of this event
+   * @param aLongTap true if the tap should be long, false for a short
+   * tap.
    */
+  void sendNativeTouchTap(in long aScreenX,
+                          in long aScreenY,
+                          in boolean aLongTap);
 
   /**
-   * If MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL is set and aNativeMessage is
-   * WM_VSCROLL or WM_HSCROLL, widget will set the window handle to the lParam
-   * instead of NULL.
+   * Cancel any existing touch points or long tap delays. Calling this is safe
+   * even if you're sure there aren't any pointers recorded. You should call
+   * this when tests shut down to reset the digitizer driver. Not doing so can
+   * leave the digitizer in an undetermined state which can screw up subsequent
+   * tests and native input.
    */
-  const unsigned long MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL = 0x00010000;
+  void clearNativeTouchSequence();
 
   /**
    * See nsIWidget::ActivateNativeMenuItemAt
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    */
   void activateNativeMenuItemAt(in AString indexString);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -28,33 +28,31 @@
 #include "gfxCrashReporterUtils.h"
 
 #include "gfxGDIFontList.h"
 #include "gfxGDIFont.h"
 
 #include "mozilla/layers/CompositorParent.h"   // for CompositorParent::IsInCompositorThread
 #include "DeviceManagerD3D9.h"
 
+#include "WinUtils.h"
+
 #ifdef CAIRO_HAS_DWRITE_FONT
 #include "gfxDWriteFontList.h"
 #include "gfxDWriteFonts.h"
 #include "gfxDWriteCommon.h"
 #include <dwrite.h>
 #endif
 
 #include "gfxUserFontSet.h"
 #include "nsWindowsHelpers.h"
 #include "gfx2DGlue.h"
 
 #include <string>
 
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::layers;
-
 #ifdef CAIRO_HAS_D2D_SURFACE
 #include "gfxD2DSurface.h"
 
 #include <d3d10_1.h>
 
 #include "mozilla/gfx/2D.h"
 
 #include "nsMemory.h"
@@ -62,16 +60,19 @@ using namespace mozilla::layers;
 
 #include <d3d11.h>
 
 #include "nsIMemoryReporter.h"
 #include <winternl.h>
 #include "d3dkmtQueryStatistics.h"
 
 using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
 
 #ifdef CAIRO_HAS_D2D_SURFACE
 
 static const char *kFeatureLevelPref =
   "gfx.direct3d.last_used_feature_level_idx";
 static const int kSupportedFeatureLevels[] =
   { D3D10_FEATURE_LEVEL_10_1, D3D10_FEATURE_LEVEL_10_0,
     D3D10_FEATURE_LEVEL_9_3 };
@@ -358,18 +359,16 @@ gfxWindowsPlatform::gfxWindowsPlatform()
 
     mUsingGDIFonts = false;
 
     /* 
      * Initialize COM 
      */ 
     CoInitialize(nullptr); 
 
-    mScreenDC = GetDC(nullptr);
-
 #ifdef CAIRO_HAS_D2D_SURFACE
     RegisterStrongMemoryReporter(new GfxD2DSurfaceCacheReporter());
     RegisterStrongMemoryReporter(new GfxD2DSurfaceVramReporter());
     mD2DDevice = nullptr;
 #endif
     RegisterStrongMemoryReporter(new GfxD2DVramDrawTargetReporter());
     RegisterStrongMemoryReporter(new GfxD2DVramSourceSurfaceReporter());
 
@@ -379,33 +378,38 @@ gfxWindowsPlatform::gfxWindowsPlatform()
     // bug 917496.
     //RegisterStrongMemoryReporter(new GPUAdapterReporter());
 }
 
 gfxWindowsPlatform::~gfxWindowsPlatform()
 {
     mDeviceManager = nullptr;
 
-    ::ReleaseDC(nullptr, mScreenDC);
     // not calling FT_Done_FreeType because cairo may still hold references to
     // these FT_Faces.  See bug 458169.
 #ifdef CAIRO_HAS_D2D_SURFACE
     if (mD2DDevice) {
         cairo_release_device(mD2DDevice);
     }
 #endif
 
     mozilla::gfx::Factory::D2DCleanup();
 
     /* 
      * Uninitialize COM 
      */ 
     CoUninitialize();
 }
 
+double
+gfxWindowsPlatform::GetDPIScale()
+{
+  return WinUtils::LogToPhysFactor();
+}
+
 void
 gfxWindowsPlatform::UpdateRenderMode()
 {
 /* Pick the default render mode for
  * desktop.
  */
     mRenderMode = RENDER_GDI;
 
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -170,26 +170,22 @@ public:
      * cairo device creation routines.
      */
     void VerifyD2DDevice(bool aAttemptForce);
 
 #ifdef CAIRO_HAS_D2D_SURFACE
     HRESULT CreateDevice(nsRefPtr<IDXGIAdapter1> &adapter1, int featureLevelIndex);
 #endif
 
-    HDC GetScreenDC() { return mScreenDC; }
-
     /**
      * Return the resolution scaling factor to convert between "logical" or
      * "screen" pixels as used by Windows (dependent on the DPI scaling option
      * in the Display control panel) and actual device pixels.
      */
-    double GetDPIScale() {
-        return GetDeviceCaps(mScreenDC, LOGPIXELSY) / 96.0;
-    }
+    double GetDPIScale();
 
     nsresult GetFontList(nsIAtom *aLangGroup,
                          const nsACString& aGenericFamily,
                          nsTArray<nsString>& aListOfFonts);
 
     nsresult UpdateFontList();
 
     virtual void GetCommonFallbackFonts(const uint32_t aCh,
@@ -269,17 +265,16 @@ public:
 
     static bool IsOptimus();
 
 protected:
     RenderMode mRenderMode;
 
     int8_t mUseClearTypeForDownloadableFonts;
     int8_t mUseClearTypeAlways;
-    HDC mScreenDC;
 
 private:
     void Init();
     IDXGIAdapter1 *GetDXGIAdapter();
 
     bool mUseDirectWrite;
     bool mUsingGDIFonts;
 
--- a/mobile/android/base/resources/layout/two_line_page_row.xml
+++ b/mobile/android/base/resources/layout/two_line_page_row.xml
@@ -15,17 +15,17 @@
     <LinearLayout android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
                   android:layout_marginRight="10dip"
                   android:orientation="vertical">
 
         <org.mozilla.gecko.home.FadedTextView
                 android:id="@+id/title"
                 style="@style/Widget.TwoLinePageRow.Title"
-                android:layout_width="wrap_content"
+                android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 gecko:fadeWidth="30dp"/>
 
         <TextView android:id="@+id/url"
                   style="@style/Widget.TwoLinePageRow.Url"
                   android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
                   android:drawablePadding="5dp"/>
--- a/mobile/android/base/tests/UITest.java
+++ b/mobile/android/base/tests/UITest.java
@@ -121,17 +121,16 @@ abstract class UITest extends ActivityIn
     }
 
     private void initHelpers() {
         // Other helpers make assertions so init AssertionHelper first.
         AssertionHelper.init(this);
 
         DeviceHelper.init(this);
         GeckoHelper.init(this);
-        GestureHelper.init(this);
         NavigationHelper.init(this);
         WaitHelper.init(this);
     }
 
     @Override
     public Solo getSolo() {
         return mSolo;
     }
--- a/mobile/android/base/tests/components/AboutHomeComponent.java
+++ b/mobile/android/base/tests/components/AboutHomeComponent.java
@@ -36,16 +36,20 @@ public class AboutHomeComponent extends 
     // Explicit ordering of HomePager pages on a tablet.
     private enum TabletPage {
         TOP_SITES,
         BOOKMARKS,
         READING_LIST,
         HISTORY
     }
 
+    // The percentage of the page to swipe between 0 and 1. This value was set through
+    // testing: 0.55f was tested on try and fails on armv6 devices.
+    private static final float SWIPE_PERCENTAGE = 0.70f;
+
     public AboutHomeComponent(final UITestContext testContext) {
         super(testContext);
     }
 
     private ViewPager getHomePagerView() {
         return (ViewPager) mSolo.getView(R.id.home_pager);
     }
 
@@ -65,45 +69,43 @@ public class AboutHomeComponent extends 
     }
 
     public AboutHomeComponent assertVisible() {
         assertEquals("The HomePager is visible",
                      View.VISIBLE, getHomePagerView().getVisibility());
         return this;
     }
 
-    // TODO: Take specific page as parameter rather than swipe in a direction?
     public AboutHomeComponent swipeToPageOnRight() {
         mTestContext.dumpLog("Swiping to the page on the right.");
-        swipe(Solo.LEFT);
+        swipeToPage(Solo.RIGHT);
         return this;
     }
 
     public AboutHomeComponent swipeToPageOnLeft() {
         mTestContext.dumpLog("Swiping to the page on the left.");
-        swipe(Solo.RIGHT);
+        swipeToPage(Solo.LEFT);
         return this;
     }
 
-    private void swipe(final int direction) {
+    private void swipeToPage(final int pageDirection) {
+        assertTrue("Swiping in a vaild direction",
+                pageDirection == Solo.LEFT || pageDirection == Solo.RIGHT);
         assertVisible();
 
         final int pageIndex = getHomePagerView().getCurrentItem();
-        if (direction == Solo.LEFT) {
-            GestureHelper.swipeLeft();
-        } else {
-            GestureHelper.swipeRight();
-        }
+
+        mSolo.scrollViewToSide(getHomePagerView(), pageDirection, SWIPE_PERCENTAGE);
 
-        final PagerAdapter adapter = getHomePagerView().getAdapter();
-        assertNotNull("The HomePager's PagerAdapter is not null", adapter);
-
-        // Swiping left goes to next, swiping right goes to previous
-        final int unboundedPageIndex = pageIndex + (direction == Solo.LEFT ? 1 : -1);
-        final int expectedPageIndex = Math.min(Math.max(0, unboundedPageIndex), adapter.getCount() - 1);
+        // The page on the left is a lower index and vice versa.
+        final int unboundedPageIndex = pageIndex + (pageDirection == Solo.LEFT ? -1 : 1);
+        final int pageCount = DeviceHelper.isTablet() ?
+                TabletPage.values().length : PhonePage.values().length;
+        final int maxPageIndex = pageCount - 1;
+        final int expectedPageIndex = Math.min(Math.max(0, unboundedPageIndex), maxPageIndex);
 
         waitForPageIndex(expectedPageIndex);
     }
 
     private void waitForPageIndex(final int expectedIndex) {
         final String pageName;
         if (DeviceHelper.isTablet()) {
             pageName = TabletPage.values()[expectedIndex].name();
deleted file mode 100644
--- a/mobile/android/base/tests/helpers/GestureHelper.java
+++ /dev/null
@@ -1,44 +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/. */
-
-package org.mozilla.gecko.tests.helpers;
-
-import org.mozilla.gecko.Driver;
-import org.mozilla.gecko.tests.UITestContext;
-
-import com.jayway.android.robotium.solo.Solo;
-
-/**
- * Provides simplified gestures wrapping the Robotium gestures API.
- */
-public final class GestureHelper {
-    private static int DEFAULT_DRAG_STEP_COUNT = 10;
-
-    private static Solo sSolo;
-    private static Driver sDriver;
-
-    private GestureHelper() { /* To disallow instantation. */ }
-
-    public static void init(final UITestContext context) {
-        sSolo = context.getSolo();
-        sDriver = context.getDriver();
-    }
-
-    private static void swipeOnScreen(final int direction) {
-        final int halfWidth = sDriver.getGeckoWidth() / 2;
-        final int halfHeight = sDriver.getGeckoHeight() / 2;
-
-        sSolo.drag(direction == Solo.LEFT ? halfWidth : 0,
-                   direction == Solo.LEFT ? 0 : halfWidth,
-                   halfHeight, halfHeight, DEFAULT_DRAG_STEP_COUNT);
-    }
-
-    public static void swipeLeft() {
-        swipeOnScreen(Solo.LEFT);
-    }
-
-    public static void swipeRight() {
-        swipeOnScreen(Solo.RIGHT);
-    }
-}
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -42,16 +42,17 @@ googlemail.com: did not receive HSTS hea
 googleplex.com: could not connect to host
 goto.google.com: did not receive HSTS header
 greplin.com: did not receive HSTS header
 grepular.com: max-age too low: 8640000
 groups.google.com: did not receive HSTS header
 history.google.com: did not receive HSTS header
 hostedtalkgadget.google.com: did not receive HSTS header
 id.atlassian.com: did not receive HSTS header
+in.xero.com: max-age too low: 3600
 iop.intuit.com: max-age too low: 86400
 irccloud.com: did not receive HSTS header
 jitsi.org: did not receive HSTS header
 jottit.com: did not receive HSTS header
 kiwiirc.com: max-age too low: 5256000
 ledgerscope.net: did not receive HSTS header
 liberty.lavabit.com: could not connect to host
 lifeguard.aecom.com: max-age too low: 3600
@@ -63,16 +64,17 @@ market.android.com: did not receive HSTS
 medium.com: max-age too low: 2592000
 my.alfresco.com: did not receive HSTS header
 mydigipass.com: did not receive HSTS header
 neonisi.com: could not connect to host
 openshift.redhat.com: did not receive HSTS header
 ottospora.nl: could not connect to host
 packagist.org: max-age too low: 2592000
 paypal.com: max-age too low: 14400
+payroll.xero.com: max-age too low: 3600
 platform.lookout.com: could not connect to host
 play.google.com: did not receive HSTS header
 plus.google.com: did not receive HSTS header
 plus.sandbox.google.com: did not receive HSTS header
 profiles.google.com: did not receive HSTS header
 rapidresearch.me: did not receive HSTS header
 romab.com: max-age too low: 2628000
 sah3.net: could not connect to host
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,29 +3,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsSiteSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
 #include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1397301006735000);
+const PRTime gPreloadListExpirationTime = INT64_C(1397905536640000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
 static const nsSTSPreload kSTSPreloadList[] = {
   { "aladdinschools.appspot.com", false },
   { "alpha.irccloud.com", false },
   { "api.intercom.io", false },
+  { "api.xero.com", false },
   { "app.recurly.com", false },
   { "appseccalifornia.org", true },
   { "arivo.com.br", true },
   { "bank.simple.com", false },
   { "bassh.net", true },
   { "bccx.com", true },
   { "blog.cyveillance.com", true },
   { "blog.linode.com", false },
@@ -44,33 +45,36 @@ static const nsSTSPreload kSTSPreloadLis
   { "controlcenter.gigahost.dk", true },
   { "crate.io", true },
   { "crm.onlime.ch", false },
   { "crypto.cat", false },
   { "cupcake.io", true },
   { "cupcake.is", true },
   { "cybozu.com", true },
   { "cyphertite.com", true },
+  { "data.qld.gov.au", false },
   { "davidlyness.com", true },
   { "developer.mydigipass.com", false },
   { "dist.torproject.org", false },
   { "dm.lookout.com", false },
   { "dm.mylookout.com", false },
   { "download.jitsi.org", false },
   { "ebanking.indovinabank.com.vn", false },
   { "ecosystem.atlassian.net", true },
+  { "eff.org", true },
   { "entropia.de", false },
   { "errors.zenpayroll.com", false },
   { "espra.com", true },
   { "factor.cc", false },
   { "faq.lookout.com", false },
   { "forum.linode.com", false },
   { "forum.quantifiedself.com", true },
   { "gernert-server.de", true },
   { "getlantern.org", true },
+  { "go.xero.com", false },
   { "grc.com", false },
   { "haste.ch", true },
   { "howrandom.org", true },
   { "id.mayfirst.org", false },
   { "inertianetworks.com", true },
   { "intercom.io", false },
   { "itriskltd.com", true },
   { "keyerror.com", true },
@@ -78,43 +82,47 @@ static const nsSTSPreload kSTSPreloadLis
   { "lastpass.com", false },
   { "launchkey.com", true },
   { "library.linode.com", false },
   { "linode.com", false },
   { "linx.net", false },
   { "lockify.com", true },
   { "login.persona.org", true },
   { "login.sapo.pt", true },
+  { "login.xero.com", false },
   { "logotype.se", true },
   { "lolicore.ch", true },
   { "lookout.com", false },
   { "lumi.do", false },
   { "luneta.nearbuysystems.com", false },
+  { "mail.de", true },
   { "makeyourlaws.org", false },
   { "manage.zenpayroll.com", false },
   { "manager.linode.com", false },
   { "mattmccutchen.net", true },
   { "mediacru.sh", true },
   { "mega.co.nz", false },
   { "members.mayfirst.org", false },
   { "members.nearlyfreespeech.net", false },
   { "mudcrab.us", true },
   { "my.onlime.ch", false },
+  { "my.xero.com", false },
   { "mylookout.com", false },
   { "neg9.org", false },
   { "oplop.appspot.com", true },
   { "p.linode.com", false },
   { "passwd.io", true },
   { "paste.linode.com", false },
   { "pastebin.linode.com", false },
   { "pay.gigahost.dk", true },
   { "paymill.com", true },
   { "paymill.de", false },
   { "piratenlogin.de", true },
   { "pixi.me", true },
+  { "publications.qld.gov.au", false },
   { "riseup.net", true },
   { "roundcube.mayfirst.org", false },
   { "sandbox.mydigipass.com", false },
   { "securityheaders.com", true },
   { "shodan.io", true },
   { "silentcircle.com", false },
   { "simbolo.co.uk", false },
   { "simple.com", false },
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -7,22 +7,25 @@
 #define nsIWidget_h__
 
 #include "nsISupports.h"
 #include "nsColor.h"
 #include "nsRect.h"
 #include "nsStringGlue.h"
 
 #include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
 #include "nsWidgetInitData.h"
 #include "nsTArray.h"
+#include "nsITimer.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
 #include "Units.h"
 
 // forward declarations
 class   nsFontMetrics;
 class   nsRenderingContext;
 class   nsDeviceContext;
 struct  nsFont;
 class   nsIRollupListener;
@@ -92,18 +95,18 @@ typedef void* nsNativeWidget;
 #ifdef XP_WIN
 #define NS_NATIVE_TSF_THREAD_MGR       100
 #define NS_NATIVE_TSF_CATEGORY_MGR     101
 #define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102
 #define NS_NATIVE_ICOREWINDOW          103 // winrt specific
 #endif
 
 #define NS_IWIDGET_IID \
-{ 0x746cb189, 0x9793, 0x4e53, \
-  { 0x89, 0x47, 0x78, 0x56, 0xb6, 0xcd, 0x9f, 0x71 } }
+{ 0x67da44c4, 0xe21b, 0x4742, \
+  { 0x9c, 0x2b, 0x26, 0xc7, 0x70, 0x21, 0xde, 0x87 } }
 
 /*
  * Window shadow styles
  * Also used for the -moz-window-shadow CSS property
  */
 
 #define NS_STYLE_WINDOW_SHADOW_NONE             0
 #define NS_STYLE_WINDOW_SHADOW_DEFAULT          1
@@ -488,17 +491,19 @@ class nsIWidget : public nsISupports {
     };
 
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWIDGET_IID)
 
     nsIWidget()
       : mLastChild(nullptr)
       , mPrevSibling(nullptr)
       , mOnDestroyCalled(false)
-    {}
+    {
+      ClearNativeTouchSequence();
+    }
 
         
     /**
      * Create and initialize a widget. 
      *
      * All the arguments can be NULL in which case a top level window
      * with size 0 is created. The event callback function has to be
      * provided only if the caller wants to deal with the events this
@@ -1554,16 +1559,95 @@ class nsIWidget : public nsISupports {
     virtual nsresult SynthesizeNativeMouseScrollEvent(nsIntPoint aPoint,
                                                       uint32_t aNativeMessage,
                                                       double aDeltaX,
                                                       double aDeltaY,
                                                       double aDeltaZ,
                                                       uint32_t aModifierFlags,
                                                       uint32_t aAdditionalFlags) = 0;
 
+    /*
+     * TouchPointerState states for SynthesizeNativeTouchPoint. Match
+     * touch states in nsIDOMWindowUtils.idl.
+     */
+    enum TouchPointerState {
+      // The pointer is in a hover state above the digitizer
+      TOUCH_HOVER    = 0x01,
+      // The pointer is in contact with the digitizer
+      TOUCH_CONTACT  = 0x02,
+      // The pointer has been removed from the digitizer detection area
+      TOUCH_REMOVE   = 0x04,
+      // The pointer has been canceled. Will cancel any pending os level
+      // gestures that would triggered as a result of completion of the
+      // input sequence. This may not cancel moz platform related events
+      // that might get tirggered by input already delivered.
+      TOUCH_CANCEL   = 0x08
+    };
+
+    /*
+     * Create a new or update an existing touch pointer on the digitizer.
+     * To trigger os level gestures, individual touch points should
+     * transition through a complete set of touch states which should be
+     * sent as individual messages.
+     *
+     * @param aPointerId The touch point id to create or update.
+     * @param aPointerState one or more of the touch states listed above
+     * @param aScreenX, aScreenY screen coords of this event
+     * @param aPressure 0.0 -> 1.0 float val indicating pressure
+     * @param aOrientation 0 -> 359 degree value indicating the
+     * orientation of the pointer. Use 90 for normal taps.
+     */
+    virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+                                                TouchPointerState aPointerState,
+                                                nsIntPoint aPointerScreenPoint,
+                                                double aPointerPressure,
+                                                uint32_t aPointerOrientation) = 0;
+
+    /*
+     * Cancels all active simulated touch input points and pending long taps.
+     * Native widgets should track existing points such that they can clear the
+     * digitizer state when this call is made.
+     */
+    virtual nsresult ClearNativeTouchSequence();
+
+    /*
+     * Helper for simulating a simple tap event with one touch point. When
+     * aLongTap is true, simulates a native long tap with a duration equal to
+     * ui.click_hold_context_menus.delay. This pref is compatible with the
+     * apzc long tap duration. Defaults to 1.5 seconds.
+     */
+    nsresult SynthesizeNativeTouchTap(nsIntPoint aPointerScreenPoint,
+                                      bool aLongTap);
+
+private:
+  class LongTapInfo
+  {
+  public:
+    LongTapInfo(int32_t aPointerId, nsIntPoint& aPoint,
+                mozilla::TimeDuration aDuration) :
+      mPointerId(aPointerId),
+      mPosition(aPoint),
+      mDuration(aDuration),
+      mStamp(mozilla::TimeStamp::Now())
+    {
+    }
+
+    int32_t mPointerId;
+    nsIntPoint mPosition;
+    mozilla::TimeDuration mDuration;
+    mozilla::TimeStamp mStamp;
+  };
+
+  static void OnLongTapTimerCallback(nsITimer* aTimer, void* aClosure);
+
+  nsAutoPtr<LongTapInfo> mLongTapTouchPoint;
+  nsCOMPtr<nsITimer> mLongTapTimer;
+  static int32_t sPointerIdCounter;
+
+public:
     /**
      * Activates a native menu item at the position specified by the index
      * string. The index string is a string of positive integers separated
      * by the "|" (pipe) character. The last integer in the string represents
      * the item index in a submenu located using the integers preceding it.
      *
      * Example: 1|0|4
      * In this string, the first integer represents the top-level submenu
--- a/widget/windows/WinUtils.cpp
+++ b/widget/windows/WinUtils.cpp
@@ -32,16 +32,17 @@
 #include "nsIChannel.h"
 #include "nsIObserver.h"
 #include "imgIEncoder.h"
 #include "nsIThread.h"
 #include "MainThreadUtils.h"
 #include "gfxColor.h"
 #ifdef MOZ_METRO
 #include "winrt/MetroInput.h"
+#include "winrt/MetroUtils.h"
 #endif // MOZ_METRO
 
 #ifdef NS_ENABLE_TSF
 #include <textstor.h>
 #include "nsTextStore.h"
 #endif // #ifdef NS_ENABLE_TSF
 
 #ifdef PR_LOGGING
@@ -181,16 +182,57 @@ WinUtils::Log(const char *fmt, ...)
   NS_ASSERTION(gWindowsLog, "Called WinUtils Log() but Widget "
                                "log module doesn't exist!");
   PR_LOG(gWindowsLog, PR_LOG_ALWAYS, (buffer));
 #endif
   delete[] buffer;
 }
 
 /* static */
+double
+WinUtils::LogToPhysFactor()
+{
+  // dpi / 96.0
+  if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Metro) {
+#ifdef MOZ_METRO
+    return MetroUtils::LogToPhysFactor();
+#else
+    return 1.0;
+#endif
+  } else {
+    HDC hdc = ::GetDC(nullptr);
+    double result = ::GetDeviceCaps(hdc, LOGPIXELSY) / 96.0;
+    ::ReleaseDC(nullptr, hdc);
+    return result;
+  }
+}
+
+/* static */
+double
+WinUtils::PhysToLogFactor()
+{
+  // 1.0 / (dpi / 96.0)
+  return 1.0 / LogToPhysFactor();
+}
+
+/* static */
+double
+WinUtils::PhysToLog(int32_t aValue)
+{
+  return double(aValue) * PhysToLogFactor();
+}
+
+/* static */
+int32_t
+WinUtils::LogToPhys(double aValue)
+{
+  return int32_t(NS_round(aValue * LogToPhysFactor()));
+}
+
+/* static */
 bool
 WinUtils::PeekMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
                       UINT aLastMessage, UINT aOption)
 {
 #ifdef NS_ENABLE_TSF
   ITfMessagePump* msgPump = nsTextStore::GetMessagePump();
   if (msgPump) {
     BOOL ret = FALSE;
--- a/widget/windows/WinUtils.h
+++ b/widget/windows/WinUtils.h
@@ -65,16 +65,25 @@ class myDownloadObserver MOZ_FINAL : pub
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOWNLOADOBSERVER
 };
 
 class WinUtils {
 public:
   /**
+   * Functions to convert between logical pixels as used by most Windows APIs
+   * and physical (device) pixels.
+   */
+  static double LogToPhysFactor();
+  static double PhysToLogFactor();
+  static int32_t LogToPhys(double aValue);
+  static double PhysToLog(int32_t aValue);
+
+  /**
    * Logging helpers that dump output to prlog module 'Widget', console, and
    * OutputDebugString. Note these output in both debug and release builds.
    */
   static void Log(const char *fmt, ...);
   static void LogW(const wchar_t *fmt, ...);
 
   /**
    * PeekMessage() and GetMessage() are wrapper methods for PeekMessageW(),
--- a/widget/windows/nsNativeThemeWin.cpp
+++ b/widget/windows/nsNativeThemeWin.cpp
@@ -2425,17 +2425,17 @@ nsNativeThemeWin::GetMinimumWidgetSize(n
       return NS_OK;
   }
 
   int32_t part, state;
   nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
   if (NS_FAILED(rv))
     return rv;
 
-  HDC hdc = gfxWindowsPlatform::GetPlatform()->GetScreenDC();
+  HDC hdc = ::GetDC(nullptr);
   if (!hdc)
     return NS_ERROR_FAILURE;
 
   SIZE sz;
   GetThemePartSize(theme, hdc, part, state, nullptr, sizeReq, &sz);
   aResult->width = sz.cx;
   aResult->height = sz.cy;
 
@@ -2443,22 +2443,23 @@ nsNativeThemeWin::GetMinimumWidgetSize(n
     case NS_THEME_SPINNER_UP_BUTTON:
     case NS_THEME_SPINNER_DOWN_BUTTON:
       aResult->width++;
       aResult->height = aResult->height / 2 + 1;
       break;
 
     case NS_THEME_MENUSEPARATOR:
     {
-      SIZE gutterSize(GetGutterSize(theme,hdc));
+      SIZE gutterSize(GetGutterSize(theme, hdc));
       aResult->width += gutterSize.cx;
       break;
     }
   }
 
+  ::ReleaseDC(nullptr, hdc);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, 
                                      nsIAtom* aAttribute, bool* aShouldRepaint)
 {
   // Some widget types just never change state.
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -974,17 +974,17 @@ float nsWindow::GetDPI()
     // Something's broken
     return 96.0f;
   }
   return float(heightPx/heightInches);
 }
 
 double nsWindow::GetDefaultScaleInternal()
 {
-  return gfxWindowsPlatform::GetPlatform()->GetDPIScale();
+  return WinUtils::LogToPhysFactor();
 }
 
 nsWindow*
 nsWindow::GetParentWindow(bool aIncludeOwner)
 {
   return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
 }
 
--- a/widget/windows/nsWindowBase.cpp
+++ b/widget/windows/nsWindowBase.cpp
@@ -1,19 +1,26 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
 #include "nsWindowBase.h"
 
 #include "mozilla/MiscEvents.h"
+
+#include "WinUtils.h"
 #include "npapi.h"
 
 using namespace mozilla;
+using namespace mozilla::widget;
+
+static const wchar_t kUser32LibName[] =  L"user32.dll";
+bool nsWindowBase::sTouchInjectInitialized = false;
+InjectTouchInputPtr nsWindowBase::sInjectTouchFuncPtr;
 
 bool
 nsWindowBase::DispatchPluginEvent(const MSG& aMsg)
 {
   if (!PluginHasFocus()) {
     return false;
   }
   WidgetPluginEvent pluginEvent(true, NS_PLUGIN_INPUT_EVENT, this);
@@ -22,8 +29,169 @@ nsWindowBase::DispatchPluginEvent(const 
   NPEvent npEvent;
   npEvent.event = aMsg.message;
   npEvent.wParam = aMsg.wParam;
   npEvent.lParam = aMsg.lParam;
   pluginEvent.pluginEvent = &npEvent;
   pluginEvent.retargetToFocusedDocument = true;
   return DispatchWindowEvent(&pluginEvent);
 }
+
+// static
+bool
+nsWindowBase::InitTouchInjection()
+{
+  if (!sTouchInjectInitialized) {
+    // Initialize touch injection on the first call
+    HMODULE hMod = LoadLibraryW(kUser32LibName);
+    if (!hMod) {
+      return false;
+    }
+
+    InitializeTouchInjectionPtr func =
+      (InitializeTouchInjectionPtr)GetProcAddress(hMod, "InitializeTouchInjection");
+    if (!func) {
+      WinUtils::Log("InitializeTouchInjection not available.");
+      return false;
+    }
+
+    if (!func(TOUCH_INJECT_MAX_POINTS, TOUCH_FEEDBACK_DEFAULT)) {
+      WinUtils::Log("InitializeTouchInjection failure. GetLastError=%d", GetLastError());
+      return false;
+    }
+
+    sInjectTouchFuncPtr =
+      (InjectTouchInputPtr)GetProcAddress(hMod, "InjectTouchInput");
+    if (!sInjectTouchFuncPtr) {
+      WinUtils::Log("InjectTouchInput not available.");
+      return false;
+    }
+    sTouchInjectInitialized = true;
+  }
+  return true;
+}
+
+bool
+nsWindowBase::InjectTouchPoint(uint32_t aId, nsIntPoint& aPointerScreenPoint,
+                               POINTER_FLAGS aFlags, uint32_t aPressure,
+                               uint32_t aOrientation)
+{
+  if (aId > TOUCH_INJECT_MAX_POINTS) {
+    WinUtils::Log("Pointer ID exceeds maximum. See TOUCH_INJECT_MAX_POINTS.");
+    return false;
+  }
+
+  POINTER_TOUCH_INFO info;
+  memset(&info, 0, sizeof(POINTER_TOUCH_INFO));
+
+  info.touchFlags = TOUCH_FLAG_NONE;
+  info.touchMask = TOUCH_MASK_CONTACTAREA|TOUCH_MASK_ORIENTATION|TOUCH_MASK_PRESSURE;
+  info.pressure = aPressure;
+  info.orientation = aOrientation;
+  
+  info.pointerInfo.pointerFlags = aFlags;
+  info.pointerInfo.pointerType =  PT_TOUCH;
+  info.pointerInfo.pointerId = aId;
+  info.pointerInfo.ptPixelLocation.x = WinUtils::LogToPhys(aPointerScreenPoint.x);
+  info.pointerInfo.ptPixelLocation.y = WinUtils::LogToPhys(aPointerScreenPoint.y);
+
+  info.rcContact.top = info.pointerInfo.ptPixelLocation.y - 2;
+  info.rcContact.bottom = info.pointerInfo.ptPixelLocation.y + 2;
+  info.rcContact.left = info.pointerInfo.ptPixelLocation.x - 2;
+  info.rcContact.right = info.pointerInfo.ptPixelLocation.x + 2;
+  
+  if (!sInjectTouchFuncPtr(1, &info)) {
+    WinUtils::Log("InjectTouchInput failure. GetLastError=%d", GetLastError());
+    return false;
+  }
+  return true;
+}
+
+nsresult
+nsWindowBase::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+                                         nsIWidget::TouchPointerState aPointerState,
+                                         nsIntPoint aPointerScreenPoint,
+                                         double aPointerPressure,
+                                         uint32_t aPointerOrientation)
+{
+  if (!InitTouchInjection()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  bool hover = aPointerState & TOUCH_HOVER;
+  bool contact = aPointerState & TOUCH_CONTACT;
+  bool remove = aPointerState & TOUCH_REMOVE;
+  bool cancel = aPointerState & TOUCH_CANCEL;
+
+  // win api expects a value from 0 to 1024. aPointerPressure is a value
+  // from 0.0 to 1.0.
+  uint32_t pressure = (uint32_t)ceil(aPointerPressure * 1024);
+
+  // If we already know about this pointer id get it's record
+  PointerInfo* info = mActivePointers.Get(aPointerId);
+
+  // We know about this pointer, send an update
+  if (info) {
+    POINTER_FLAGS flags = POINTER_FLAG_UPDATE;
+    if (hover) {
+      flags |= POINTER_FLAG_INRANGE;
+    } else if (contact) {
+      flags |= POINTER_FLAG_INCONTACT|POINTER_FLAG_INRANGE;
+    } else if (remove) {
+      flags = POINTER_FLAG_UP;
+      // Remove the pointer from our tracking list. This is nsAutPtr wrapped,
+      // so shouldn't leak.
+      mActivePointers.Remove(aPointerId);
+    }
+
+    if (cancel) {
+      flags |= POINTER_FLAG_CANCELED;
+    }
+
+    return !InjectTouchPoint(aPointerId, aPointerScreenPoint, flags,
+                             pressure, aPointerOrientation) ?
+      NS_ERROR_UNEXPECTED : NS_OK;
+  }
+
+  // Missing init state, error out
+  if (remove || cancel) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // Create a new pointer
+  info = new PointerInfo(aPointerId, aPointerScreenPoint);
+
+  POINTER_FLAGS flags = POINTER_FLAG_INRANGE;
+  if (contact) {
+    flags |= POINTER_FLAG_INCONTACT|POINTER_FLAG_DOWN;
+  }
+
+  mActivePointers.Put(aPointerId, info);
+  return !InjectTouchPoint(aPointerId, aPointerScreenPoint, flags,
+                           pressure, aPointerOrientation) ?
+    NS_ERROR_UNEXPECTED : NS_OK;
+}
+
+// static
+PLDHashOperator
+nsWindowBase::CancelTouchPoints(const unsigned int& aPointerId, nsAutoPtr<PointerInfo>& aInfo, void* aUserArg)
+{
+  nsWindowBase* self = static_cast<nsWindowBase*>(aUserArg);
+  self->InjectTouchPoint(aInfo.get()->mPointerId, aInfo.get()->mPosition, POINTER_FLAG_CANCELED);
+  return (PLDHashOperator)(PL_DHASH_NEXT|PL_DHASH_REMOVE);
+}
+
+nsresult
+nsWindowBase::ClearNativeTouchSequence()
+{
+  if (!sTouchInjectInitialized) {
+    return NS_OK;
+  }
+
+  // cancel all input points
+  mActivePointers.Enumerate(CancelTouchPoints, (void*)this);
+
+  nsBaseWidget::ClearNativeTouchSequence();
+
+  return NS_OK;
+}
+
+
--- a/widget/windows/nsWindowBase.h
+++ b/widget/windows/nsWindowBase.h
@@ -3,17 +3,20 @@
  * 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/. */
 
 #ifndef nsWindowBase_h_
 #define nsWindowBase_h_
 
 #include "mozilla/EventForwards.h"
 #include "nsBaseWidget.h"
+#include "nsClassHashtable.h"
+
 #include <windows.h>
+#include "touchinjection_sdk80.h"
 
 /*
  * nsWindowBase - Base class of common methods other classes need to access
  * in both win32 and winrt window classes.
  */
 class nsWindowBase : public nsBaseWidget
 {
 public:
@@ -70,13 +73,49 @@ public:
   /*
    * Returns true if a plugin has focus on this widget.  Otherwise, false.
    */
   virtual bool PluginHasFocus() const MOZ_FINAL
   {
     return (mInputContext.mIMEState.mEnabled == IMEState::PLUGIN);
   }
 
+public:
+  /*
+   * Touch input injection apis
+   */
+  virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+                                              TouchPointerState aPointerState,
+                                              nsIntPoint aPointerScreenPoint,
+                                              double aPointerPressure,
+                                              uint32_t aPointerOrientation);
+  virtual nsresult ClearNativeTouchSequence();
+
+protected:
+  static bool InitTouchInjection();
+  bool InjectTouchPoint(uint32_t aId, nsIntPoint& aPointerScreenPoint,
+                        POINTER_FLAGS aFlags, uint32_t aPressure = 1024,
+                        uint32_t aOrientation = 90);
+
+  class PointerInfo
+  {
+  public:
+    PointerInfo(int32_t aPointerId, nsIntPoint& aPoint) :
+      mPointerId(aPointerId),
+      mPosition(aPoint)
+    {
+    }
+
+    int32_t mPointerId;
+    nsIntPoint mPosition;
+  };
+
+  static PLDHashOperator CancelTouchPoints(const unsigned int& aPointerId, nsAutoPtr<PointerInfo>& aInfo, void* aUserArg);
+
+  nsClassHashtable<nsUint32HashKey, PointerInfo> mActivePointers;
+  static bool sTouchInjectInitialized;
+  static InjectTouchInputPtr sInjectTouchFuncPtr;
+
 protected:
   InputContext mInputContext;
 };
 
 #endif // nsWindowBase_h_
new file mode 100644
--- /dev/null
+++ b/widget/windows/touchinjection_sdk80.h
@@ -0,0 +1,107 @@
+/* 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/. */
+
+#ifndef touchinjection_sdk80_h
+#define touchinjection_sdk80_h
+
+// Note, this isn't inclusive of all touch injection header info.
+// You may need to add more to expand on current apis. 
+
+#ifndef TOUCH_FEEDBACK_DEFAULT
+
+#define TOUCH_FEEDBACK_DEFAULT 0x1
+#define TOUCH_FEEDBACK_INDIRECT 0x2
+#define TOUCH_FEEDBACK_NONE 0x3
+
+enum {
+  PT_POINTER  = 0x00000001,   // Generic pointer
+  PT_TOUCH    = 0x00000002,   // Touch
+  PT_PEN      = 0x00000003,   // Pen
+  PT_MOUSE    = 0x00000004,   // Mouse
+};
+
+typedef DWORD POINTER_INPUT_TYPE;
+typedef UINT32 POINTER_FLAGS;
+
+typedef enum {
+  POINTER_CHANGE_NONE,
+  POINTER_CHANGE_FIRSTBUTTON_DOWN,
+  POINTER_CHANGE_FIRSTBUTTON_UP,
+  POINTER_CHANGE_SECONDBUTTON_DOWN,
+  POINTER_CHANGE_SECONDBUTTON_UP,
+  POINTER_CHANGE_THIRDBUTTON_DOWN,
+  POINTER_CHANGE_THIRDBUTTON_UP,
+  POINTER_CHANGE_FOURTHBUTTON_DOWN,
+  POINTER_CHANGE_FOURTHBUTTON_UP,
+  POINTER_CHANGE_FIFTHBUTTON_DOWN,
+  POINTER_CHANGE_FIFTHBUTTON_UP,
+} POINTER_BUTTON_CHANGE_TYPE;
+
+typedef struct {
+  POINTER_INPUT_TYPE    pointerType;
+  UINT32          pointerId;
+  UINT32          frameId;
+  POINTER_FLAGS   pointerFlags;
+  HANDLE          sourceDevice;
+  HWND            hwndTarget;
+  POINT           ptPixelLocation;
+  POINT           ptHimetricLocation;
+  POINT           ptPixelLocationRaw;
+  POINT           ptHimetricLocationRaw;
+  DWORD           dwTime;
+  UINT32          historyCount;
+  INT32           InputData;
+  DWORD           dwKeyStates;
+  UINT64          PerformanceCount;
+  POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
+} POINTER_INFO;
+
+typedef UINT32 TOUCH_FLAGS;
+typedef UINT32 TOUCH_MASK;
+
+typedef struct {
+  POINTER_INFO    pointerInfo;
+  TOUCH_FLAGS     touchFlags;
+  TOUCH_MASK      touchMask;
+  RECT            rcContact;
+  RECT            rcContactRaw;
+  UINT32          orientation;
+  UINT32          pressure;
+} POINTER_TOUCH_INFO;
+
+#define TOUCH_FLAG_NONE                 0x00000000 // Default
+
+#define TOUCH_MASK_NONE                 0x00000000 // Default - none of the optional fields are valid
+#define TOUCH_MASK_CONTACTAREA          0x00000001 // The rcContact field is valid
+#define TOUCH_MASK_ORIENTATION          0x00000002 // The orientation field is valid
+#define TOUCH_MASK_PRESSURE             0x00000004 // The pressure field is valid
+
+#define POINTER_FLAG_NONE               0x00000000 // Default
+#define POINTER_FLAG_NEW                0x00000001 // New pointer
+#define POINTER_FLAG_INRANGE            0x00000002 // Pointer has not departed
+#define POINTER_FLAG_INCONTACT          0x00000004 // Pointer is in contact
+#define POINTER_FLAG_FIRSTBUTTON        0x00000010 // Primary action
+#define POINTER_FLAG_SECONDBUTTON       0x00000020 // Secondary action
+#define POINTER_FLAG_THIRDBUTTON        0x00000040 // Third button
+#define POINTER_FLAG_FOURTHBUTTON       0x00000080 // Fourth button
+#define POINTER_FLAG_FIFTHBUTTON        0x00000100 // Fifth button
+#define POINTER_FLAG_PRIMARY            0x00002000 // Pointer is primary
+#define POINTER_FLAG_CONFIDENCE         0x00004000 // Pointer is considered unlikely to be accidental
+#define POINTER_FLAG_CANCELED           0x00008000 // Pointer is departing in an abnormal manner
+#define POINTER_FLAG_DOWN               0x00010000 // Pointer transitioned to down state (made contact)
+#define POINTER_FLAG_UPDATE             0x00020000 // Pointer update
+#define POINTER_FLAG_UP                 0x00040000 // Pointer transitioned from down state (broke contact)
+#define POINTER_FLAG_WHEEL              0x00080000 // Vertical wheel
+#define POINTER_FLAG_HWHEEL             0x00100000 // Horizontal wheel
+#define POINTER_FLAG_CAPTURECHANGED     0x00200000 // Lost capture
+
+#endif // TOUCH_FEEDBACK_DEFAULT
+
+#define TOUCH_FLAGS_CONTACTUPDATE (POINTER_FLAG_UPDATE|POINTER_FLAG_INRANGE|POINTER_FLAG_INCONTACT)
+#define TOUCH_FLAGS_CONTACTDOWN (POINTER_FLAG_DOWN|POINTER_FLAG_INRANGE|POINTER_FLAG_INCONTACT)
+
+typedef BOOL (WINAPI* InitializeTouchInjectionPtr)(UINT32 maxCount, DWORD dwMode);
+typedef BOOL (WINAPI* InjectTouchInputPtr)(UINT32 count, CONST POINTER_TOUCH_INFO *info);
+
+#endif // touchinjection_sdk80_h
\ No newline at end of file
--- a/widget/windows/winrt/MetroInput.cpp
+++ b/widget/windows/winrt/MetroInput.cpp
@@ -10,27 +10,31 @@
 #include "mozilla/dom/Touch.h"  // Touch
 #include "nsTArray.h" // Touch lists
 #include "nsIDOMSimpleGestureEvent.h" // Constants for gesture events
 #include "InputData.h"
 #include "UIABridgePrivate.h"
 #include "MetroAppShell.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
+#include "WinUtils.h"
+#include "nsIPresShell.h"
+#include "nsEventStateManager.h"
 
 // System headers (alphabetical)
 #include <windows.ui.core.h> // ABI::Window::UI::Core namespace
 #include <windows.ui.input.h> // ABI::Window::UI::Input namespace
 
 //#define DEBUG_INPUT
 
 // Using declarations
 using namespace ABI::Windows; // UI, System, Foundation namespaces
 using namespace Microsoft; // WRL namespace (ComPtr, possibly others)
 using namespace mozilla;
+using namespace mozilla::widget;
 using namespace mozilla::widget::winrt;
 using namespace mozilla::dom;
 
 // File-scoped statics (unnamed namespace)
 namespace {
   // XXX: Set these min values appropriately
   const double SWIPE_MIN_DISTANCE = 5.0;
   const double SWIPE_MIN_VELOCITY = 5.0;
@@ -70,18 +74,18 @@ namespace {
     aPoint->get_Properties(props.GetAddressOf());
     aPoint->get_Position(&position);
     aPoint->get_PointerId(&pointerId);
     props->get_ContactRect(&contactRect);
     props->get_Pressure(&pressure);
 
     nsIntPoint touchPoint = MetroUtils::LogToPhys(position);
     nsIntPoint touchRadius;
-    touchRadius.x = MetroUtils::LogToPhys(contactRect.Width) / 2;
-    touchRadius.y = MetroUtils::LogToPhys(contactRect.Height) / 2;
+    touchRadius.x = WinUtils::LogToPhys(contactRect.Width) / 2;
+    touchRadius.y = WinUtils::LogToPhys(contactRect.Height) / 2;
     return new Touch(pointerId,
                      touchPoint,
                      // Rotation radius and angle.
                      // W3C touch events v1 do not use these.
                      // The draft for W3C touch events v2 explains that
                      // radius and angle should describe the ellipse that
                      // most closely circumscribes the touching area.  Since
                      // Windows gives us a bounding rectangle rather than an
@@ -117,18 +121,18 @@ namespace {
     float pressure;
 
     aPoint->get_Properties(props.GetAddressOf());
     aPoint->get_Position(&position);
     props->get_ContactRect(&contactRect);
     props->get_Pressure(&pressure);
     nsIntPoint touchPoint = MetroUtils::LogToPhys(position);
     nsIntPoint touchRadius;
-    touchRadius.x = MetroUtils::LogToPhys(contactRect.Width) / 2;
-    touchRadius.y = MetroUtils::LogToPhys(contactRect.Height) / 2;
+    touchRadius.x = WinUtils::LogToPhys(contactRect.Width) / 2;
+    touchRadius.y = WinUtils::LogToPhys(contactRect.Height) / 2;
 
     // from Touch.Equals
     return touchPoint != aTouch->mRefPoint ||
            pressure != aTouch->Force() ||
            /* mRotationAngle == aTouch->RotationAngle() || */
            touchRadius.x != aTouch->RadiusX() ||
            touchRadius.y != aTouch->RadiusY();
   }
@@ -959,26 +963,24 @@ MetroInput::HandleTap(const Foundation::
 
   LayoutDeviceIntPoint refPoint;
   bool hitTestChrome = TransformRefPoint(aPoint, refPoint);
   if (!hitTestChrome) {
     // Let APZC handle tap/doubletap detection for content.
     return;
   }
 
-  // send mousemove
   WidgetMouseEvent* mouseEvent =
     new WidgetMouseEvent(true, NS_MOUSE_MOVE, mWidget.Get(),
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   mouseEvent->refPoint = refPoint;
   mouseEvent->clickCount = aTapCount;
   mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   DispatchAsyncEventIgnoreStatus(mouseEvent);
 
-  // Send the mousedown
   mouseEvent =
     new WidgetMouseEvent(true, NS_MOUSE_BUTTON_DOWN, mWidget.Get(),
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   mouseEvent->refPoint = refPoint;
   mouseEvent->clickCount = aTapCount;
   mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   mouseEvent->button = WidgetMouseEvent::buttonType::eLeftButton;
   DispatchAsyncEventIgnoreStatus(mouseEvent);
@@ -986,32 +988,16 @@ MetroInput::HandleTap(const Foundation::
   mouseEvent =
     new WidgetMouseEvent(true, NS_MOUSE_BUTTON_UP, mWidget.Get(),
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   mouseEvent->refPoint = refPoint;
   mouseEvent->clickCount = aTapCount;
   mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   mouseEvent->button = WidgetMouseEvent::buttonType::eLeftButton;
   DispatchAsyncEventIgnoreStatus(mouseEvent);
-
-  // Send one more mousemove to avoid getting a hover state.
-  // In the Metro environment for any application, a tap does not imply a
-  // mouse cursor move.  In desktop environment for any application a tap
-  // does imply a cursor move.
-  POINT point;
-  if (GetCursorPos(&point)) {
-    ScreenToClient((HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW), &point);
-    mouseEvent =
-      new WidgetMouseEvent(true, NS_MOUSE_MOVE, mWidget.Get(),
-                           WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
-    mouseEvent->refPoint = LayoutDeviceIntPoint(point.x, point.y);
-    mouseEvent->clickCount = aTapCount;
-    mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
-    DispatchAsyncEventIgnoreStatus(mouseEvent);
-  }
 }
 
 void
 MetroInput::HandleLongTap(const Foundation::Point& aPoint)
 {
 #ifdef DEBUG_INPUT
   LogFunction();
 #endif
@@ -1041,21 +1027,37 @@ MetroInput::DispatchAsyncEventIgnoreStat
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this, &MetroInput::DeliverNextQueuedEventIgnoreStatus);
   NS_DispatchToCurrentThread(runnable);
 }
 
 void
 MetroInput::DeliverNextQueuedEventIgnoreStatus()
 {
-  WidgetGUIEvent* event =
+  nsAutoPtr<WidgetGUIEvent> event =
     static_cast<WidgetGUIEvent*>(mInputEventQueue.PopFront());
-  MOZ_ASSERT(event);
-  DispatchEventIgnoreStatus(event);
-  delete event;
+  MOZ_ASSERT(event.get());
+  DispatchEventIgnoreStatus(event.get());
+
+  // Clear :hover/:active states for mouse events generated by HandleTap
+  WidgetMouseEvent* mouseEvent = event.get()->AsMouseEvent();
+  if (!mouseEvent) {
+    return;
+  }
+  if (mouseEvent->message != NS_MOUSE_BUTTON_UP ||
+      mouseEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
+    return;
+  }
+  nsCOMPtr<nsIPresShell> presShell = mWidget->GetPresShell();
+  if (presShell) {
+    nsEventStateManager* esm = presShell->GetPresContext()->EventStateManager();
+    if (esm) {
+      esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
+    }
+  }
 }
 
 void
 MetroInput::DispatchAsyncTouchEvent(WidgetTouchEvent* aEvent)
 {
   aEvent->time = ::GetMessageTime();
   mModifierKeyState.Update();
   mModifierKeyState.InitInputEvent(*aEvent);
--- a/widget/windows/winrt/MetroUtils.cpp
+++ b/widget/windows/winrt/MetroUtils.cpp
@@ -28,82 +28,102 @@ using namespace ABI::Windows::UI::Applic
 using namespace mozilla;
 
 using namespace ABI::Windows::Foundation;
 using namespace Microsoft::WRL;
 using namespace Microsoft::WRL::Wrappers;
 using namespace ABI::Windows::UI::ViewManagement;
 using namespace ABI::Windows::Graphics::Display;
 
-// File-scoped statics (unnamed namespace)
-namespace {
-  FLOAT LogToPhysFactor() {
-    ComPtr<IDisplayInformationStatics> dispInfoStatics;
-    if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayInformation).Get(),
-                                       dispInfoStatics.GetAddressOf()))) {
-      ComPtr<IDisplayInformation> dispInfo;
-      if (SUCCEEDED(dispInfoStatics->GetForCurrentView(&dispInfo))) {
-        FLOAT dpi;
-        if (SUCCEEDED(dispInfo->get_LogicalDpi(&dpi))) {
-          return dpi / 96.0f;
-        }
+// Conversion between logical and physical coordinates
+
+double
+MetroUtils::LogToPhysFactor()
+{
+  ComPtr<IDisplayInformationStatics> dispInfoStatics;
+  if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayInformation).Get(),
+                                      dispInfoStatics.GetAddressOf()))) {
+    ComPtr<IDisplayInformation> dispInfo;
+    if (SUCCEEDED(dispInfoStatics->GetForCurrentView(&dispInfo))) {
+      FLOAT dpi;
+      if (SUCCEEDED(dispInfo->get_LogicalDpi(&dpi))) {
+        return (double)dpi / 96.0f;
       }
     }
+  }
 
-    ComPtr<IDisplayPropertiesStatics> dispProps;
-    if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayProperties).Get(),
-                                       dispProps.GetAddressOf()))) {
-      FLOAT dpi;
-      if (SUCCEEDED(dispProps->get_LogicalDpi(&dpi))) {
-        return dpi / 96.0f;
+  ComPtr<IDisplayPropertiesStatics> dispProps;
+  if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayProperties).Get(),
+                                      dispProps.GetAddressOf()))) {
+    FLOAT dpi;
+    if (SUCCEEDED(dispProps->get_LogicalDpi(&dpi))) {
+      return (double)dpi / 96.0f;
+    }
+  }
+
+  return 1.0;
+}
+
+double
+MetroUtils::PhysToLogFactor()
+{
+  return 1.0 / LogToPhysFactor();
+}
+
+double
+MetroUtils::ScaleFactor()
+{
+  // Return the resolution scale factor reported by the metro environment.
+  // XXX TODO: also consider the desktop resolution setting, as IE appears to do?
+  ComPtr<IDisplayInformationStatics> dispInfoStatics;
+  if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayInformation).Get(),
+                                      dispInfoStatics.GetAddressOf()))) {
+    ComPtr<IDisplayInformation> dispInfo;
+    if (SUCCEEDED(dispInfoStatics->GetForCurrentView(&dispInfo))) {
+      ResolutionScale scale;
+      if (SUCCEEDED(dispInfo->get_ResolutionScale(&scale))) {
+        return (double)scale / 100.0;
       }
     }
-
-    return 1.0f;
   }
 
-  FLOAT PhysToLogFactor() {
-    return 1.0f / LogToPhysFactor();
+  ComPtr<IDisplayPropertiesStatics> dispProps;
+  if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayProperties).Get(),
+                                     dispProps.GetAddressOf()))) {
+    ResolutionScale scale;
+    if (SUCCEEDED(dispProps->get_ResolutionScale(&scale))) {
+      return (double)scale / 100.0;
+    }
   }
-};
 
-// Conversion between logical and physical coordinates
-int32_t
-MetroUtils::LogToPhys(FLOAT aValue)
-{
-  return int32_t(NS_round(aValue * LogToPhysFactor()));
+  return 1.0;
 }
 
 nsIntPoint
 MetroUtils::LogToPhys(const Point& aPt)
 {
-  FLOAT factor = LogToPhysFactor();
+  double factor = LogToPhysFactor();
   return nsIntPoint(int32_t(NS_round(aPt.X * factor)), int32_t(NS_round(aPt.Y * factor)));
 }
 
 nsIntRect
 MetroUtils::LogToPhys(const Rect& aRect)
 {
-  FLOAT factor = LogToPhysFactor();
+  double factor = LogToPhysFactor();
   return nsIntRect(int32_t(NS_round(aRect.X * factor)),
                    int32_t(NS_round(aRect.Y * factor)),
                    int32_t(NS_round(aRect.Width * factor)),
                    int32_t(NS_round(aRect.Height * factor)));
 }
 
-FLOAT
-MetroUtils::PhysToLog(int32_t aValue)
-{
-  return FLOAT(aValue) * PhysToLogFactor();
-}
-
 Point
 MetroUtils::PhysToLog(const nsIntPoint& aPt)
 {
-  FLOAT factor = PhysToLogFactor();
+  // Points contain FLOATs
+  FLOAT factor = (FLOAT)PhysToLogFactor();
   Point p = { FLOAT(aPt.x) * factor, FLOAT(aPt.y) * factor };
   return p;
 }
 
 nsresult
 MetroUtils::FireObserver(const char* aMessage, const PRUnichar* aData)
 {
   nsCOMPtr<nsIObserverService> observerService =
--- a/widget/windows/winrt/MetroUtils.h
+++ b/widget/windows/winrt/MetroUtils.h
@@ -68,23 +68,25 @@ class MetroUtils
   typedef Microsoft::WRL::Wrappers::HString HString;
   typedef ABI::Windows::UI::ViewManagement::ApplicationViewState ApplicationViewState;
   typedef ABI::Windows::Foundation::Point Point;
   typedef ABI::Windows::Foundation::Rect Rect;
 
 public:
   // Functions to convert between logical pixels as used by most Windows APIs
   // and physical (device) pixels.
-  // See MSDN documentation about DIPs (device independent pixels) for details.
-  static int32_t LogToPhys(FLOAT aValue);
+  static double LogToPhysFactor();
+  static double PhysToLogFactor();
   static nsIntPoint LogToPhys(const Point& aPt);
   static nsIntRect LogToPhys(const Rect& aRect);
-  static FLOAT PhysToLog(int32_t aValue);
   static Point PhysToLog(const nsIntPoint& aPt);
 
+  // Resolution scale factor
+  static double ScaleFactor();
+
   static nsresult FireObserver(const char* aMessage, const PRUnichar* aData = nullptr);
 
   static HRESULT CreateUri(HSTRING aUriStr, Microsoft::WRL::ComPtr<IUriRuntimeClass>& aUriOut);
   static HRESULT CreateUri(HString& aHString, Microsoft::WRL::ComPtr<IUriRuntimeClass>& aUriOut);
   static HRESULT GetViewState(ApplicationViewState& aState);
   static HRESULT TryUnsnap(bool* aResult = nullptr);
   static HRESULT ShowSettingsFlyout();
 
--- a/widget/windows/winrt/MetroWidget.cpp
+++ b/widget/windows/winrt/MetroWidget.cpp
@@ -1326,72 +1326,61 @@ MetroWidget::GetAccessible()
   // If the pref was true, return null here, disabling a11y.
   if (accForceDisable)
       return nullptr;
 
   return GetRootAccessible();
 }
 #endif
 
-double MetroWidget::GetDefaultScaleInternal()
+double
+MetroWidget::GetDefaultScaleInternal()
 {
-  // Return the resolution scale factor reported by the metro environment.
-  // XXX TODO: also consider the desktop resolution setting, as IE appears to do?
-
-  ComPtr<IDisplayInformationStatics> dispInfoStatics;
-  if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayInformation).Get(),
-                                      dispInfoStatics.GetAddressOf()))) {
-    ComPtr<IDisplayInformation> dispInfo;
-    if (SUCCEEDED(dispInfoStatics->GetForCurrentView(&dispInfo))) {
-      ResolutionScale scale;
-      if (SUCCEEDED(dispInfo->get_ResolutionScale(&scale))) {
-        return (double)scale / 100.0;
-      }
-    }
-  }
-
-  ComPtr<IDisplayPropertiesStatics> dispProps;
-  if (SUCCEEDED(GetActivationFactory(HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayProperties).Get(),
-                                     dispProps.GetAddressOf()))) {
-    ResolutionScale scale;
-    if (SUCCEEDED(dispProps->get_ResolutionScale(&scale))) {
-      return (double)scale / 100.0;
-    }
-  }
-
-  return 1.0;
+  return MetroUtils::ScaleFactor();
 }
 
 LayoutDeviceIntPoint
 MetroWidget::CSSIntPointToLayoutDeviceIntPoint(const CSSIntPoint &aCSSPoint)
 {
   CSSToLayoutDeviceScale scale = GetDefaultScale();
   LayoutDeviceIntPoint devPx(int32_t(NS_round(scale.scale * aCSSPoint.x)),
                              int32_t(NS_round(scale.scale * aCSSPoint.y)));
   return devPx;
 }
 
-float MetroWidget::GetDPI()
+float
+MetroWidget::GetDPI()
 {
   if (!mView) {
     return 96.0;
   }
   return mView->GetDPI();
 }
 
-void MetroWidget::ChangedDPI()
+void
+MetroWidget::ChangedDPI()
 {
   if (mWidgetListener) {
     nsIPresShell* presShell = mWidgetListener->GetPresShell();
     if (presShell) {
       presShell->BackingScaleFactorChanged();
     }
   }
 }
 
+already_AddRefed<nsIPresShell>
+MetroWidget::GetPresShell()
+{
+  if (mWidgetListener) {
+    nsCOMPtr<nsIPresShell> ps = mWidgetListener->GetPresShell();
+    return ps.forget();
+  }
+  return nullptr;
+}
+
 NS_IMETHODIMP
 MetroWidget::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY)
 {
   return NS_OK;
 }
 
 void
 MetroWidget::SizeModeChanged()
--- a/widget/windows/winrt/MetroWidget.h
+++ b/widget/windows/winrt/MetroWidget.h
@@ -170,16 +170,18 @@ public:
   void Paint(const nsIntRegion& aInvalidRegion); 
 
   MetroWidget* MetroWidget::GetTopLevelWindow(bool aStopOnDialogOrPopup) { return this; }
   virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations);
   virtual void* GetNativeData(uint32_t aDataType);
   virtual void  FreeNativeData(void * data, uint32_t aDataType);
   virtual nsIntPoint WidgetToScreenOffset();
 
+  already_AddRefed<nsIPresShell> GetPresShell();
+
   void UserActivity();
 
 #ifdef ACCESSIBILITY
   mozilla::a11y::Accessible* DispatchAccessibleEvent(uint32_t aEventType);
   mozilla::a11y::Accessible* GetAccessible();
 #endif // ACCESSIBILITY
 
   // needed for current nsIFilePicker
--- a/widget/windows/winrt/UIABridge.cpp
+++ b/widget/windows/winrt/UIABridge.cpp
@@ -310,20 +310,20 @@ UIABridge::get_BoundingRectangle(UiaRect
     return UIA_E_ELEMENTNOTAVAILABLE;
   }
 
   // returns logical pixels
   Rect bounds;
   mWindow->get_Bounds(&bounds);
 
   // we need to return physical pixels
-  retVal->left = MetroUtils::LogToPhys(bounds.X);
-  retVal->top = MetroUtils::LogToPhys(bounds.Y);
-  retVal->width = MetroUtils::LogToPhys(bounds.Width);
-  retVal->height = MetroUtils::LogToPhys(bounds.Height);
+  retVal->left = WinUtils::LogToPhys(bounds.X);
+  retVal->top = WinUtils::LogToPhys(bounds.Y);
+  retVal->width = WinUtils::LogToPhys(bounds.Width);
+  retVal->height = WinUtils::LogToPhys(bounds.Height);
 
   return S_OK;
 }
 
 HRESULT
 UIABridge::GetEmbeddedFragmentRoots(SAFEARRAY ** retVal)
 {
   if (!Connected()) {
--- a/widget/xpwidgets/nsBaseWidget.cpp
+++ b/widget/xpwidgets/nsBaseWidget.cpp
@@ -65,16 +65,21 @@ using namespace mozilla::ipc;
 using namespace mozilla;
 using base::Thread;
 
 nsIContent* nsBaseWidget::mLastRollup = nullptr;
 // Global user preference for disabling native theme. Used
 // in NativeWindowTheme.
 bool            gDisableNativeTheme               = false;
 
+// Async pump timer during injected long touch taps
+#define TOUCH_INJECT_PUMP_TIMER_MSEC 50
+#define TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC 1500
+int32_t nsIWidget::sPointerIdCounter = 0;
+
 // nsBaseWidget
 NS_IMPL_ISUPPORTS1(nsBaseWidget, nsIWidget)
 
 
 nsAutoRollup::nsAutoRollup()
 {
   // remember if mLastRollup was null, and only clear it upon destruction
   // if so. This prevents recursive usage of nsAutoRollup from clearing
@@ -1521,17 +1526,113 @@ nsBaseWidget::GetRootAccessible()
     services::GetAccessibilityService();
   if (accService) {
     return accService->GetRootDocumentAccessible(presShell, nsContentUtils::IsSafeToRunScript());
   }
 
   return nullptr;
 }
 
+#endif // ACCESSIBILITY
+
+nsresult
+nsIWidget::SynthesizeNativeTouchTap(nsIntPoint aPointerScreenPoint, bool aLongTap)
+{
+  if (sPointerIdCounter > TOUCH_INJECT_MAX_POINTS) {
+    sPointerIdCounter = 0;
+  }
+  int pointerId = sPointerIdCounter;
+  sPointerIdCounter++;
+  nsresult rv = SynthesizeNativeTouchPoint(pointerId, TOUCH_CONTACT,
+                                           aPointerScreenPoint, 1.0, 90);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (!aLongTap) {
+    nsresult rv = SynthesizeNativeTouchPoint(pointerId, TOUCH_REMOVE,
+                                             aPointerScreenPoint, 0, 0);
+    return rv;
+  }
+
+  // initiate a long tap
+  int elapse = Preferences::GetInt("ui.click_hold_context_menus.delay",
+                                   TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC);
+  if (!mLongTapTimer) {
+    mLongTapTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) {
+      SynthesizeNativeTouchPoint(pointerId, TOUCH_CANCEL,
+                                 aPointerScreenPoint, 0, 0);
+      return NS_ERROR_UNEXPECTED;
+    }
+    // Windows requires recuring events, so we set this to a smaller window
+    // than the pref value.
+    int timeout = elapse;
+    if (timeout > TOUCH_INJECT_PUMP_TIMER_MSEC) {
+      timeout = TOUCH_INJECT_PUMP_TIMER_MSEC;
+    }
+    mLongTapTimer->InitWithFuncCallback(OnLongTapTimerCallback, this,
+                                        timeout,
+                                        nsITimer::TYPE_REPEATING_SLACK);
+  }
+
+  // If we already have a long tap pending, cancel it. We only allow one long
+  // tap to be active at a time.
+  if (mLongTapTouchPoint) {
+    SynthesizeNativeTouchPoint(mLongTapTouchPoint->mPointerId, TOUCH_CANCEL,
+                               mLongTapTouchPoint->mPosition, 0, 0);
+  }
+
+  mLongTapTouchPoint = new LongTapInfo(pointerId, aPointerScreenPoint,
+                                       TimeDuration::FromMilliseconds(elapse));
+  return NS_OK;
+}
+
+// static
+void
+nsIWidget::OnLongTapTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+  nsIWidget *self = static_cast<nsIWidget *>(aClosure);
+
+  if ((self->mLongTapTouchPoint->mStamp + self->mLongTapTouchPoint->mDuration) >
+      TimeStamp::Now()) {
+#ifdef XP_WIN
+    // Windows needs us to keep pumping feedback to the digitizer, so update
+    // the pointer id with the same position.
+    self->SynthesizeNativeTouchPoint(self->mLongTapTouchPoint->mPointerId,
+                                     TOUCH_CONTACT,
+                                     self->mLongTapTouchPoint->mPosition,
+                                     1.0, 90);
 #endif
+    return;
+  }
+
+  // finished, remove the touch point
+  self->mLongTapTimer->Cancel();
+  self->mLongTapTimer = nullptr;
+  self->SynthesizeNativeTouchPoint(self->mLongTapTouchPoint->mPointerId,
+                                   TOUCH_REMOVE,
+                                   self->mLongTapTouchPoint->mPosition,
+                                   0, 0);
+  self->mLongTapTouchPoint = nullptr;
+}
+
+nsresult
+nsIWidget::ClearNativeTouchSequence()
+{
+  if (!mLongTapTimer) {
+    return NS_OK;
+  }
+  mLongTapTimer->Cancel();
+  mLongTapTimer = nullptr;
+  SynthesizeNativeTouchPoint(mLongTapTouchPoint->mPointerId, TOUCH_CANCEL,
+                             mLongTapTouchPoint->mPosition, 0, 0);
+  mLongTapTouchPoint = nullptr;
+  return NS_OK;
+}
 
 #ifdef DEBUG
 //////////////////////////////////////////////////////////////
 //
 // Convert a GUI event message code to a string.
 // Makes it a lot easier to debug events.
 //
 // See gtk/nsWidget.cpp and windows/nsWindow.cpp
--- a/widget/xpwidgets/nsBaseWidget.h
+++ b/widget/xpwidgets/nsBaseWidget.h
@@ -36,16 +36,21 @@ class CompositorChild;
 class CompositorParent;
 }
 }
 
 namespace base {
 class Thread;
 }
 
+// Windows specific constant indicating the maximum number of touch points the
+// inject api will allow. This also sets the maximum numerical value for touch
+// ids we can use when injecting touch points on Windows.
+#define TOUCH_INJECT_MAX_POINTS 256
+
 class nsBaseWidget;
 
 class WidgetShutdownObserver MOZ_FINAL : public nsIObserver
 {
 public:
   WidgetShutdownObserver(nsBaseWidget* aWidget)
     : mWidget(aWidget)
   { }
@@ -315,16 +320,24 @@ protected:
                                                     uint32_t aNativeMessage,
                                                     double aDeltaX,
                                                     double aDeltaY,
                                                     double aDeltaZ,
                                                     uint32_t aModifierFlags,
                                                     uint32_t aAdditionalFlags)
   { return NS_ERROR_UNEXPECTED; }
 
+  virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+                                              TouchPointerState aPointerState,
+                                              nsIntPoint aPointerScreenPoint,
+                                              double aPointerPressure,
+                                              uint32_t aPointerOrientation)
+  { return NS_ERROR_UNEXPECTED; }
+
+protected:
   // Stores the clip rectangles in aRects into mClipRects. Returns true
   // if the new rectangles are different from the old rectangles.
   bool StoreWindowClipRegion(const nsTArray<nsIntRect>& aRects);
 
   virtual already_AddRefed<nsIWidget>
   AllocateChildPopupWidget()
   {
     static NS_DEFINE_IID(kCPopUpCID, NS_CHILD_CID);