Bug 1357981 - Allocate input type=file related member variables only when needed. f=smaug, r=baku

In order to reduce the size of HTMLInputElement for performance gain, we're
going to allocate input type=file related member variables only when type=file.

--HG--
extra : rebase_source : de31e8c55ee44b6ef7f8143b39e64d369d021856
This commit is contained in:
Jessica Jong 2017-04-26 02:36:00 -04:00
parent d91dc284ca
commit bdf639f264
2 changed files with 179 additions and 109 deletions

View File

@ -412,6 +412,72 @@ NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState, NS_INPUT_ELEMENT_STATE_IID)
NS_IMPL_ISUPPORTS(HTMLInputElementState, HTMLInputElementState)
struct HTMLInputElement::FileData
{
/**
* The value of the input if it is a file input. This is the list of files or
* directories DOM objects used when uploading a file. It is vital that this
* is kept separate from mValue so that it won't be possible to 'leak' the
* value from a text-input to a file-input. Additionally, the logic for this
* value is kept as simple as possible to avoid accidental errors where the
* wrong filename is used. Therefor the list of filenames is always owned by
* this member, never by the frame. Whenever the frame wants to change the
* filename it has to call SetFilesOrDirectories to update this member.
*/
nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
/**
* Hack for bug 1086684: Stash the .value when we're a file picker.
*/
nsString mFirstFilePath;
RefPtr<FileList> mFileList;
Sequence<RefPtr<FileSystemEntry>> mEntries;
nsString mStaticDocFileList;
void ClearGetFilesHelpers()
{
if (mGetFilesNonRecursiveHelper) {
mGetFilesRecursiveHelper->Unlink();
mGetFilesRecursiveHelper = nullptr;
}
if (mGetFilesNonRecursiveHelper) {
mGetFilesNonRecursiveHelper->Unlink();
mGetFilesNonRecursiveHelper = nullptr;
}
}
// Cycle Collection support.
void Traverse(nsCycleCollectionTraversalCallback &cb)
{
FileData* tmp = this;
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
if (mGetFilesRecursiveHelper) {
mGetFilesRecursiveHelper->Traverse(cb);
}
if (mGetFilesNonRecursiveHelper) {
mGetFilesNonRecursiveHelper->Traverse(cb);
}
}
void Unlink()
{
FileData* tmp = this;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
ClearGetFilesHelpers();
}
};
HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
: mFilePicker(aFilePicker)
@ -1106,6 +1172,12 @@ HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNo
, mSelectionCached(true)
, mIsPreviewEnabled(false)
{
// If size is above 512, mozjemalloc allocates 1kB, see
// memory/mozjemalloc/jemalloc.c
static_assert(sizeof(HTMLInputElement) <= 512,
"Keep the size of HTMLInputElement under 512 to avoid "
"performance regression!");
// We are in a type=text so we now we currenty need a nsTextEditorState.
mInputData.mState =
nsTextEditorState::Construct(this, &sCachedTextEditorState);
@ -1171,33 +1243,23 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
if (tmp->IsSingleLineTextControl(false)) {
tmp->mInputData.mState->Traverse(cb);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
if (tmp->mGetFilesRecursiveHelper) {
tmp->mGetFilesRecursiveHelper->Traverse(cb);
if (tmp->mFileData) {
tmp->mFileData->Traverse(cb);
}
if (tmp->mGetFilesNonRecursiveHelper) {
tmp->mGetFilesNonRecursiveHelper->Traverse(cb);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
nsGenericHTMLFormElementWithState)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
if (tmp->IsSingleLineTextControl(false)) {
tmp->mInputData.mState->Unlink();
}
tmp->ClearGetFilesHelpers();
if (tmp->mFileData) {
tmp->mFileData->Unlink();
}
//XXX should unlink more?
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -1250,11 +1312,12 @@ HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) co
if (it->OwnerDoc()->IsStaticDocument()) {
// We're going to be used in print preview. Since the doc is static
// we can just grab the pretty string and use it as wallpaper
GetDisplayFileName(it->mStaticDocFileList);
GetDisplayFileName(it->mFileData->mStaticDocFileList);
} else {
it->ClearGetFilesHelpers();
it->mFilesOrDirectories.Clear();
it->mFilesOrDirectories.AppendElements(mFilesOrDirectories);
it->mFileData->ClearGetFilesHelpers();
it->mFileData->mFilesOrDirectories.Clear();
it->mFileData->mFilesOrDirectories.AppendElements(
mFileData->mFilesOrDirectories);
}
break;
case VALUE_MODE_DEFAULT_ON:
@ -1671,17 +1734,17 @@ HTMLInputElement::GetValueInternal(nsAString& aValue,
}
if (aCallerType == CallerType::System) {
aValue.Assign(mFirstFilePath);
aValue.Assign(mFileData->mFirstFilePath);
return;
}
if (mFilesOrDirectories.IsEmpty()) {
if (mFileData->mFilesOrDirectories.IsEmpty()) {
aValue.Truncate();
return;
}
nsAutoString file;
GetDOMFileOrDirectoryName(mFilesOrDirectories[0], file);
GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], file);
if (file.IsEmpty()) {
aValue.Truncate();
return;
@ -2507,9 +2570,15 @@ void
HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
ErrorResult& aRv)
{
for (uint32_t i = 0; i < mFilesOrDirectories.Length(); i++) {
if (NS_WARN_IF(mType != NS_FORM_INPUT_FILE)) {
return;
}
const nsTArray<OwningFileOrDirectory>& filesOrDirs =
GetFilesOrDirectoriesInternal();
for (uint32_t i = 0; i < filesOrDirs.Length(); i++) {
nsAutoString str;
GetDOMFileOrDirectoryPath(mFilesOrDirectories[i], str, aRv);
GetDOMFileOrDirectoryPath(filesOrDirs[i], str, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
@ -2521,6 +2590,10 @@ HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
void
HTMLInputElement::MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles)
{
if (NS_WARN_IF(mType != NS_FORM_INPUT_FILE)) {
return;
}
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
MOZ_ASSERT(global);
if (!global) {
@ -2543,6 +2616,10 @@ void
HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
ErrorResult& aRv)
{
if (NS_WARN_IF(mType != NS_FORM_INPUT_FILE)) {
return;
}
if (XRE_IsContentProcess()) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
@ -2588,6 +2665,10 @@ void
HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
ErrorResult& aRv)
{
if (NS_WARN_IF(mType != NS_FORM_INPUT_FILE)) {
return;
}
nsCOMPtr<nsIFile> file;
aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
if (NS_WARN_IF(aRv.Failed())) {
@ -2968,19 +3049,21 @@ HTMLInputElement::GetPreviewVisibility()
void
HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
{
MOZ_ASSERT(mFileData);
if (OwnerDoc()->IsStaticDocument()) {
aValue = mStaticDocFileList;
aValue = mFileData->mStaticDocFileList;
return;
}
if (mFilesOrDirectories.Length() == 1) {
GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
if (mFileData->mFilesOrDirectories.Length() == 1) {
GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], aValue);
return;
}
nsXPIDLString value;
if (mFilesOrDirectories.IsEmpty()) {
if (mFileData->mFilesOrDirectories.IsEmpty()) {
if ((IsDirPickerEnabled() && Allowdirs()) ||
(IsWebkitDirPickerEnabled() &&
HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
@ -2995,7 +3078,7 @@ HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
}
} else {
nsString count;
count.AppendInt(int(mFilesOrDirectories.Length()));
count.AppendInt(int(mFileData->mFilesOrDirectories.Length()));
const char16_t* params[] = { count.get() };
nsContentUtils::FormatLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
@ -3005,19 +3088,27 @@ HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
aValue = value;
}
const nsTArray<OwningFileOrDirectory>&
HTMLInputElement::GetFilesOrDirectoriesInternal() const
{
return mFileData->mFilesOrDirectories;
}
void
HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
bool aSetValueChanged)
{
ClearGetFilesHelpers();
MOZ_ASSERT(mFileData);
mFileData->ClearGetFilesHelpers();
if (IsWebkitFileSystemEnabled()) {
HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
mEntries.Clear();
mFileData->mEntries.Clear();
}
mFilesOrDirectories.Clear();
mFilesOrDirectories.AppendElements(aFilesOrDirectories);
mFileData->mFilesOrDirectories.Clear();
mFileData->mFilesOrDirectories.AppendElements(aFilesOrDirectories);
AfterSetFilesOrDirectories(aSetValueChanged);
}
@ -3026,20 +3117,23 @@ void
HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
bool aSetValueChanged)
{
MOZ_ASSERT(mFileData);
RefPtr<FileList> files = static_cast<FileList*>(aFiles);
mFilesOrDirectories.Clear();
ClearGetFilesHelpers();
mFileData->mFilesOrDirectories.Clear();
mFileData->ClearGetFilesHelpers();
if (IsWebkitFileSystemEnabled()) {
HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
mEntries.Clear();
mFileData->mEntries.Clear();
}
if (aFiles) {
uint32_t listLength;
aFiles->GetLength(&listLength);
for (uint32_t i = 0; i < listLength; i++) {
OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
OwningFileOrDirectory* element =
mFileData->mFilesOrDirectories.AppendElement();
element->SetAsFile() = files->Item(i);
}
}
@ -3051,6 +3145,10 @@ HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
void
HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
{
if (NS_WARN_IF(mType != NS_FORM_INPUT_FILE)) {
return;
}
SetFilesOrDirectories(aFilesOrDirectories, true);
if (IsWebkitFileSystemEnabled()) {
@ -3094,11 +3192,12 @@ HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged)
// call under GetMozFullPath won't be rejected for not being urgent.
// XXX Protected by the ifndef because the blob code doesn't allow us to send
// this message in b2g.
if (mFilesOrDirectories.IsEmpty()) {
mFirstFilePath.Truncate();
if (mFileData->mFilesOrDirectories.IsEmpty()) {
mFileData->mFirstFilePath.Truncate();
} else {
ErrorResult rv;
GetDOMFileOrDirectoryPath(mFilesOrDirectories[0], mFirstFilePath, rv);
GetDOMFileOrDirectoryPath(mFileData->mFilesOrDirectories[0],
mFileData->mFirstFilePath, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
}
@ -3146,12 +3245,12 @@ HTMLInputElement::GetFiles()
return nullptr;
}
if (!mFileList) {
mFileList = new FileList(static_cast<nsIContent*>(this));
if (!mFileData->mFileList) {
mFileData->mFileList = new FileList(static_cast<nsIContent*>(this));
UpdateFileList();
}
return mFileList;
return mFileData->mFileList;
}
/* static */ void
@ -3177,15 +3276,17 @@ HTMLInputElement::HandleNumberControlSpin(void* aData)
void
HTMLInputElement::UpdateFileList()
{
if (mFileList) {
mFileList->Clear();
MOZ_ASSERT(mFileData);
if (mFileData->mFileList) {
mFileData->mFileList->Clear();
const nsTArray<OwningFileOrDirectory>& array =
GetFilesOrDirectoriesInternal();
for (uint32_t i = 0; i < array.Length(); ++i) {
if (array[i].IsFile()) {
mFileList->Append(array[i].GetAsFile());
mFileData->mFileList->Append(array[i].GetAsFile());
}
}
}
@ -5086,10 +5187,12 @@ HTMLInputElement::HandleTypeChange(uint8_t aNewType, bool aNotify)
MOZ_ASSERT(oldType != aNewType);
if (aNewType == NS_FORM_INPUT_FILE || oldType == NS_FORM_INPUT_FILE) {
// Strictly speaking, we only need to clear files on going _to_ or _from_
// the NS_FORM_INPUT_FILE type, not both, since we'll never confuse values
// and filenames. But this is safer.
ClearFiles(false);
if (aNewType == NS_FORM_INPUT_FILE) {
mFileData.reset(new FileData());
} else {
mFileData->Unlink();
mFileData = nullptr;
}
}
if (oldType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
@ -6647,9 +6750,9 @@ HTMLInputElement::SaveState()
}
break;
case VALUE_MODE_FILENAME:
if (!mFilesOrDirectories.IsEmpty()) {
if (!mFileData->mFilesOrDirectories.IsEmpty()) {
inputState = new HTMLInputElementState();
inputState->SetFilesOrDirectories(mFilesOrDirectories);
inputState->SetFilesOrDirectories(mFileData->mFilesOrDirectories);
}
break;
case VALUE_MODE_VALUE:
@ -8574,24 +8677,12 @@ HTMLInputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
return HTMLInputElementBinding::Wrap(aCx, this, aGivenProto);
}
void
HTMLInputElement::ClearGetFilesHelpers()
{
if (mGetFilesRecursiveHelper) {
mGetFilesRecursiveHelper->Unlink();
mGetFilesRecursiveHelper = nullptr;
}
if (mGetFilesNonRecursiveHelper) {
mGetFilesNonRecursiveHelper->Unlink();
mGetFilesNonRecursiveHelper = nullptr;
}
}
GetFilesHelper*
HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
ErrorResult& aRv)
{
MOZ_ASSERT(mFileData);
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
MOZ_ASSERT(global);
if (!global) {
@ -8600,36 +8691,36 @@ HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
}
if (aRecursiveFlag) {
if (!mGetFilesRecursiveHelper) {
mGetFilesRecursiveHelper =
GetFilesHelper::Create(global,
GetFilesOrDirectoriesInternal(),
aRecursiveFlag, aRv);
if (!mFileData->mGetFilesRecursiveHelper) {
mFileData->mGetFilesRecursiveHelper =
GetFilesHelper::Create(global,
GetFilesOrDirectoriesInternal(),
aRecursiveFlag, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
}
return mGetFilesRecursiveHelper;
return mFileData->mGetFilesRecursiveHelper;
}
if (!mGetFilesNonRecursiveHelper) {
mGetFilesNonRecursiveHelper =
GetFilesHelper::Create(global,
GetFilesOrDirectoriesInternal(),
aRecursiveFlag, aRv);
if (!mFileData->mGetFilesNonRecursiveHelper) {
mFileData->mGetFilesNonRecursiveHelper =
GetFilesHelper::Create(global,
GetFilesOrDirectoriesInternal(),
aRecursiveFlag, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
}
return mGetFilesNonRecursiveHelper;
return mFileData->mGetFilesNonRecursiveHelper;
}
void
HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
{
MOZ_ASSERT(mEntries.IsEmpty());
MOZ_ASSERT(mFileData && mFileData->mEntries.IsEmpty());
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
MOZ_ASSERT(global);
@ -8654,14 +8745,18 @@ HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrD
// dropped fileEntry and directoryEntry objects.
fs->CreateRoot(entries);
mEntries.SwapElements(entries);
mFileData->mEntries.SwapElements(entries);
}
void
HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
{
if (NS_WARN_IF(mType != NS_FORM_INPUT_FILE)) {
return;
}
Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
aSequence.AppendElements(mEntries);
aSequence.AppendElements(mFileData->mEntries);
}
} // namespace dom

View File

@ -256,10 +256,7 @@ public:
void GetDisplayFileName(nsAString& aFileName) const;
const nsTArray<OwningFileOrDirectory>& GetFilesOrDirectoriesInternal() const
{
return mFilesOrDirectories;
}
const nsTArray<OwningFileOrDirectory>& GetFilesOrDirectoriesInternal() const;
void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
bool aSetValueChanged);
@ -1533,30 +1530,8 @@ protected:
nsTextEditorState* mState;
} mInputData;
/**
* The value of the input if it is a file input. This is the list of files or
* directories DOM objects used when uploading a file. It is vital that this
* is kept separate from mValue so that it won't be possible to 'leak' the
* value from a text-input to a file-input. Additionally, the logic for this
* value is kept as simple as possible to avoid accidental errors where the
* wrong filename is used. Therefor the list of filenames is always owned by
* this member, never by the frame. Whenever the frame wants to change the
* filename it has to call SetFilesOrDirectories to update this member.
*/
nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
/**
* Hack for bug 1086684: Stash the .value when we're a file picker.
*/
nsString mFirstFilePath;
RefPtr<FileList> mFileList;
Sequence<RefPtr<FileSystemEntry>> mEntries;
nsString mStaticDocFileList;
struct FileData;
UniquePtr<FileData> mFileData;
/**
* The value of the input element when first initialized and it is updated