blob: fc7d43be8271142dc126ecd0fc080809c7d46e4a [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill/content/browser/form_forest.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase_vector.h"
#include "base/containers/stack.h"
#include "base/debug/dump_without_crashing.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/stl_util.h"
#include "components/autofill/content/browser/form_forest_util_inl.h"
#include "components/autofill/core/common/autofill_features.h"
#include "content/public/browser/render_process_host.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy_features.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
// AFCHECK(condition[, error_handler]) creates a crash dump and executes
// |error_handler| if |condition| is false.
// TODO(https://crbug.com/1187842): Replace AFCHECK() with DCHECK().
#define AFCHECK(condition, ...) \
if (!(condition)) { \
SCOPED_CRASH_KEY_STRING256("autofill", "main_url", MainUrlForDebugging()); \
AFCRASHDUMP(); \
__VA_ARGS__; \
}
#if DCHECK_IS_ON()
#define AFCRASHDUMP() DCHECK(false)
#else
#define AFCRASHDUMP() base::debug::DumpWithoutCrashing()
#endif
namespace autofill::internal {
namespace {
// Indicates if |rfh| is a fenced frame (https://crbug.com/1111084).
//
// We do not want to fill across the boundary of a fenced frame. Hence, a fenced
// frame's FrameData must be disconnected (in terms of FormData::child_frames
// and FrameData::parent_form) from its parent form. This is already guaranteed
// because FormData::child_frames does not contain fenced frames. However,
// UpdateTreeOfRendererForm() would still invoke TriggerReparse() to detect the
// parent form. IsFencedFrameRoot() should be implemented to suppress this.
//
// We also do not want to fill across iframes with the disallowdocumentaccess
// attribute (https://crbug.com/961448). Since disallowdocumentaccess is
// currently not going to ship and supporting it requires significant additional
// work in UpdateTreeOfRendererForm() to remove FormData::child_frame and unset
// FrameData::parent_form for frames that disallow document access, there is no
// immediate need to support it. See https://crrev.com/c/3055422 for a draft
// implementation.
bool IsFencedFrameRoot(content::RenderFrameHost* rfh) {
return rfh->IsFencedFrameRoot();
}
} // namespace
FormForest::FrameData::FrameData(LocalFrameToken frame_token)
: frame_token(frame_token) {}
FormForest::FrameData::~FrameData() = default;
FormForest::FormForest() = default;
FormForest::~FormForest() = default;
std::string FormForest::MainUrlForDebugging() const {
content::RenderFrameHost* some_rfh =
content::RenderFrameHost::FromID(some_rfh_for_debugging_);
if (!some_rfh) {
for (const auto& frame_data : frame_datas_) {
if (frame_data && frame_data->driver)
some_rfh = frame_data->driver->render_frame_host();
}
}
if (!some_rfh)
return std::string();
return some_rfh->GetMainFrame()->GetLastCommittedURL().spec();
}
absl::optional<LocalFrameToken> FormForest::Resolve(const FrameData& reference,
FrameToken query) {
if (absl::holds_alternative<LocalFrameToken>(query))
return absl::get<LocalFrameToken>(query);
DCHECK(absl::holds_alternative<RemoteFrameToken>(query));
AFCHECK(reference.driver, return absl::nullopt);
content::RenderFrameHost* rfh = reference.driver->render_frame_host();
AFCHECK(rfh, return absl::nullopt);
content::RenderProcessHost* rph = rfh->GetProcess();
AFCHECK(rph, return absl::nullopt);
blink::RemoteFrameToken blink_remote_token(
absl::get<RemoteFrameToken>(query).value());
content::RenderFrameHost* remote_rfh =
content::RenderFrameHost::FromPlaceholderToken(rph->GetID(),
blink_remote_token);
if (!remote_rfh)
return absl::nullopt;
// TODO(https://crbug.com/1310047): The RFH is a child and we will not
// flatten fenced frames, so the RFH cannot be a fenced frame.
CHECK(!remote_rfh->IsFencedFrameRoot());
return LocalFrameToken(remote_rfh->GetFrameToken().value());
}
FormForest::FrameData* FormForest::GetOrCreateFrameData(LocalFrameToken frame) {
auto it = frame_datas_.find(frame);
if (it == frame_datas_.end())
it = frame_datas_.insert(it, std::make_unique<FrameData>(frame));
AFCHECK(it != frame_datas_.end());
AFCHECK(it->get());
return it->get();
}
FormForest::FrameData* FormForest::GetFrameData(LocalFrameToken frame) {
auto it = frame_datas_.find(frame);
return it != frame_datas_.end() ? it->get() : nullptr;
}
FormData* FormForest::GetFormData(FormGlobalId form, FrameData* frame_data) {
DCHECK(!frame_data || frame_data == GetFrameData(form.frame_token));
if (!frame_data)
frame_data = GetFrameData(form.frame_token);
if (!frame_data)
return nullptr;
auto it = base::ranges::find(frame_data->child_forms, form.renderer_id,
&FormData::unique_renderer_id);
return it != frame_data->child_forms.end() ? &*it : nullptr;
}
FormForest::FrameAndForm FormForest::GetRoot(FormGlobalId form) {
for (;;) {
FrameData* frame = GetFrameData(form.frame_token);
AFCHECK(frame, return {nullptr, nullptr});
if (!frame->parent_form) {
auto it = base::ranges::find(frame->child_forms, form.renderer_id,
&FormData::unique_renderer_id);
AFCHECK(it != frame->child_forms.end(), return {nullptr, nullptr});
return {frame, &*it};
}
form = *frame->parent_form;
}
}
void FormForest::EraseReferencesTo(
absl::variant<LocalFrameToken, FormGlobalId> frame_or_form,
base::flat_set<FormGlobalId>* forms_with_removed_fields) {
auto Match = [&](FormGlobalId form) {
return absl::holds_alternative<LocalFrameToken>(frame_or_form)
? absl::get<LocalFrameToken>(frame_or_form) == form.frame_token
: absl::get<FormGlobalId>(frame_or_form) == form;
};
for (std::unique_ptr<FrameData>& some_frame : frame_datas_) {
AFCHECK(some_frame, continue);
for (FormData& some_form : some_frame->child_forms) {
size_t num_removed =
base::EraseIf(some_form.fields, [&](const FormFieldData& some_form) {
return Match(some_form.renderer_form_id());
});
if (num_removed > 0 && forms_with_removed_fields) {
AFCHECK(!some_frame->parent_form);
forms_with_removed_fields->insert(some_form.global_id());
}
}
if (some_frame->parent_form && Match(*some_frame->parent_form))
some_frame->parent_form = absl::nullopt;
}
}
base::flat_set<FormGlobalId> FormForest::EraseForms(
base::span<const FormGlobalId> renderer_forms) {
for (const FormGlobalId renderer_form : renderer_forms) {
if (FrameData* frame = GetFrameData(renderer_form.frame_token)) {
base::EraseIf(frame->child_forms, [&](const FormData& some_form) {
return some_form.global_id() == renderer_form;
});
}
}
base::flat_set<FormGlobalId> forms_with_removed_fields;
for (const FormGlobalId renderer_form : renderer_forms) {
if (FrameData* frame = GetFrameData(renderer_form.frame_token)) {
EraseReferencesTo(renderer_form, &forms_with_removed_fields);
}
}
return forms_with_removed_fields;
}
void FormForest::EraseFrame(LocalFrameToken frame) {
some_rfh_for_debugging_ = content::GlobalRenderFrameHostId();
if (frame_datas_.erase(frame)) {
EraseReferencesTo(frame, /*forms_with_removed_fields=*/nullptr);
}
}
// Maintains the following invariants:
// 1. The graph is a disjoint union of trees, and the trees faithfully represent
// the frame-transcending forms in the DOMs.
// 2. The root forms of each tree contain (only) the nodes of all forms in
// the subtree in DOM order, and all non-root forms have no fields.
//
// We keep the FormData::child_frame and FrameData::parent_form relations in
// symmetry to ease reasoning about the tree. Since children are predetermined
// by FormData:child_frame, we do so by always updating the childrens'
// FrameData::parent_form.
//
// If |form| is not part of a frame-transcending form, the function has minimal
// overhead. That is, if |form|'s FormData::child_frames is empty and |form|'s
// frame has no parent frame, the function reduces to a few moves and index
// lookups.
//
// If the FrameData::parent_form of |form|'s frame is not set although a parent
// frame exists, the function triggers a reparse in the parent frame. This will
// trigger an UpdateTreeOfRendererForm() for the true parent form (amongst
// others), which will then also set the child frame's FrameData::parent_form.
void FormForest::UpdateTreeOfRendererForm(FormData* form,
ContentAutofillDriver* driver) {
SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
"Autofill.FormForest.UpdateTreeOfRendererForm.Duration");
AFCHECK(form, return );
AFCHECK(driver, return );
AFCHECK(form->host_frame, return );
some_rfh_for_debugging_ = driver->render_frame_host()->GetGlobalId();
FrameData* frame = GetOrCreateFrameData(form->host_frame);
AFCHECK(frame, return );
AFCHECK(!frame->driver || frame->driver == driver, return );
frame->driver = driver;
content::RenderFrameHost* rfh = driver->render_frame_host();
AFCHECK(rfh, return );
AFCHECK(form->host_frame == LocalFrameToken(rfh->GetFrameToken().value()),
return );
// Moves |form| into its |frame|'s FrameData::child_forms, with a special
// treatment of the fields: |form|'s fields are replaced with |old_form|'s
// fields if |form| had previously been known as |old_form|.
//
// Retaining the old fields is important because |old_form| may have been a
// root and therefore contained fields from other forms. The relevant old and
// new fields will be moved to |form|'s root later.
//
// Also unsets the FrameData::parent_form pointer for newly removed children.
// Usually, a removed child frame has been or will be destroyed. However, a
// child frame may also be removed because the frame became invisible. For
// simplicity, we do not move fields from |form|'s root back to the former
// children. Instead, we rely on the descendant frames being reparsed before
// they become visible again.
std::vector<FormFieldData> form_fields = std::move(form->fields);
bool child_frames_changed;
if (FormData* old_form = GetFormData(form->global_id(), frame)) {
form->fields = std::move(old_form->fields);
child_frames_changed = old_form->child_frames != form->child_frames;
for_each_in_set_difference(
old_form->child_frames, form->child_frames,
[this, frame](FrameToken removed_child_token) {
absl::optional<LocalFrameToken> local_child =
Resolve(*frame, removed_child_token);
FrameData* child_frame;
if (local_child && (child_frame = GetFrameData(*local_child)))
child_frame->parent_form = absl::nullopt;
},
&FrameTokenWithPredecessor::token);
*old_form = std::move(*form);
form = old_form;
} else {
DCHECK(!base::Contains(frame->child_forms, form->unique_renderer_id,
&FormData::unique_renderer_id));
form->fields = {};
child_frames_changed = false;
frame->child_forms.push_back(std::move(*form));
form = &frame->child_forms.back();
}
DCHECK(form && form == GetFormData(form->global_id()));
// Do *NOT* modify any FrameData::child_forms after this line!
// Doing so may resize FrameData::child_forms, while we keep raw pointers to
// individual items.
// Traverses |form|'s tree starting at its root to set FrameData::parent_form
// and update the root form's fields, while keeping them in DOM order:
// - adds the fields from |form_fields| to `root->fields`;
// - removes fields from `root->fields` that originated from |old_form|'s
// renderer form but are not in |form_fields|;
// - removes fields that originated from frames that have been removed from
// |form| as well as fields from their descendant frames;
// - moves fields from former root forms that are now children of |form|
// from that form to `root->fields`.
//
// We perform all these tasks in a single pre-order depth-first traversal of
// |form|'s tree. That is, every field of a form is visited before/after
// recursing into the subframes of the form that come after/before that field.
// Only if |form|'s tree is trivial (consists of a single form), we can take
// an abbreviation and just move the fields.
if (!frame->parent_form && form->child_frames.empty() &&
!child_frames_changed) {
form->fields = std::move(form_fields);
} else {
FrameAndForm root = GetRoot(form->global_id());
AFCHECK(root, return );
// Moves the first |max_number_of_fields_to_be_moved| fields that originated
// from the renderer form |source_form| from |source| to |target|.
// Default-initializes each source field after its move to prevent it from
// being moved in a future call.
//
// Calling MoveFields() repeatedly to move one field at a time runs in
// quadratic time due to the comparisons of the fields' renderer_form_id().
// The cost could be reduced to linear time by reversing the source vectors
// and moving fields from the back of |source|. However, the cost of
// reversing likely exceeds the cost of the renderer_form_id() comparisons.
auto MoveFields = [](size_t max_number_of_fields_to_be_moved,
FormGlobalId source_form,
std::vector<FormFieldData>& source,
std::vector<FormFieldData>& target) {
DCHECK_NE(&source, &target);
size_t number_of_fields_moved = 0;
for (FormFieldData& f : source) {
if (f.renderer_form_id() == source_form) {
if (++number_of_fields_moved > max_number_of_fields_to_be_moved)
break;
target.push_back(std::move(f));
f = {}; // Clobber |f| in |source| so future MoveFields() skip it.
DCHECK(!f.renderer_form_id());
}
}
};
// The |frontier| contains the Nodes to be expanded in LIFO order. Each Node
// represents the range of fields and the next frame to be visited.
//
// A Node consists of
// - the Node::form whose fields and child frames are to be visited;
// - the Node::frame that hosts Node::form;
// - the Node::next_frame to be traversed after visiting the fields that
// precede this frame in the form.
//
// For example, consider a Node |n| whose form has 4 child frames and at
// least 5 fields:
// <form>
// <iframe></iframe>
// <input id="0">
// <input id="1">
// <input id="2">
// <iframe></iframe>
// <iframe></iframe>
// <input id="3">
// <input id="4">
// <iframe></iframe>
// <input id="5">
// ...
// </form>
// The relative order of fields and frames is encoded in
// `n.form->child_frames[i].predecessor`: the value represents which field
// from `n.form`, if any, precedes the frame in DOM order. In this example:
// - `n.form->child_frames[0].predecessor == -1`;
// - `n.form->child_frames[1].predecessor == 2`;
// - `n.form->child_frames[2].predecessor == 2`;
// - `n.form->child_frames[3].predecessor == 4`.
// Then, the field range represented by |n| depends on `n.next_frame`:
// - `n.next_frame == 0` represents the empty range;
// - `n.next_frame == 1` represents the fields with indices 0, 1, 2;
// - `n.next_frame == 2` represents the empty range;
// - `n.next_frame == 3` represents the fields with indices 3, 4;
// - `n.next_frame == 4` represents the fields with indices greater than 4.
//
// Note that the fields of `n.form` are typically not stored in
// `n.form->fields`:
// - If `n.form` refers to the passed |form| (`n.form == form`), then these
// fields have been moved to |form_fields|.
// - Otherwise, they are stored in `n.form`'s root form.
//
// Since the relative order of the fields is the same as in the renderer
// form, the index |i| of a field from `n.form` translates to the |i|th
// field |f| from the respective vector that contains the fields from
// `n.form` for which `f.renderer_form_id() == n.form->global_id()`.
//
// When the traversal expands a Node |n| from the |frontier|, we
// - pull these fields from |roots_on_path| (defined below) or |form_fields|
// and append them to |root_fields|, the future fields of the root;
// - push on the |frontier|
// - one Node for each child form in that frame, and
// - a Node with incremented `n.next_frame` for the successor frame and
// its preceding fields
// in an order that ensures DOM-order traversal.
// If Node::next_frame is out of bounds (indicating that all fields and
// frames have been visited already), we omit the latter step.
struct Node {
// This field is not a raw_ptr<> because it was filtered by the rewriter
// for: #reinterpret-cast-trivial-type
RAW_PTR_EXCLUSION FrameData* frame; // Not null.
// This field is not a raw_ptr<> because it was filtered by
// the rewriter for: #reinterpret-cast-trivial-type
RAW_PTR_EXCLUSION FormData* form; // Not null.
size_t next_frame; // In the range [0, `form->child_frames.size()`].
};
base::stack<Node> frontier;
frontier.push({root.frame, root.form, 0});
// Fields to be moved to |root_fields| may not just come from |form_fields|
// or `root.form->fields`, but also from forms that used to be roots but
// have become children of |form|. These former roots contain the fields
// from their subtrees. To access these fields, we store the fields from the
// root as well as from former root nodes (unless they have no fields) in
// |roots_on_path|.
base::stack<FormData*> roots_on_path;
// New fields of the root form. To be populated in the tree traversal.
std::vector<FormFieldData> root_fields;
root_fields.reserve(root.form->fields.size() + form_fields.size());
// We bound the number of visited nodes. We want to visit the field ranges
// of `root.form` plus up to 64 nodes from its descendant frames. (This
// constant may be adjusted if real-world trees tend to be bigger.)
//
// If visiting the field ranges of a frame would push us over the kMaxVisits
// limit, we disconnect that frame's subtrees from `root.form`'s tree.
//
// The invariants are
// - `num_did_visit <= num_will_visit` and
// - `num_will_visit <= kMaxVisits`.
// The latter is immediate. The former holds because
// - |num_did_visit| is the number `frontier.pop()` operations,
// - |num_will_visit| is the number of `frontier.push()` operations.
auto NumChildrenOfForm = [](const FormData& form) {
return form.child_frames.size() + 1;
};
auto NumChildrenOfFrame = [&NumChildrenOfForm](const FrameData& frame) {
size_t num = 0;
for (const FormData& form : frame.child_forms)
num += NumChildrenOfForm(form);
return num;
};
size_t num_did_visit = 0;
size_t num_will_visit = NumChildrenOfForm(*root.form);
const size_t kMaxVisits = num_will_visit + 64;
while (!frontier.empty()) {
++num_did_visit;
AFCHECK(num_did_visit <= num_will_visit, break);
AFCHECK(num_will_visit <= kMaxVisits, break);
Node n = frontier.top();
frontier.pop();
AFCHECK(n.frame, continue);
AFCHECK(n.form, continue);
// Pushes the current form on |roots_on_path| only if this is the first
// time we encounter the form in the traversal (Node::next_frame == 0).
if (n.next_frame == 0 && (n.form == root.form || !n.form->fields.empty()))
roots_on_path.push(n.form);
AFCHECK(!roots_on_path.empty(), continue);
std::vector<FormFieldData>& source =
n.form->global_id() == form->global_id()
? form_fields
: roots_on_path.top()->fields;
// Moves the next fields from `n.form` to |root_fields|.
//
// If `n.next_frame` is in bounds, pushes on the |frontier|
// - one Node for each child form in that frame, and
// - a Node with incremented `n.next_frame` for the successor frame and
// its preceding fields.
// The order of these pushes is inversed so that they're expanded in DOM
// order.
//
// If `n.next_frame` is out of bounds, all paths through |n| have been
// fully traversed. In this case we pop `n.form` from |roots_on_path| if
// applicable.
//
// To avoid excessive computational cost on sites with many forms (e.g.,
// <form><input></form> x 200 x multiple frames), the traversal does not
// descend into `n.form`'s |child_frame| if visiting |child_frame|'s field
// ranges would push us over the kMaxVisits limit. In this case, we
// disconnect the subtrees of |child_frame| from |n|.
//
// Note that an earlier tree traversal may have moved some fields from
// |child_frame|'s subtrees to `root.form` (or a former root). The present
// tree traversal implicitly deletes such fields from `root.form`. We
// intentionally do not move them back to |child_frame|'s subtree because
// (a) this would add a lot of complexity just to handle a rare special
// case, and (b) the fields will re-occur in |child_frame|'s subtree once
// UpdateTreeOfRendererForm() is called for their renderer forms.
if (n.next_frame < n.form->child_frames.size()) {
// [begin, end) is the range of fields from `n.form` before
// `n.next_frame`.
size_t begin = base::checked_cast<size_t>(
n.next_frame > 0
? n.form->child_frames[n.next_frame - 1].predecessor + 1
: 0);
size_t end = base::checked_cast<size_t>(
n.form->child_frames[n.next_frame].predecessor + 1);
AFCHECK(begin <= end, continue);
MoveFields(end - begin, n.form->global_id(), source, root_fields);
// Pushes the right-sibling field range of |n| onto the stack.
frontier.push(
{.frame = n.frame, .form = n.form, .next_frame = n.next_frame + 1});
// Pushes the child field ranges of |n| onto the stack. To ensure DOM
// order, we do so in reverse order and after the right sibling.
//
// Even if a |child_frame| isn't known yet, we create its FrameData and
// set its FrameData::parent_frame to avoid a reparse of `n.frame` when
// a form is seen in |child_frame|.
//
// If visiting |child_frame|'s field ranges would push us over the
// kMaxVisits limit, we disconnect the |child_frame| from `n.form` by
// unsetting FrameData::parent_form.
absl::optional<LocalFrameToken> local_child =
Resolve(*n.frame, n.form->child_frames[n.next_frame].token);
FrameData* child_frame;
if (local_child && (child_frame = GetOrCreateFrameData(*local_child))) {
num_will_visit += NumChildrenOfFrame(*child_frame);
if (num_will_visit > kMaxVisits) {
num_will_visit -= NumChildrenOfFrame(*child_frame);
child_frame->parent_form = absl::nullopt;
} else {
child_frame->parent_form = n.form->global_id();
for (size_t i = child_frame->child_forms.size(); i > 0; --i) {
frontier.push({.frame = child_frame,
.form = &child_frame->child_forms[i - 1],
.next_frame = 0});
}
}
}
} else {
MoveFields(std::numeric_limits<size_t>::max(), n.form->global_id(),
source, root_fields);
if (n.form == roots_on_path.top()) {
roots_on_path.top()->fields.clear();
roots_on_path.pop();
}
}
}
AFCHECK(num_did_visit == num_will_visit);
root.form->fields = std::move(root_fields);
base::UmaHistogramCounts100(
"Autofill.FormForest.UpdateTreeOfRendererForm.Visits", num_did_visit);
}
// Triggers a reparse in a parent frame if `frame->parent_form` is unset.
//
// If |frame| has a parent frame and is not a fenced frame, there are two
// scenarios where `frame->parent_form` is unset:
// - The parent frame hasn't been processed by UpdateTreeOfRendererForm() yet.
// - The parent form did not include the correct token of |frame| in its
// FormData::child_frames (for example, because loading a cross-origin page
// into the <iframe> has changed |frame|'s FrameToken).
//
// In this case, we trigger a reparse in the parent frame. As a result,
// UpdateTreeOfRendererForm() will be called for the parent form, whose
// FormData::child_frames now include |frame|.
content::RenderFrameHost* parent_rfh = rfh->GetParent();
if (!frame->parent_form && parent_rfh && !IsFencedFrameRoot(rfh)) {
LocalFrameToken parent_frame_token(
LocalFrameToken(parent_rfh->GetFrameToken().value()));
FrameData* parent_frame = GetFrameData(parent_frame_token);
if (parent_frame && parent_frame->driver) {
AFCHECK(parent_frame->driver->render_frame_host() == parent_rfh);
parent_frame->driver->TriggerReparse();
}
}
}
const FormData* FormForest::GetBrowserForm(FormGlobalId renderer_form) const {
SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
"Autofill.FormForest.GetBrowserFormOfRendererForm.Duration");
AFCHECK(renderer_form.frame_token, return nullptr);
// For calling non-const-qualified getters.
FormForest& mutable_this = *const_cast<FormForest*>(this);
FormData* form = mutable_this.GetRoot(renderer_form).form;
AFCHECK(form, return nullptr);
return form;
}
FormForest::RendererForms::RendererForms() = default;
FormForest::RendererForms::RendererForms(RendererForms&&) = default;
FormForest::RendererForms& FormForest::RendererForms::operator=(
RendererForms&&) = default;
FormForest::RendererForms::~RendererForms() = default;
FormForest::RendererForms FormForest::GetRendererFormsOfBrowserForm(
const FormData& browser_form,
const url::Origin& triggered_origin,
const base::flat_map<FieldGlobalId, ServerFieldType>& field_type_map)
const {
SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
"Autofill.FormForest.GetRendererFormsOfBrowserForm.Duration");
AFCHECK(browser_form.host_frame, RendererForms result;
result.renderer_forms = {browser_form}; return result);
// For calling non-const-qualified getters.
FormForest& mutable_this = *const_cast<FormForest*>(this);
// Reinstates the fields of |browser_form| in copies of their renderer forms.
// See the function's documentation in the header for details on the security
// policy |IsSafeToFill|.
RendererForms result;
for (const FormFieldData& browser_field : browser_form.fields) {
FormGlobalId form_id = browser_field.renderer_form_id();
// Finds or creates the renderer form from which |browser_field| originated.
// The form with |form_id| may have been removed from the tree, for example,
// between a fill and a refill.
auto renderer_form = base::ranges::find(result.renderer_forms.rbegin(),
result.renderer_forms.rend(),
form_id, &FormData::global_id);
if (renderer_form == result.renderer_forms.rend()) {
const FormData* original_form = mutable_this.GetFormData(form_id);
if (!original_form) // The form with |form_id| may have been removed.
continue;
result.renderer_forms.push_back(*original_form);
renderer_form = result.renderer_forms.rbegin();
renderer_form->fields.clear(); // In case |original_form| is a root form.
}
DCHECK(renderer_form != result.renderer_forms.rend());
auto IsSafeToFill = [&mutable_this, &browser_form, &renderer_form,
&triggered_origin,
&field_type_map](const FormFieldData& field) {
// Non-sensitive values may be filled into fields that belong to the main
// frame's origin. This is independent of the origin of the field that
// triggered the autofill, |triggered_origin|.
auto IsSensitiveFieldType = [](ServerFieldType field_type) {
switch (field_type) {
case CREDIT_CARD_TYPE:
case CREDIT_CARD_NAME_FULL:
case CREDIT_CARD_NAME_FIRST:
case CREDIT_CARD_NAME_LAST:
case CREDIT_CARD_EXP_MONTH:
case CREDIT_CARD_EXP_2_DIGIT_YEAR:
case CREDIT_CARD_EXP_4_DIGIT_YEAR:
case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR:
case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR:
return false;
default:
return true;
}
};
// Fields whose document enables the policy-controlled feature
// shared-autofill may be safe to fill.
auto HasSharedAutofillPermission = [&mutable_this](
LocalFrameToken frame_token) {
FrameData* frame = mutable_this.GetFrameData(frame_token);
return frame && frame->driver && frame->driver->render_frame_host() &&
frame->driver->render_frame_host()->IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kSharedAutofill);
};
const url::Origin& main_origin = browser_form.main_frame_origin;
auto it = field_type_map.find(field.global_id());
ServerFieldType field_type =
it != field_type_map.end() ? it->second : UNKNOWN_TYPE;
if (features::kAutofillSharedAutofillRelaxedParam.Get()) {
return field.origin == triggered_origin ||
(HasSharedAutofillPermission(renderer_form->host_frame) &&
(field.origin != main_origin ||
field_type != CREDIT_CARD_NUMBER));
} else {
return field.origin == triggered_origin ||
(field.origin == main_origin &&
HasSharedAutofillPermission(renderer_form->host_frame) &&
!IsSensitiveFieldType(field_type)) ||
(triggered_origin == main_origin &&
HasSharedAutofillPermission(renderer_form->host_frame));
}
};
renderer_form->fields.push_back(browser_field);
if (!IsSafeToFill(renderer_form->fields.back())) {
renderer_form->fields.back().value.clear();
} else {
result.safe_fields.push_back(browser_field.global_id());
}
}
return result;
}
} // namespace autofill::internal