Bug 1251809 - Add input[type=file] tooltip support for e10s. r=ehsan

MozReview-Commit-ID: FpwKGrFQNrK
This commit is contained in:
Jared Wein 2016-03-16 19:07:51 -04:00
parent 5b87f86e00
commit 9b52f45b30
5 changed files with 164 additions and 38 deletions

View File

@ -5,7 +5,7 @@
#include "nsISupports.idl"
[uuid(57128a85-34de-42db-a252-84dd57724a59)]
[builtinclass, uuid(57128a85-34de-42db-a252-84dd57724a59)]
interface nsIDOMFileList : nsISupports
{
readonly attribute unsigned long length;

View File

@ -35,6 +35,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/SVGTitleElement.h"
#include "nsIDOMEvent.h"
#include "nsIDOMFileList.h"
#include "nsIDOMMouseEvent.h"
#include "nsIFormControl.h"
#include "nsIDOMHTMLInputElement.h"
@ -48,6 +49,7 @@
#include "nsIWebNavigation.h"
#include "nsIDOMHTMLElement.h"
#include "nsIPresShell.h"
#include "nsIStringBundle.h"
#include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h"
#include "nsIDOMWindowCollection.h"
@ -68,6 +70,8 @@
#include "mozilla/Attributes.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
#include "mozilla/dom/File.h" // for input type=file
#include "mozilla/dom/FileList.h" // for input type=file
using namespace mozilla;
using namespace mozilla::dom;
@ -1071,9 +1075,7 @@ public:
protected:
~DefaultTooltipTextProvider() {}
nsCOMPtr<nsIAtom> mTag_dialog;
nsCOMPtr<nsIAtom> mTag_dialogheader;
nsCOMPtr<nsIAtom> mTag_window;
nsCOMPtr<nsIAtom> mTag_dialogHeader;
};
NS_IMPL_ISUPPORTS(DefaultTooltipTextProvider, nsITooltipTextProvider)
@ -1082,9 +1084,7 @@ DefaultTooltipTextProvider::DefaultTooltipTextProvider()
{
// There are certain element types which we don't want to use
// as tool tip text.
mTag_dialog = do_GetAtom("dialog");
mTag_dialogheader = do_GetAtom("dialogheader");
mTag_window = do_GetAtom("window");
mTag_dialogHeader = do_GetAtom("dialogheader");
}
// A helper routine that determines whether we're still interested in SVG
@ -1142,13 +1142,66 @@ DefaultTooltipTextProvider::GetNodeText(nsIDOMNode* aNode, char16_t** aText,
if (currElement) {
nsCOMPtr<nsIContent> content(do_QueryInterface(currElement));
if (content) {
if (!content->IsAnyOfXULElements(mTag_dialog,
mTag_dialogheader,
mTag_window)) {
if (!content->IsAnyOfXULElements(nsGkAtoms::dialog,
mTag_dialogHeader,
nsGkAtoms::window)) {
// first try the normal title attribute...
if (!content->IsSVGElement()) {
currElement->GetAttribute(NS_LITERAL_STRING("title"), outText);
if (outText.Length()) {
// If the element is an <input type="file"> without a title,
// we should show the current file selection.
if (content->IsHTMLElement(nsGkAtoms::input) &&
content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
NS_LITERAL_STRING("file"), eIgnoreCase) &&
!content->HasAttr(kNameSpaceID_None, nsGkAtoms::title)) {
found = true;
nsCOMPtr<nsIDOMFileList> fileList;
nsCOMPtr<nsIDOMHTMLInputElement> currInputElement(do_QueryInterface(currElement));
nsresult rv = currInputElement->GetFiles(getter_AddRefs(fileList));
NS_ENSURE_SUCCESS(rv, rv);
if (!fileList) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIStringBundleService> bundleService =
mozilla::services::GetStringBundleService();
if (!bundleService) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIStringBundle> bundle;
rv = bundleService->CreateBundle("chrome://global/locale/layout/HtmlForm.properties",
getter_AddRefs(bundle));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t listLength = 0;
rv = fileList->GetLength(&listLength);
NS_ENSURE_SUCCESS(rv, rv);
if (listLength == 0) {
if (content->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
rv = bundle->GetStringFromName(MOZ_UTF16("NoFilesSelected"),
getter_Copies(outText));
} else {
rv = bundle->GetStringFromName(MOZ_UTF16("NoFileSelected"),
getter_Copies(outText));
}
NS_ENSURE_SUCCESS(rv, rv);
} else {
FileList* fl = static_cast<FileList*>(fileList.get());
fl->Item(0)->GetName(outText);
// For UX and performance (jank) reasons we cap the number of
// files that we list in the tooltip to 20 plus a "and xxx more"
// line, or to 21 if exactly 21 files were picked.
const uint32_t TRUNCATED_FILE_COUNT = 20;
uint32_t count = std::min(listLength, TRUNCATED_FILE_COUNT);
for (uint32_t i = 1; i < count; ++i) {
nsString fileName;
fl->Item(i)->GetName(fileName);
outText.Append(NS_LITERAL_STRING("\n"));
outText.Append(fileName);
}
}
} else if (NS_SUCCEEDED(currElement->GetAttribute(NS_LITERAL_STRING("title"), outText)) &&
outText.Length()) {
found = true;
}
}

View File

@ -18,7 +18,6 @@ skip-if = e10s # Bug 1064580
skip-if = e10s
[browser_findbar.js]
[browser_input_file_tooltips.js]
skip-if = e10s # Bug 1236991 - Update or remove tests that use fillInPageTooltip
[browser_isSynthetic.js]
support-files =
empty.png

View File

@ -1,29 +1,53 @@
function test()
{
let data = [
{ value: "/tmp", result: "tmp" },
{ title: "foo", result: "foo" },
{ result: "No file selected." },
{ multiple: true, result: "No files selected." },
{ required: true, result: "Please select a file." }
];
let doc = gBrowser.contentDocument;
let tooltip = document.getElementById("aHTMLTooltip");
let tempFile;
add_task(function* setup() {
yield new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
});
tempFile = createTempFile();
registerCleanupFunction(function() {
tempFile.remove(true);
});
});
for (let test of data) {
let input = doc.createElement('input');
add_task(function* test_singlefile_selected() {
yield do_test({value: true, result: "testfile_bug1251809"});
});
add_task(function* test_title_set() {
yield do_test({title: "foo", result: "foo"});
});
add_task(function* test_nofile_selected() {
yield do_test({result: "No file selected."});
});
add_task(function* test_multipleset_nofile_selected() {
yield do_test({multiple: true, result: "No files selected."});
});
add_task(function* test_requiredset() {
yield do_test({required: true, result: "Please select a file."});
});
function* do_test(test) {
info(`starting test ${JSON.stringify(test)}`);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
yield new Promise(resolve => {
EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 300, 300, resolve);
});
ContentTask.spawn(tab.linkedBrowser, test, function*(test) {
let doc = content.document;
let input = doc.createElement("input");
doc.body.appendChild(input);
input.type = 'file';
input.id = "test_input";
input.setAttribute("style", "position: absolute; top: 0; left: 0;");
input.type = "file";
if (test.title) {
input.setAttribute('title', test.title);
}
if (test.value) {
if (test.value == "/tmp" && navigator.platform.indexOf('Win') != -1) {
test.value = "C:\\Temp";
test.result = "Temp";
}
input.value = test.value;
input.setAttribute("title", test.title);
}
if (test.multiple) {
input.multiple = true;
@ -31,8 +55,57 @@ function test()
if (test.required) {
input.required = true;
}
});
ok(tooltip.fillInPageTooltip(input));
is(tooltip.getAttribute('label'), test.result);
if (test.value) {
let MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
MockFilePicker.returnValue = MockFilePicker.returnOK;
MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", [], false);
MockFilePicker.returnFiles = [tempFile];
try {
// Open the File Picker dialog (MockFilePicker) to select
// the files for the test.
yield BrowserTestUtils.synthesizeMouseAtCenter("#test_input", {}, tab.linkedBrowser);
yield ContentTask.spawn(tab.linkedBrowser, {}, function*() {
let input = content.document.querySelector("#test_input");
yield ContentTaskUtils.waitForCondition(() => input.files.length,
"The input should have at least one file selected");
info(`The input has ${input.files.length} file(s) selected.`);
});
} finally {
MockFilePicker.cleanup();
}
}
let awaitTooltipOpen = new Promise(resolve => {
let tooltipId = Services.appinfo.browserTabsRemoteAutostart ?
"remoteBrowserTooltip" :
"aHTMLTooltip";
let tooltip = document.getElementById(tooltipId);
tooltip.addEventListener("popupshown", function onpopupshown(event) {
tooltip.removeEventListener("popupshown", onpopupshown);
resolve(event.target);
});
});
yield new Promise(resolve => {
EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 100, 5, resolve);
});
yield new Promise(resolve => setTimeout(resolve, 100));
yield new Promise(resolve => {
EventUtils.synthesizeNativeMouseMove(tab.linkedBrowser, 110, 15, resolve);
});
let tooltip = yield awaitTooltipOpen;
is(tooltip.getAttribute("label"), test.result, "tooltip label should match expectation");
yield BrowserTestUtils.removeTab(tab);
}
function createTempFile() {
let file = FileUtils.getDir("TmpD", [], false);
file.append("testfile_bug1251809");
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
return file;
}

View File

@ -538,8 +538,9 @@
<parameter name="tipElement"/>
<body>
<![CDATA[
// Don't show the tooltip if the tooltip node is a document or disconnected.
// Don't show the tooltip if the tooltip node is a document, browser, or disconnected.
if (!tipElement || !tipElement.ownerDocument ||
tipElement.localName == "browser" ||
(tipElement.ownerDocument.compareDocumentPosition(tipElement) & document.DOCUMENT_POSITION_DISCONNECTED)) {
return false;
}