Bug 1495153 part 2 - Implement cropping the filename for <input type=file>. r=emilio,jfkthame

This commit is contained in:
Mats Palmgren 2018-10-06 19:31:51 +02:00
parent 771d50381e
commit 3eec51d6b3
7 changed files with 271 additions and 8 deletions

View File

@ -27,6 +27,7 @@
#include "nsContentUtils.h"
#include "mozilla/EventStates.h"
#include "nsTextNode.h"
#include "nsTextFrame.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -56,6 +57,144 @@ nsFileControlFrame::Init(nsIContent* aContent,
mMouseListener = new DnDListener(this);
}
bool
nsFileControlFrame::CropTextToWidth(gfxContext& aRenderingContext,
const nsIFrame* aFrame,
nscoord aWidth,
nsString& aText)
{
if (aText.IsEmpty()) {
return false;
}
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
// see if the text will completely fit in the width given
nscoord textWidth =
nsLayoutUtils::AppUnitWidthOfStringBidi(aText, aFrame, *fm,
aRenderingContext);
if (textWidth <= aWidth) {
return false;
}
DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
// see if the width is even smaller than the ellipsis
fm->SetTextRunRTL(false);
textWidth = nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, drawTarget);
if (textWidth >= aWidth) {
aText = kEllipsis;
return true;
}
// determine how much of the string will fit in the max width
nscoord totalWidth = textWidth;
using mozilla::unicode::ClusterIterator;
using mozilla::unicode::ClusterReverseIterator;
ClusterIterator leftIter(aText.Data(), aText.Length());
ClusterReverseIterator rightIter(aText.Data(), aText.Length());
const char16_t* leftPos = leftIter;
const char16_t* rightPos = rightIter;
const char16_t* pos;
ptrdiff_t length;
nsAutoString leftString, rightString;
while (leftPos < rightPos) {
leftIter.Next();
pos = leftIter;
length = pos - leftPos;
textWidth = nsLayoutUtils::AppUnitWidthOfString(leftPos, length,
*fm, drawTarget);
if (totalWidth + textWidth > aWidth) {
break;
}
leftString.Append(leftPos, length);
leftPos = pos;
totalWidth += textWidth;
if (leftPos >= rightPos) {
break;
}
rightIter.Next();
pos = rightIter;
length = rightPos - pos;
textWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length,
*fm, drawTarget);
if (totalWidth + textWidth > aWidth) {
break;
}
rightString.Insert(pos, 0, length);
rightPos = pos;
totalWidth += textWidth;
}
aText = leftString + kEllipsis + rightString;
return true;
}
void
nsFileControlFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus)
{
// Restore the uncropped filename.
nsAutoString filename;
HTMLInputElement::FromNode(mContent)->GetDisplayFileName(filename);
bool done = false;
while (true) {
UpdateDisplayedValue(filename, false); // update the text node
AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
LinesBegin()->MarkDirty();
nsBlockFrame::Reflow(aPresContext, aMetrics, aReflowInput, aStatus);
if (done) {
break;
}
nscoord lineISize = LinesBegin()->ISize();
const auto cbWM = aMetrics.GetWritingMode();
const auto wm = GetWritingMode();
nscoord iSize = wm.IsOrthogonalTo(cbWM) ? aMetrics.BSize(cbWM)
: aMetrics.ISize(cbWM);
auto bp = GetLogicalUsedBorderAndPadding(wm);
nscoord contentISize = iSize - bp.IStartEnd(wm);
if (lineISize > contentISize) {
// The filename overflows - crop it and reflow again (once).
// NOTE: the label frame might have bidi-continuations
auto* labelFrame = mTextContent->GetPrimaryFrame();
nscoord labelBP =
labelFrame->GetLogicalUsedBorderAndPadding(wm).IStartEnd(wm);
auto* lastLabelCont = labelFrame->LastContinuation();
if (lastLabelCont != labelFrame) {
labelBP +=
lastLabelCont->GetLogicalUsedBorderAndPadding(wm).IStartEnd(wm);
}
auto* buttonFrame = mBrowseFilesOrDirs->GetPrimaryFrame();
nscoord availableISizeForLabel = contentISize - buttonFrame->ISize(wm) -
buttonFrame->GetLogicalUsedMargin(wm).IStartEnd(wm);
if (CropTextToWidth(*aReflowInput.mRenderingContext,
labelFrame,
availableISizeForLabel - labelBP,
filename)) {
nsBlockFrame::DidReflow(aPresContext, &aReflowInput);
aStatus.Reset();
labelFrame->AddStateBits(NS_FRAME_IS_DIRTY |
NS_BLOCK_NEEDS_BIDI_RESOLUTION);
mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
done = true;
continue;
}
}
break;
}
}
void
nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
{
@ -410,7 +549,7 @@ nsFileControlFrame::DnDListener::CanDropTheseFiles(DataTransfer* aDataTransfer,
}
nscoord
nsFileControlFrame::GetMinISize(gfxContext *aRenderingContext)
nsFileControlFrame::GetMinISize(gfxContext* aRenderingContext)
{
nscoord result;
DISPLAY_MIN_INLINE_SIZE(this, result);
@ -420,6 +559,23 @@ nsFileControlFrame::GetMinISize(gfxContext *aRenderingContext)
return result;
}
nscoord
nsFileControlFrame::GetPrefISize(gfxContext* aRenderingContext)
{
nscoord result;
DISPLAY_MIN_INLINE_SIZE(this, result);
// Make sure we measure with the uncropped filename.
if (mPrefWidth == NS_INTRINSIC_WIDTH_UNKNOWN) {
nsAutoString filename;
HTMLInputElement::FromNode(mContent)->GetDisplayFileName(filename);
UpdateDisplayedValue(filename, false);
}
result = nsBlockFrame::GetPrefISize(aRenderingContext);
return result;
}
void
nsFileControlFrame::SyncDisabledState()
{
@ -470,7 +626,15 @@ void
nsFileControlFrame::UpdateDisplayedValue(const nsAString& aValue, bool aNotify)
{
auto* text = Text::FromNode(mTextContent->GetFirstChild());
uint32_t oldLength = aNotify ? 0 : text->TextLength();
text->SetText(aValue, aNotify);
if (!aNotify) {
// We can't notify during Reflow so we need to tell the text frame
// about the text content change we just did.
if (auto* textFrame = static_cast<nsTextFrame*>(text->GetPrimaryFrame())) {
textFrame->NotifyNativeAnonymousTextnodeChange(oldLength);
}
}
}
nsresult

View File

@ -27,23 +27,29 @@ class nsFileControlFrame final : public nsBlockFrame,
public nsIAnonymousContentCreator
{
public:
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsFileControlFrame)
explicit nsFileControlFrame(ComputedStyle* aStyle);
virtual void Init(nsIContent* aContent,
nsContainerFrame* aParent,
nsIFrame* aPrevInFlow) override;
void Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) override;
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsFileControlFrame)
// nsIFormControlFrame
virtual nsresult SetFormProperty(nsAtom* aName, const nsAString& aValue) override;
virtual void SetFocus(bool aOn, bool aRepaint) override;
virtual nscoord GetMinISize(gfxContext *aRenderingContext) override;
nscoord GetMinISize(gfxContext* aRenderingContext) override;
nscoord GetPrefISize(gfxContext* aRenderingContext) override;
virtual void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) override;
@ -51,9 +57,9 @@ public:
virtual nsresult GetFrameName(nsAString& aResult) const override;
#endif
virtual nsresult AttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType) override;
nsresult AttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType) override;
virtual void ContentStatesChanged(mozilla::EventStates aStates) override;
// nsIAnonymousContentCreator
@ -151,6 +157,15 @@ protected:
RefPtr<DnDListener> mMouseListener;
protected:
/**
* Crop aText to fit inside aWidth using the styles of aFrame.
* @return true if aText was modified
*/
static bool CropTextToWidth(gfxContext& aRenderingContext,
const nsIFrame* aFrame,
nscoord aWidth,
nsString& aText);
/**
* Sync the disabled state of the content with anonymous children.
*/

View File

@ -4796,6 +4796,28 @@ nsTextFrame::DisconnectTextRuns()
}
}
void
nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength)
{
MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree());
MarkIntrinsicISizesDirty();
// This is to avoid making a new Reflow request in CharacterDataChanged:
for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
f->AddStateBits(NS_FRAME_IS_DIRTY);
f->mReflowRequestedForCharDataChange = true;
}
// Pretend that all the text changed.
CharacterDataChangeInfo info;
info.mAppend = false;
info.mChangeStart = 0;
info.mChangeEnd = aOldLength;
info.mReplaceLength = mContent->TextLength();
CharacterDataChanged(info);
}
nsresult
nsTextFrame::CharacterDataChanged(const CharacterDataChangeInfo& aInfo)
{

View File

@ -668,6 +668,13 @@ public:
bool HasAnyNoncollapsedCharacters() override;
/**
* Call this after you have manually changed the text node contents without
* notifying that change. This behaves as if all the text contents changed.
* (You should only use this for native anonymous content.)
*/
void NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength);
protected:
virtual ~nsTextFrame();

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html><head>
<meta charset="utf-8">
<title>Reference for dynamic-max-width.html</title>
</head>
<body>
<input type=file dir=rtl>
<br>
<input type=file>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html class="reftest-wait"><head>
<meta charset="utf-8">
<title>CSS Test: file control with dynamic change to max-width</title>
<style type="text/css">
input { max-width: 10em; }
</style>
<script>
function tweak() {
[...document.querySelectorAll('input')].forEach(function(e) {
e.style.maxWidth = 'initial';
});
document.documentElement.removeAttribute("class");
}
window.addEventListener("MozReftestInvalidate", tweak);
</script>
</head>
<body onload="test()">
<input type=file dir=rtl>
<br>
<input type=file>
</body>
</html>

View File

@ -5,3 +5,4 @@ fuzzy-if(gtkWidget||webrender,0-1,0-10) fails-if(Android) == background.html bac
fuzzy-if(gtkWidget,0-1,0-10) fails-if(Android) == style.html style-ref.xul
!= width-clip.html width-clip-ref.html
fails-if(Android) == color-inherit.html color-inherit-ref.html
fuzzy-if(Android,1-2,2-2) == dynamic-max-width.html dynamic-max-width-ref.html