490 lines
17 KiB
C++
490 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2006-2017 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2010, 2011, 2012 Google Inc. All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "FormController.h"
|
|
|
|
#include "HTMLFormElement.h"
|
|
#include "HTMLInputElement.h"
|
|
#include "ScriptDisallowedScope.h"
|
|
#include "TypedElementDescendantIterator.h"
|
|
#include <wtf/NeverDestroyed.h>
|
|
#include <wtf/WeakHashMap.h>
|
|
#include <wtf/text/StringBuilder.h>
|
|
#include <wtf/text/StringConcatenateNumbers.h>
|
|
#include <wtf/text/StringToIntegerConversion.h>
|
|
|
|
namespace WebCore {
|
|
|
|
using namespace HTMLNames;
|
|
|
|
static inline HTMLFormElement* ownerFormForState(const HTMLFormControlElementWithState& control)
|
|
{
|
|
// Assume controls with form attribute have no owners because we restore
|
|
// state during parsing and form owners of such controls might be
|
|
// indeterminate.
|
|
return control.hasAttributeWithoutSynchronization(formAttr) ? 0 : control.form();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Serilized form of FormControlState:
|
|
// (',' means strings around it are separated in stateVector.)
|
|
//
|
|
// SerializedControlState ::= SkipState | RestoreState
|
|
// SkipState ::= '0'
|
|
// RestoreState ::= UnsignedNumber, ControlValue+
|
|
// UnsignedNumber ::= [0-9]+
|
|
// ControlValue ::= arbitrary string
|
|
//
|
|
// RestoreState has a sequence of ControlValues. The length of the
|
|
// sequence is represented by UnsignedNumber.
|
|
|
|
static inline void serializeFormControlStateTo(const FormControlState& formControlState, Vector<String>& stateVector)
|
|
{
|
|
stateVector.append(String::number(formControlState.size()));
|
|
for (auto& value : formControlState)
|
|
stateVector.append(value.isNull() ? emptyString() : value);
|
|
}
|
|
|
|
static inline std::optional<FormControlState> deserializeFormControlState(const Vector<String>& stateVector, size_t& index)
|
|
{
|
|
if (index >= stateVector.size())
|
|
return std::nullopt;
|
|
auto size = parseIntegerAllowingTrailingJunk<size_t>(stateVector[index++]).value_or(0);
|
|
if (index + size > stateVector.size())
|
|
return std::nullopt;
|
|
Vector<String> subvector;
|
|
subvector.reserveInitialCapacity(size);
|
|
for (size_t i = 0; i < size; ++i)
|
|
subvector.uncheckedAppend(stateVector[index++]);
|
|
return subvector;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
class FormElementKey {
|
|
public:
|
|
explicit FormElementKey(AtomStringImpl* = nullptr, AtomStringImpl* = nullptr);
|
|
~FormElementKey();
|
|
FormElementKey(const FormElementKey&);
|
|
FormElementKey& operator=(const FormElementKey&);
|
|
|
|
AtomStringImpl* name() const { return m_name; }
|
|
AtomStringImpl* type() const { return m_type; }
|
|
|
|
// Hash table deleted values, which are only constructed and never copied or destroyed.
|
|
FormElementKey(WTF::HashTableDeletedValueType) : m_name(hashTableDeletedValue()) { }
|
|
bool isHashTableDeletedValue() const { return m_name == hashTableDeletedValue(); }
|
|
|
|
private:
|
|
void ref() const;
|
|
void deref() const;
|
|
|
|
static AtomStringImpl* hashTableDeletedValue() { return reinterpret_cast<AtomStringImpl*>(-1); }
|
|
|
|
AtomStringImpl* m_name;
|
|
AtomStringImpl* m_type;
|
|
};
|
|
|
|
FormElementKey::FormElementKey(AtomStringImpl* name, AtomStringImpl* type)
|
|
: m_name(name)
|
|
, m_type(type)
|
|
{
|
|
ref();
|
|
}
|
|
|
|
FormElementKey::~FormElementKey()
|
|
{
|
|
deref();
|
|
}
|
|
|
|
FormElementKey::FormElementKey(const FormElementKey& other)
|
|
: m_name(other.name())
|
|
, m_type(other.type())
|
|
{
|
|
ref();
|
|
}
|
|
|
|
FormElementKey& FormElementKey::operator=(const FormElementKey& other)
|
|
{
|
|
other.ref();
|
|
deref();
|
|
m_name = other.name();
|
|
m_type = other.type();
|
|
return *this;
|
|
}
|
|
|
|
void FormElementKey::ref() const
|
|
{
|
|
if (name())
|
|
name()->ref();
|
|
if (type())
|
|
type()->ref();
|
|
}
|
|
|
|
void FormElementKey::deref() const
|
|
{
|
|
if (name())
|
|
name()->deref();
|
|
if (type())
|
|
type()->deref();
|
|
}
|
|
|
|
inline bool operator==(const FormElementKey& a, const FormElementKey& b)
|
|
{
|
|
return a.name() == b.name() && a.type() == b.type();
|
|
}
|
|
|
|
struct FormElementKeyHash {
|
|
static unsigned hash(const FormElementKey&);
|
|
static bool equal(const FormElementKey& a, const FormElementKey& b) { return a == b; }
|
|
static const bool safeToCompareToEmptyOrDeleted = true;
|
|
};
|
|
|
|
unsigned FormElementKeyHash::hash(const FormElementKey& key)
|
|
{
|
|
return StringHasher::hashMemory<sizeof(FormElementKey)>(&key);
|
|
}
|
|
|
|
struct FormElementKeyHashTraits : HashTraits<FormElementKey> {
|
|
static void constructDeletedValue(FormElementKey& slot) { new (NotNull, &slot) FormElementKey(WTF::HashTableDeletedValue); }
|
|
static bool isDeletedValue(const FormElementKey& value) { return value.isHashTableDeletedValue(); }
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
class SavedFormState {
|
|
WTF_MAKE_NONCOPYABLE(SavedFormState);
|
|
WTF_MAKE_FAST_ALLOCATED;
|
|
|
|
public:
|
|
SavedFormState() = default;
|
|
static std::unique_ptr<SavedFormState> deserialize(const Vector<String>&, size_t& index);
|
|
void serializeTo(Vector<String>&) const;
|
|
bool isEmpty() const { return m_stateForNewFormElements.isEmpty(); }
|
|
void appendControlState(const AtomString& name, const AtomString& type, const FormControlState&);
|
|
FormControlState takeControlState(const AtomString& name, const AtomString& type);
|
|
|
|
Vector<String> referencedFilePaths() const;
|
|
|
|
private:
|
|
HashMap<FormElementKey, Deque<FormControlState>, FormElementKeyHash, FormElementKeyHashTraits> m_stateForNewFormElements;
|
|
size_t m_controlStateCount { 0 };
|
|
};
|
|
|
|
static bool isNotFormControlTypeCharacter(UChar ch)
|
|
{
|
|
return !(ch == '-' || isASCIILower(ch));
|
|
}
|
|
|
|
std::unique_ptr<SavedFormState> SavedFormState::deserialize(const Vector<String>& stateVector, size_t& index)
|
|
{
|
|
if (index >= stateVector.size())
|
|
return nullptr;
|
|
auto itemCount = parseIntegerAllowingTrailingJunk<size_t>(stateVector[index++]).value_or(0);
|
|
if (!itemCount)
|
|
return nullptr;
|
|
auto savedFormState = makeUnique<SavedFormState>();
|
|
while (itemCount--) {
|
|
if (index + 1 >= stateVector.size())
|
|
return nullptr;
|
|
String name = stateVector[index++];
|
|
String type = stateVector[index++];
|
|
auto state = deserializeFormControlState(stateVector, index);
|
|
if (type.isEmpty() || type.find(isNotFormControlTypeCharacter) != notFound || !state)
|
|
return nullptr;
|
|
savedFormState->appendControlState(name, type, state.value());
|
|
}
|
|
return savedFormState;
|
|
}
|
|
|
|
void SavedFormState::serializeTo(Vector<String>& stateVector) const
|
|
{
|
|
stateVector.append(String::number(m_controlStateCount));
|
|
for (auto& element : m_stateForNewFormElements) {
|
|
const FormElementKey& key = element.key;
|
|
for (auto& controlState : element.value) {
|
|
stateVector.append(key.name());
|
|
stateVector.append(key.type());
|
|
serializeFormControlStateTo(controlState, stateVector);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SavedFormState::appendControlState(const AtomString& name, const AtomString& type, const FormControlState& state)
|
|
{
|
|
m_stateForNewFormElements.add(FormElementKey { name.impl(), type.impl() }, Deque<FormControlState> { }).iterator->value.append(state);
|
|
++m_controlStateCount;
|
|
}
|
|
|
|
FormControlState SavedFormState::takeControlState(const AtomString& name, const AtomString& type)
|
|
{
|
|
auto iterator = m_stateForNewFormElements.find(FormElementKey { name.impl(), type.impl() });
|
|
if (iterator == m_stateForNewFormElements.end())
|
|
return { };
|
|
|
|
auto state = iterator->value.takeFirst();
|
|
--m_controlStateCount;
|
|
if (iterator->value.isEmpty())
|
|
m_stateForNewFormElements.remove(iterator);
|
|
return state;
|
|
}
|
|
|
|
Vector<String> SavedFormState::referencedFilePaths() const
|
|
{
|
|
Vector<String> toReturn;
|
|
for (auto& element : m_stateForNewFormElements) {
|
|
if (!equal(element.key.type(), "file", 4))
|
|
continue;
|
|
for (auto& state : element.value) {
|
|
for (auto& file : HTMLInputElement::filesFromFileInputFormControlState(state))
|
|
toReturn.append(file.path);
|
|
}
|
|
}
|
|
return toReturn;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
class FormKeyGenerator {
|
|
WTF_MAKE_NONCOPYABLE(FormKeyGenerator);
|
|
WTF_MAKE_FAST_ALLOCATED;
|
|
|
|
public:
|
|
FormKeyGenerator() = default;
|
|
AtomString formKey(const HTMLFormControlElementWithState&);
|
|
void willDeleteForm(HTMLFormElement*);
|
|
|
|
private:
|
|
WeakHashMap<HTMLFormElement, AtomString> m_formToKeyMap;
|
|
HashMap<String, unsigned> m_formSignatureToNextIndexMap;
|
|
};
|
|
|
|
static inline void recordFormStructure(const HTMLFormElement& form, StringBuilder& builder)
|
|
{
|
|
ScriptDisallowedScope::InMainThread scriptDisallowedScope;
|
|
// 2 is enough to distinguish forms in webkit.org/b/91209#c0
|
|
const size_t namedControlsToBeRecorded = 2;
|
|
auto& controls = form.unsafeAssociatedElements();
|
|
builder.append(" [");
|
|
for (size_t i = 0, namedControls = 0; i < controls.size() && namedControls < namedControlsToBeRecorded; ++i) {
|
|
auto* formAssociatedElement = controls[i]->asFormAssociatedElement();
|
|
if (!formAssociatedElement->isFormControlElementWithState())
|
|
continue;
|
|
RefPtr<HTMLFormControlElementWithState> control = static_cast<HTMLFormControlElementWithState*>(formAssociatedElement);
|
|
if (!ownerFormForState(*control))
|
|
continue;
|
|
AtomString name = control->name();
|
|
if (name.isEmpty())
|
|
continue;
|
|
namedControls++;
|
|
builder.append(name, ' ');
|
|
}
|
|
builder.append(']');
|
|
}
|
|
|
|
static inline String formSignature(const HTMLFormElement& form)
|
|
{
|
|
URL actionURL = form.getURLAttribute(actionAttr);
|
|
// Remove the query part because it might contain volatile parameters such
|
|
// as a session key.
|
|
actionURL.setQuery(String());
|
|
StringBuilder builder;
|
|
if (!actionURL.isEmpty())
|
|
builder.append(actionURL.string());
|
|
|
|
recordFormStructure(form, builder);
|
|
return builder.toString();
|
|
}
|
|
|
|
AtomString FormKeyGenerator::formKey(const HTMLFormControlElementWithState& control)
|
|
{
|
|
auto form = makeRefPtr(ownerFormForState(control));
|
|
if (!form) {
|
|
static MainThreadNeverDestroyed<const AtomString> formKeyForNoOwner("No owner", AtomString::ConstructFromLiteral);
|
|
return formKeyForNoOwner;
|
|
}
|
|
|
|
return m_formToKeyMap.ensure(*form, [this, &form] {
|
|
auto signature = formSignature(*form);
|
|
auto nextIndex = m_formSignatureToNextIndexMap.add(signature, 0).iterator->value++;
|
|
// FIXME: Would be nice to have makeAtomString to use to optimize the case where the string already exists.
|
|
return makeString(signature, " #", nextIndex);
|
|
}).iterator->value;
|
|
}
|
|
|
|
void FormKeyGenerator::willDeleteForm(HTMLFormElement* form)
|
|
{
|
|
RELEASE_ASSERT(form);
|
|
m_formToKeyMap.remove(*form);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
FormController::FormController() = default;
|
|
|
|
FormController::~FormController() = default;
|
|
|
|
static String formStateSignature()
|
|
{
|
|
// In the legacy version of serialized state, the first item was a name
|
|
// attribute value of a form control. The following string literal should
|
|
// contain some characters which are rarely used for name attribute values.
|
|
static NeverDestroyed<String> signature(MAKE_STATIC_STRING_IMPL("\n\r?% WebKit serialized form state version 8 \n\r=&"));
|
|
return signature;
|
|
}
|
|
|
|
std::unique_ptr<FormController::SavedFormStateMap> FormController::createSavedFormStateMap(const FormControlVector& controlList)
|
|
{
|
|
FormKeyGenerator keyGenerator;
|
|
auto stateMap = makeUnique<SavedFormStateMap>();
|
|
for (const HTMLFormControlElementWithState& control : controlList) {
|
|
if (!control.shouldSaveAndRestoreFormControlState())
|
|
continue;
|
|
auto& formState = stateMap->add(keyGenerator.formKey(control).impl(), nullptr).iterator->value;
|
|
if (!formState)
|
|
formState = makeUnique<SavedFormState>();
|
|
formState->appendControlState(control.name(), control.type(), control.saveFormControlState());
|
|
}
|
|
return stateMap;
|
|
}
|
|
|
|
Vector<String> FormController::formElementsState(const Document& document) const
|
|
{
|
|
// FIXME: We should be saving the state of form controls in shadow trees, too.
|
|
FormControlVector controls;
|
|
for (auto& control : descendantsOfType<HTMLFormControlElementWithState>(document)) {
|
|
ASSERT(control.insertionIndex());
|
|
controls.append(control);
|
|
}
|
|
|
|
std::sort(controls.begin(), controls.end(), [](auto a, auto b) {
|
|
return a.get().insertionIndex() < b.get().insertionIndex();
|
|
});
|
|
|
|
auto stateMap = createSavedFormStateMap(controls);
|
|
Vector<String> stateVector;
|
|
stateVector.reserveInitialCapacity(controls.size() * 4);
|
|
stateVector.append(formStateSignature());
|
|
for (auto& state : *stateMap) {
|
|
stateVector.append(state.key.get());
|
|
state.value->serializeTo(stateVector);
|
|
}
|
|
bool hasOnlySignature = stateVector.size() == 1;
|
|
if (hasOnlySignature)
|
|
stateVector.clear();
|
|
return stateVector;
|
|
}
|
|
|
|
void FormController::setStateForNewFormElements(const Vector<String>& stateVector)
|
|
{
|
|
formStatesFromStateVector(stateVector, m_savedFormStateMap);
|
|
}
|
|
|
|
FormControlState FormController::takeStateForFormElement(const HTMLFormControlElementWithState& control)
|
|
{
|
|
if (m_savedFormStateMap.isEmpty())
|
|
return FormControlState();
|
|
if (!m_formKeyGenerator)
|
|
m_formKeyGenerator = makeUnique<FormKeyGenerator>();
|
|
SavedFormStateMap::iterator it = m_savedFormStateMap.find(m_formKeyGenerator->formKey(control).impl());
|
|
if (it == m_savedFormStateMap.end())
|
|
return FormControlState();
|
|
FormControlState state = it->value->takeControlState(control.name(), control.type());
|
|
if (it->value->isEmpty())
|
|
m_savedFormStateMap.remove(it);
|
|
return state;
|
|
}
|
|
|
|
void FormController::formStatesFromStateVector(const Vector<String>& stateVector, SavedFormStateMap& map)
|
|
{
|
|
map.clear();
|
|
|
|
size_t i = 0;
|
|
if (stateVector.size() < 1 || stateVector[i++] != formStateSignature())
|
|
return;
|
|
|
|
while (i + 1 < stateVector.size()) {
|
|
AtomString formKey = stateVector[i++];
|
|
auto state = SavedFormState::deserialize(stateVector, i);
|
|
if (!state) {
|
|
i = 0;
|
|
break;
|
|
}
|
|
map.add(formKey.impl(), WTFMove(state));
|
|
}
|
|
if (i != stateVector.size())
|
|
map.clear();
|
|
}
|
|
|
|
void FormController::willDeleteForm(HTMLFormElement& form)
|
|
{
|
|
if (m_formKeyGenerator)
|
|
m_formKeyGenerator->willDeleteForm(&form);
|
|
}
|
|
|
|
void FormController::restoreControlStateFor(HTMLFormControlElementWithState& control)
|
|
{
|
|
// We don't save state of a control with shouldSaveAndRestoreFormControlState()
|
|
// == false. But we need to skip restoring process too because a control in
|
|
// another form might have the same pair of name and type and saved its state.
|
|
if (!control.shouldSaveAndRestoreFormControlState())
|
|
return;
|
|
if (ownerFormForState(control))
|
|
return;
|
|
auto state = takeStateForFormElement(control);
|
|
if (!state.isEmpty())
|
|
control.restoreFormControlState(state);
|
|
}
|
|
|
|
void FormController::restoreControlStateIn(HTMLFormElement& form)
|
|
{
|
|
for (auto& element : form.copyAssociatedElementsVector()) {
|
|
if (!is<HTMLFormControlElementWithState>(element.get()))
|
|
continue;
|
|
auto& control = downcast<HTMLFormControlElementWithState>(element.get());
|
|
if (!control.shouldSaveAndRestoreFormControlState())
|
|
continue;
|
|
if (ownerFormForState(control) != &form)
|
|
continue;
|
|
auto state = takeStateForFormElement(control);
|
|
if (!state.isEmpty())
|
|
control.restoreFormControlState(state);
|
|
}
|
|
}
|
|
|
|
bool FormController::hasFormStateToRestore() const
|
|
{
|
|
return !m_savedFormStateMap.isEmpty();
|
|
}
|
|
|
|
Vector<String> FormController::referencedFilePaths(const Vector<String>& stateVector)
|
|
{
|
|
Vector<String> paths;
|
|
SavedFormStateMap map;
|
|
formStatesFromStateVector(stateVector, map);
|
|
for (auto& state : map.values())
|
|
paths.appendVector(state->referencedFilePaths());
|
|
return paths;
|
|
}
|
|
|
|
} // namespace WebCore
|