Bug 1251809 - Add input[type=file] tooltip support for e10s. r?enn,ehsan
This patch adds support for tooltips on input[type=file] in e10s. I've updated the automated test to work in both non-e10s and e10s, but was unable to add test cases for multiple files being selected due to an issue with MockFilePicker where it only returns the first file from the returnList. Testing for multiple files will need to be done manually. Also, popopup.xml includes support for adding "And x more..." at the end of the tooltip if the selected number of files is more than our truncation amount. I was unable to add that to the remote implementation since I didn't see a way to use PluralForm-syntax strings from C++. I also didn' want to add a new string to cover this since we want to uplift these patches to 46.
MozReview-Commit-ID: B3524S0drrP
--- a/embedding/browser/nsDocShellTreeOwner.cpp
+++ b/embedding/browser/nsDocShellTreeOwner.cpp
@@ -30,29 +30,31 @@
#include "nsIDOMNodeList.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDocumentType.h"
#include "nsIDOMElement.h"
#include "Link.h"
#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"
#include "nsIDOMHTMLTextAreaElement.h"
#include "nsIDOMHTMLHtmlElement.h"
#include "nsIDOMHTMLAppletElement.h"
#include "nsIDOMHTMLObjectElement.h"
#include "nsIDOMHTMLEmbedElement.h"
#include "nsIDOMHTMLDocument.h"
#include "nsIImageLoadingContent.h"
#include "nsIWebNavigation.h"
#include "nsIDOMHTMLElement.h"
#include "nsIPresShell.h"
+#include "nsIStringBundle.h"
#include "nsPIDOMWindow.h"
#include "nsPIWindowRoot.h"
#include "nsIDOMWindowCollection.h"
#include "nsIWindowWatcher.h"
#include "nsPIWindowWatcher.h"
#include "nsIPrompt.h"
#include "nsITabParent.h"
#include "nsRect.h"
@@ -63,16 +65,18 @@
#include "nsPresContext.h"
#include "nsViewManager.h"
#include "nsView.h"
#include "nsIDOMDragEvent.h"
#include "nsIConstraintValidation.h"
#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;
// A helper routine that navigates the tricky path from a |nsWebBrowser| to
// a |EventTarget| via the window root and chrome event handler.
static nsresult
GetDOMEventTarget(nsWebBrowser* aInBrowser, EventTarget** aTarget)
@@ -1142,18 +1146,72 @@ DefaultTooltipTextProvider::GetNodeText(
if (currElement) {
nsCOMPtr<nsIContent> content(do_QueryInterface(currElement));
if (content) {
if (!content->IsAnyOfXULElements(mTag_dialog,
mTag_dialogheader,
mTag_window)) {
// first try the normal title attribute...
if (!content->IsSVGElement()) {
- currElement->GetAttribute(NS_LITERAL_STRING("title"), outText);
- if (outText.Length()) {
+ nsCOMPtr<nsIAtom> inputAtom = do_GetAtom("input");
+ nsString typeText;
+ bool hasAttr = false;
+ // If the element is an <input type="file"> without a title,
+ // we should show the current file selection.
+ if (content->IsHTMLElement(inputAtom) &&
+ NS_SUCCEEDED(currElement->GetAttribute(NS_LITERAL_STRING("type"), typeText)) &&
+ typeText.EqualsLiteral("file") &&
+ NS_SUCCEEDED(currElement->HasAttribute(NS_LITERAL_STRING("title"), &hasAttr)) &&
+ !hasAttr) {
+ found = true;
+ nsCOMPtr<nsIDOMFileList> fileList;
+ nsCOMPtr<nsIDOMHTMLInputElement> currInputElement(do_QueryInterface(currElement));
+ currInputElement->GetFiles(getter_AddRefs(fileList));
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult 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) {
+ currElement->HasAttribute(NS_LITERAL_STRING("multiple"), &hasAttr);
+ if (hasAttr) {
+ 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;
}
}
if (!found) {
// ...ok, that didn't work, try it in the XLink namespace
NS_NAMED_LITERAL_STRING(xlinkNS, "http://www.w3.org/1999/xlink");
nsCOMPtr<mozilla::dom::Link> linkContent(
do_QueryInterface(currElement));
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -12,17 +12,16 @@ skip-if = buildapp == 'mulet' || e10s #
skip-if = e10s # Bug 1064580
[browser_bug1198465.js]
[browser_contentTitle.js]
[browser_default_image_filename.js]
[browser_f7_caret_browsing.js]
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
[browser_keyevents_during_autoscrolling.js]
[browser_save_resend_postdata.js]
support-files =
common/mockTransfer.js
data/post_form_inner.sjs
--- a/toolkit/content/tests/browser/browser_input_file_tooltips.js
+++ b/toolkit/content/tests/browser/browser_input_file_tooltips.js
@@ -1,38 +1,110 @@
-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 tempFile;
+add_task(function* setup() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [["ui.tooltipDelay", 0]]}, resolve);
+ });
+ tempFile = createTempFile();
+ registerCleanupFunction(function() {
+ tempFile.remove(true);
+ });
+});
- let doc = gBrowser.contentDocument;
- let tooltip = document.getElementById("aHTMLTooltip");
+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."});
+});
- for (let test of data) {
- let input = doc.createElement('input');
+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 MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ if (test.value) {
+ MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", [], false);
+ MockFilePicker.returnFiles = [tempFile];
+ }
+
+ 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.clientTop; // Flush layout
+ 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;
}
if (test.required) {
input.required = true;
}
+ });
- ok(tooltip.fillInPageTooltip(input));
- is(tooltip.getAttribute('label'), test.result);
+ if (test.value) {
+ // 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.`);
+ });
}
+
+ 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;
+}
--- a/toolkit/content/widgets/popup.xml
+++ b/toolkit/content/widgets/popup.xml
@@ -532,18 +532,19 @@
Note that DefaultTooltipTextProvider::GetNodeText() from nsDocShellTreeOwner.cpp
also performs the same function, but for embedded clients that don't use a XUL/JS
layer. These two should be kept synchronized.
-->
<method name="fillInPageTooltip">
<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.tagName == "xul:browser" ||
(tipElement.ownerDocument.compareDocumentPosition(tipElement) & document.DOCUMENT_POSITION_DISCONNECTED)) {
return false;
}
var defView = tipElement.ownerDocument.defaultView;
// XXX Work around bug 350679:
// "Tooltips can be fired in documents with no view".
if (!defView)