Merge f-t to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 14 Dec 2013 18:21:57 -0800
changeset 160528 c049cb230d77f7b68dc38175a01e7fcf0f6c8bb2
parent 160511 9fcc6330dc693305f97c942c718b7bcb5b94dde1 (current diff)
parent 160527 828bbb5114bb1cccc1ad476e7840c43d55c05880 (diff)
child 160535 6ccc188b79a4d373d58a02e16e95ff1e847cc1ae
child 160560 1b466d2cbf558001012f9870bcde02c1878b790b
child 160574 1fbae0f606dc5f396f5b9299b19e33483df9c9bd
push id25835
push userphilringnalda@gmail.com
push dateSun, 15 Dec 2013 02:22:09 +0000
treeherdermozilla-central@c049cb230d77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
c049cb230d77 / 29.0a1 / 20131215030202 / files
nightly linux64
c049cb230d77 / 29.0a1 / 20131215030202 / files
nightly mac
c049cb230d77 / 29.0a1 / 20131215030202 / files
nightly win32
c049cb230d77 / 29.0a1 / 20131215030202 / files
nightly win64
c049cb230d77 / 29.0a1 / 20131215030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge f-t to m-c
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/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/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);