Merge m-c to m-i

This commit is contained in:
Phil Ringnalda 2013-12-14 18:37:06 -08:00
commit c6cca52a67
58 changed files with 1420 additions and 571 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1386360478000">
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1386891443000">
<emItems>
<emItem blockID="i454" id="sqlmoz@facebook.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
@ -106,6 +106,10 @@
<versionRange minVersion="3.4.1" maxVersion="3.4.1.194" severity="1">
</versionRange>
</emItem>
<emItem blockID="i506" id="ext@bettersurfplus.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i100" id="{394DCBA4-1F92-4f8e-8EC9-8D2CB90CB69B}">
<versionRange minVersion="2.5.0" maxVersion="2.5.0" severity="1">
</versionRange>
@ -453,6 +457,10 @@
<versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange>
</emItem>
<emItem blockID="i507" id="4zffxtbr-bs@VideoDownloadConverter_4z.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i7" id="{2224e955-00e9-4613-a844-ce69fccaae91}">
</emItem>
<emItem blockID="i485" id="/^brasilescape.*\@facebook\.com$//">
@ -693,6 +701,10 @@
<versionRange minVersion="0" maxVersion="15.0.5" severity="1">
</versionRange>
</emItem>
<emItem blockID="i505" id="extacylife@a.com">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i15" id="personas@christopher.beard">
<versionRange minVersion="1.6" maxVersion="1.6">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">

View File

@ -6101,33 +6101,16 @@ function undoCloseTab(aIndex) {
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;
}
let tab = null;
while (numberOfTabsToUndoClose > 0 &&
numberOfTabsToUndoClose--) {
var tab = null;
if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) {
TabView.prepareUndoCloseTab(blankTabToRemove);
tab = SessionStore.undoCloseTab(window, index);
tab = SessionStore.undoCloseTab(window, aIndex || 0);
TabView.afterUndoCloseTab();
if (blankTabToRemove) {
if (blankTabToRemove)
gBrowser.removeTab(blankTabToRemove);
blankTabToRemove = null;
}
}
// Reset the number of tabs closed last time to the default.
SessionStore.setNumberOfTabsClosedLast(window, 1);
return tab;
}
@ -6950,13 +6933,8 @@ var TabContextMenu = {
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;

View File

@ -115,8 +115,7 @@
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;"

View File

@ -1679,26 +1679,13 @@
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;
if (tabsToClose <= 1)
return true;
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)
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;
@ -1722,16 +1709,14 @@
+ (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;
]]>
@ -1758,11 +1743,9 @@
<![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>
@ -1779,14 +1762,10 @@
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>
@ -1815,8 +1794,6 @@
var byMouse = aParams.byMouse;
}
SessionStore.setNumberOfTabsClosedLast(window, 1);
// Handle requests for synchronously removing an already
// asynchronously closing tab.
if (!animate &&

View File

@ -229,8 +229,6 @@ run-if = toolkit == "cocoa"
[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]

View File

@ -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);
}

View File

@ -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) {}
}

View File

@ -212,7 +212,8 @@ function test1C() {
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";
@ -269,7 +270,8 @@ function test2C() {
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
@ -336,7 +338,8 @@ function test3E() {
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";
@ -403,7 +406,8 @@ function test4E() {
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
@ -462,7 +466,8 @@ function test5C() {
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

View File

@ -25,7 +25,7 @@ interface nsIDOMNode;
* |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
{
/**
@ -100,20 +100,6 @@ interface nsISessionStore : nsISupports
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
*/

View File

@ -66,7 +66,7 @@ this.RecentlyClosedTabsAndWindowsMenuUtils = {
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;
},

View File

@ -193,14 +193,6 @@ this.SessionStore = {
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);
},
@ -1616,35 +1608,6 @@ let SessionStoreInternal = {
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;
@ -4029,11 +3992,6 @@ let DirtyWindows = {
}
};
// 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

View File

@ -40,8 +40,4 @@ function test() {
"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");
}

View File

@ -38,5 +38,5 @@ function onTabViewWindowLoaded() {
gBrowser.removeTab(tabTwo);
finish();
});
}, 0);
});
}

View File

@ -64,7 +64,7 @@ function test() {
createBlankTab();
afterAllTabsLoaded(testUndoCloseWithSelectedBlankPinnedTab);
}, 0);
});
});
}
@ -94,7 +94,7 @@ function test() {
gBrowser.removeTab(gBrowser.tabs[0]);
afterAllTabsLoaded(finishTest);
}, 0);
});
});
}

View File

@ -81,7 +81,7 @@ function test() {
gBrowser.removeTab(gBrowser.tabs[1]);
gBrowser.removeTab(gBrowser.tabs[1]);
hideTabView(finishTest);
}, 0);
});
}
waitForExplicitFinish();

View File

@ -20,7 +20,7 @@ function test() {
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);

View File

@ -362,7 +362,7 @@ function newWindowWithState(state, callback) {
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 () {

View File

@ -1357,7 +1357,11 @@ VariableBubbleView.prototype = {
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) {

View File

@ -52,6 +52,7 @@ support-files =
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
@ -222,6 +223,7 @@ support-files =
[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]

View File

@ -33,44 +33,44 @@ function test() {
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();

View File

@ -43,11 +43,11 @@ function test() {
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();

View File

@ -32,11 +32,11 @@ function test() {
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();

View File

@ -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);
});
}

View File

@ -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>

View File

@ -132,15 +132,15 @@ 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);
},
/**
@ -161,12 +161,19 @@ SyntaxTreesPool.prototype = {
* 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;
},
/**
@ -182,23 +189,31 @@ SyntaxTreesPool.prototype = {
},
/**
* 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,

View File

@ -49,11 +49,6 @@ can reach it easily. -->
<!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">

View File

@ -288,7 +288,7 @@ var ContextCommands = {
viewPageSource: function cc_viewPageSource() {
let uri = this.getPageSource();
if (uri) {
BrowserUI.addAndShowTab(uri);
BrowserUI.addAndShowTab(uri, Browser.selectedTab);
}
},

View File

@ -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();
}
});

View File

@ -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();
}
});

View File

@ -557,8 +557,11 @@ function waitForObserver(aObsEvent, aTimeoutMs) {
}
/*=============================================================================
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;
@ -640,6 +643,36 @@ function synthesizeNativeMouseMUp(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
@ -784,6 +817,17 @@ TouchDragAndHold.prototype = {
_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)
@ -795,8 +839,14 @@ TouchDragAndHold.prototype = {
}
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;
}
@ -805,8 +855,16 @@ TouchDragAndHold.prototype = {
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);
},
@ -814,13 +872,20 @@ TouchDragAndHold.prototype = {
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;
@ -847,8 +912,14 @@ TouchDragAndHold.prototype = {
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;
},
};

View File

@ -24,6 +24,7 @@ support-files =
helpers/ViewStateHelper.js
res/image01.png
res/textblock01.html
res/textdivs01.html
res/textinput01.html
res/textarea01.html
res/testEngine.xml
@ -55,6 +56,8 @@ support-files =
[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]

View File

@ -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>

View File

@ -1149,6 +1149,63 @@ nsDOMWindowUtils::SendNativeMouseScrollEvent(int32_t aScreenX,
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)
{

View File

@ -43,7 +43,7 @@ interface nsIDOMEventTarget;
interface nsIRunnable;
interface nsICompositionStringSynthesizer;
[scriptable, uuid(3d9bf70c-5b83-11e3-9090-3c970e9f4238)]
[scriptable, uuid(38740b7e-095e-4198-a012-cf5f9e102a6a)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -442,6 +442,28 @@ interface nsIDOMWindowUtils : nsISupports {
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
*
@ -466,26 +488,79 @@ interface nsIDOMWindowUtils : nsISupports {
in nsIDOMElement aElement);
/**
* The values of aAdditionalFlags.
* Touch states for sendNativeTouchPoint. These values match
* nsIWidget's TouchPointerState.
*/
/**
* 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 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;
/**
* The platform specific values of aAdditionalFlags. Must be over 0x00010000.
* 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.
*/
void sendNativeTouchPoint(in unsigned long aPointerId,
in unsigned long aTouchState,
in long aScreenX,
in long aScreenY,
in double aPressure,
in unsigned long aOrientation);
/**
* 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.
* 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.
*/
const unsigned long MOUSESCROLL_WIN_SCROLL_LPARAM_NOT_NULL = 0x00010000;
void sendNativeTouchTap(in long aScreenX,
in long aScreenY,
in boolean aLongTap);
/**
* 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.
*/
void clearNativeTouchSequence();
/**
* See nsIWidget::ActivateNativeMenuItemAt

View File

@ -33,6 +33,8 @@
#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"
@ -46,10 +48,6 @@
#include <string>
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
#ifdef CAIRO_HAS_D2D_SURFACE
#include "gfxD2DSurface.h"
@ -67,6 +65,9 @@ using namespace mozilla::layers;
#include "d3dkmtQueryStatistics.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::widget;
#ifdef CAIRO_HAS_D2D_SURFACE
@ -363,8 +364,6 @@ gfxWindowsPlatform::gfxWindowsPlatform()
*/
CoInitialize(nullptr);
mScreenDC = GetDC(nullptr);
#ifdef CAIRO_HAS_D2D_SURFACE
RegisterStrongMemoryReporter(new GfxD2DSurfaceCacheReporter());
RegisterStrongMemoryReporter(new GfxD2DSurfaceVramReporter());
@ -384,7 +383,6 @@ 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
@ -401,6 +399,12 @@ gfxWindowsPlatform::~gfxWindowsPlatform()
CoUninitialize();
}
double
gfxWindowsPlatform::GetDPIScale()
{
return WinUtils::LogToPhysFactor();
}
void
gfxWindowsPlatform::UpdateRenderMode()
{

View File

@ -175,16 +175,12 @@ public:
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,
@ -274,7 +270,6 @@ protected:
int8_t mUseClearTypeForDownloadableFonts;
int8_t mUseClearTypeAlways;
HDC mScreenDC;
private:
void Init();

View File

@ -20,7 +20,7 @@
<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"/>

View File

@ -126,7 +126,6 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
DeviceHelper.init(this);
GeckoHelper.init(this);
GestureHelper.init(this);
NavigationHelper.init(this);
WaitHelper.init(this);
}

View File

@ -41,6 +41,10 @@ public class AboutHomeComponent extends BaseComponent {
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);
}
@ -70,35 +74,33 @@ public class AboutHomeComponent extends BaseComponent {
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();
}
final PagerAdapter adapter = getHomePagerView().getAdapter();
assertNotNull("The HomePager's PagerAdapter is not null", adapter);
mSolo.scrollViewToSide(getHomePagerView(), pageDirection, SWIPE_PERCENTAGE);
// 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);
}

View File

@ -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);
}
}

View File

@ -47,6 +47,7 @@ groups.google.com: did not receive HSTS header
history.google.com: did not receive HSTS header
hostedtalkgadget.google.com: did not receive HSTS header
id.atlassian.com: did not receive HSTS header
in.xero.com: max-age too low: 3600
iop.intuit.com: max-age too low: 86400
irccloud.com: did not receive HSTS header
jitsi.org: did not receive HSTS header
@ -68,6 +69,7 @@ openshift.redhat.com: did not receive HSTS header
ottospora.nl: could not connect to host
packagist.org: max-age too low: 2592000
paypal.com: max-age too low: 14400
payroll.xero.com: max-age too low: 3600
platform.lookout.com: could not connect to host
play.google.com: did not receive HSTS header
plus.google.com: did not receive HSTS header

View File

@ -8,7 +8,7 @@
/*****************************************************************************/
#include <stdint.h>
const PRTime gPreloadListExpirationTime = INT64_C(1397301006735000);
const PRTime gPreloadListExpirationTime = INT64_C(1397905536640000);
class nsSTSPreload
{
@ -21,6 +21,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "aladdinschools.appspot.com", false },
{ "alpha.irccloud.com", false },
{ "api.intercom.io", false },
{ "api.xero.com", false },
{ "app.recurly.com", false },
{ "appseccalifornia.org", true },
{ "arivo.com.br", true },
@ -49,6 +50,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "cupcake.is", true },
{ "cybozu.com", true },
{ "cyphertite.com", true },
{ "data.qld.gov.au", false },
{ "davidlyness.com", true },
{ "developer.mydigipass.com", false },
{ "dist.torproject.org", false },
@ -57,6 +59,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "download.jitsi.org", false },
{ "ebanking.indovinabank.com.vn", false },
{ "ecosystem.atlassian.net", true },
{ "eff.org", true },
{ "entropia.de", false },
{ "errors.zenpayroll.com", false },
{ "espra.com", true },
@ -66,6 +69,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "forum.quantifiedself.com", true },
{ "gernert-server.de", true },
{ "getlantern.org", true },
{ "go.xero.com", false },
{ "grc.com", false },
{ "haste.ch", true },
{ "howrandom.org", true },
@ -83,11 +87,13 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "lockify.com", true },
{ "login.persona.org", true },
{ "login.sapo.pt", true },
{ "login.xero.com", false },
{ "logotype.se", true },
{ "lolicore.ch", true },
{ "lookout.com", false },
{ "lumi.do", false },
{ "luneta.nearbuysystems.com", false },
{ "mail.de", true },
{ "makeyourlaws.org", false },
{ "manage.zenpayroll.com", false },
{ "manager.linode.com", false },
@ -98,6 +104,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "members.nearlyfreespeech.net", false },
{ "mudcrab.us", true },
{ "my.onlime.ch", false },
{ "my.xero.com", false },
{ "mylookout.com", false },
{ "neg9.org", false },
{ "oplop.appspot.com", true },
@ -110,6 +117,7 @@ static const nsSTSPreload kSTSPreloadList[] = {
{ "paymill.de", false },
{ "piratenlogin.de", true },
{ "pixi.me", true },
{ "publications.qld.gov.au", false },
{ "riseup.net", true },
{ "roundcube.mayfirst.org", false },
{ "sandbox.mydigipass.com", false },

View File

@ -12,12 +12,15 @@
#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
@ -97,8 +100,8 @@ typedef void* nsNativeWidget;
#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
@ -493,7 +496,9 @@ class nsIWidget : public nsISupports {
: mLastChild(nullptr)
, mPrevSibling(nullptr)
, mOnDestroyCalled(false)
{}
{
ClearNativeTouchSequence();
}
/**
@ -1559,6 +1564,85 @@ class nsIWidget : public nsISupports {
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

View File

@ -37,6 +37,7 @@
#include "gfxColor.h"
#ifdef MOZ_METRO
#include "winrt/MetroInput.h"
#include "winrt/MetroUtils.h"
#endif // MOZ_METRO
#ifdef NS_ENABLE_TSF
@ -185,6 +186,47 @@ WinUtils::Log(const char *fmt, ...)
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,

View File

@ -69,6 +69,15 @@ public:
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.

View File

@ -2430,7 +2430,7 @@ nsNativeThemeWin::GetMinimumWidgetSize(nsRenderingContext* aContext, nsIFrame* a
if (NS_FAILED(rv))
return rv;
HDC hdc = gfxWindowsPlatform::GetPlatform()->GetScreenDC();
HDC hdc = ::GetDC(nullptr);
if (!hdc)
return NS_ERROR_FAILURE;
@ -2448,12 +2448,13 @@ nsNativeThemeWin::GetMinimumWidgetSize(nsRenderingContext* aContext, nsIFrame* a
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;
}

View File

@ -979,7 +979,7 @@ float nsWindow::GetDPI()
double nsWindow::GetDefaultScaleInternal()
{
return gfxWindowsPlatform::GetPlatform()->GetDPIScale();
return WinUtils::LogToPhysFactor();
}
nsWindow*

View File

@ -6,9 +6,16 @@
#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)
@ -27,3 +34,164 @@ nsWindowBase::DispatchPluginEvent(const MSG& aMsg)
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;
}

View File

@ -8,7 +8,10 @@
#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
@ -75,6 +78,42 @@ public:
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;
};

View File

@ -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

View File

@ -15,6 +15,9 @@
#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
@ -26,6 +29,7 @@
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;
@ -75,8 +79,8 @@ namespace {
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.
@ -122,8 +126,8 @@ namespace {
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 ||
@ -964,7 +968,6 @@ MetroInput::HandleTap(const Foundation::Point& aPoint, unsigned int aTapCount)
return;
}
// send mousemove
WidgetMouseEvent* mouseEvent =
new WidgetMouseEvent(true, NS_MOUSE_MOVE, mWidget.Get(),
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
@ -973,7 +976,6 @@ MetroInput::HandleTap(const Foundation::Point& aPoint, unsigned int 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);
@ -991,22 +993,6 @@ MetroInput::HandleTap(const Foundation::Point& aPoint, unsigned int 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
@ -1046,11 +1032,27 @@ MetroInput::DispatchAsyncEventIgnoreStatus(WidgetInputEvent* aEvent)
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

View File

@ -33,72 +33,92 @@ 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;
}
}
}
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;
}
}
return 1.0f;
}
FLOAT PhysToLogFactor() {
return 1.0f / LogToPhysFactor();
}
};
// Conversion between logical and physical coordinates
int32_t
MetroUtils::LogToPhys(FLOAT aValue)
double
MetroUtils::LogToPhysFactor()
{
return int32_t(NS_round(aValue * 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 (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;
}
}
}
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;
}
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;
}

View File

@ -73,13 +73,15 @@ class MetroUtils
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);

View File

@ -1331,33 +1331,10 @@ MetroWidget::GetAccessible()
}
#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
@ -1369,7 +1346,8 @@ MetroWidget::CSSIntPointToLayoutDeviceIntPoint(const CSSIntPoint &aCSSPoint)
return devPx;
}
float MetroWidget::GetDPI()
float
MetroWidget::GetDPI()
{
if (!mView) {
return 96.0;
@ -1377,7 +1355,8 @@ float MetroWidget::GetDPI()
return mView->GetDPI();
}
void MetroWidget::ChangedDPI()
void
MetroWidget::ChangedDPI()
{
if (mWidgetListener) {
nsIPresShell* presShell = mWidgetListener->GetPresShell();
@ -1387,6 +1366,16 @@ void MetroWidget::ChangedDPI()
}
}
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)
{

View File

@ -175,6 +175,8 @@ public:
virtual void FreeNativeData(void * data, uint32_t aDataType);
virtual nsIntPoint WidgetToScreenOffset();
already_AddRefed<nsIPresShell> GetPresShell();
void UserActivity();
#ifdef ACCESSIBILITY

View File

@ -315,10 +315,10 @@ UIABridge::get_BoundingRectangle(UiaRect * retVal)
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;
}

View File

@ -70,6 +70,11 @@ nsIContent* nsBaseWidget::mLastRollup = nullptr;
// 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)
@ -1526,7 +1531,103 @@ nsBaseWidget::GetRootAccessible()
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
//////////////////////////////////////////////////////////////

View File

@ -41,6 +41,11 @@ 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
@ -320,6 +325,14 @@ protected:
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);