blob: f372bd0a4b76fdd4411edbbc74ec52b004fee8a5 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/unique_name_helper.h"
#include <algorithm>
#include <utility>
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/unguessable_token.h"
#include "crypto/sha2.h"
namespace content {
namespace {
bool g_preserve_stable_unique_name_for_testing = false;
using FrameAdapter = UniqueNameHelper::FrameAdapter;
class PendingChildFrameAdapter : public UniqueNameHelper::FrameAdapter {
explicit PendingChildFrameAdapter(FrameAdapter* parent) : parent_(parent) {}
// FrameAdapter overrides:
bool IsMainFrame() const override { return false; }
bool IsCandidateUnique(base::StringPiece name) const override {
return parent_->IsCandidateUnique(name);
int GetSiblingCount() const override {
// Note: no adjustment is required here: since this adapter is an internal
// helper, the parent FrameAdapter it delegates to won't know about this
// child to include it in the count.
return parent_->GetChildCount();
int GetChildCount() const override {
return 0;
std::vector<base::StringPiece> CollectAncestorNames(
BeginPoint begin_point,
bool (*should_stop)(base::StringPiece)) const override {
DCHECK_EQ(BeginPoint::kParentFrame, begin_point);
return parent_->CollectAncestorNames(BeginPoint::kThisFrame, should_stop);
std::vector<int> GetFramePosition(BeginPoint begin_point) const override {
DCHECK_EQ(BeginPoint::kParentFrame, begin_point);
return parent_->GetFramePosition(BeginPoint::kThisFrame);
FrameAdapter* const parent_;
constexpr char kFramePathPrefix[] = "<!--framePath /";
constexpr int kFramePathPrefixLength = 15;
constexpr int kFramePathSuffixLength = 3;
constexpr char kDynamicFrameMarker[] = "<!--dynamicFrame";
// 80% of unique names are shorter than this, and it also guarantees that this
// won't ever increase the length of a unique name, as a hashed unique name is
// exactly 80 characters.
constexpr size_t kMaxRequestedNameSize = 80;
bool IsNameWithFramePath(base::StringPiece name) {
return name.starts_with(kFramePathPrefix) && name.ends_with("-->") &&
(kFramePathPrefixLength + kFramePathSuffixLength) < name.size();
std::string GenerateCandidate(const FrameAdapter* frame) {
std::string new_name(kFramePathPrefix);
std::vector<base::StringPiece> ancestor_names = frame->CollectAncestorNames(
FrameAdapter::BeginPoint::kParentFrame, &IsNameWithFramePath);
std::reverse(ancestor_names.begin(), ancestor_names.end());
// Note: This checks ancestor_names[0] twice, but it's nicer to do the name
// extraction here rather than passing another function pointer to
// CollectAncestorNames().
if (!ancestor_names.empty() && IsNameWithFramePath(ancestor_names[0])) {
ancestor_names[0] = ancestor_names[0].substr(kFramePathPrefixLength,
ancestor_names[0].size() -
kFramePathPrefixLength -
new_name += base::JoinString(ancestor_names, "/");
new_name += "/<!--frame";
new_name += base::NumberToString(frame->GetSiblingCount());
new_name += "-->-->";
// NOTE: This name might not be unique - see
return new_name;
std::string GenerateFramePosition(const FrameAdapter* frame) {
std::string position_string("<!--framePosition");
std::vector<int> positions =
for (int position : positions) {
position_string += '-';
position_string += base::NumberToString(position);
// NOTE: The generated string is not guaranteed to be unique, but should
// have a better chance of being unique than the string generated by
// GenerateCandidate, because we embed extra information into the string:
// 1) we walk the full chain of ancestors, all the way to the main frame
// 2) we use frame-position-within-parent (aka |position_in_parent|)
// instead of sibling-count.
return position_string;
std::string AppendUniqueSuffix(const FrameAdapter* frame,
const std::string& prefix,
const std::string& likely_unique_suffix) {
// This should only be called if the |prefix| isn't unique, as this is
// otherwise pointless work.
// We want unique name to be stable across page reloads - this is why
// we use a deterministic |number_of_tries| rather than a random number
// (a random number would be more likely to avoid a collision, but
// would change after every page reload).
int number_of_retries = 0;
// Keep trying |prefix| + |likely_unique_suffix| + |number_of_tries|
// concatenations until we get a truly unique name.
std::string candidate(prefix);
candidate += likely_unique_suffix;
candidate += '/';
while (true) {
size_t current_length = candidate.size();
candidate += base::NumberToString(number_of_retries++);
candidate += "-->";
if (frame->IsCandidateUnique(candidate))
return candidate;
std::string CalculateNameInternal(const FrameAdapter* frame,
base::StringPiece name) {
if (!name.empty() && frame->IsCandidateUnique(name) && name != "_blank")
return name.as_string();
std::string candidate = GenerateCandidate(frame);
if (frame->IsCandidateUnique(candidate))
return candidate;
std::string likely_unique_suffix = GenerateFramePosition(frame);
return AppendUniqueSuffix(frame, candidate, likely_unique_suffix);
std::string CalculateFrameHash(base::StringPiece name) {
DCHECK_GT(name.size(), kMaxRequestedNameSize);
std::string hashed_name;
uint8_t result[crypto::kSHA256Length];
crypto::SHA256HashString(name, result, base::size(result));
hashed_name += "<!--frameHash";
hashed_name += base::HexEncode(result, base::size(result));
hashed_name += "-->";
return hashed_name;
std::string CalculateNewName(const FrameAdapter* frame,
base::StringPiece name) {
std::string hashed_name;
// By default, |name| is the browsing context name, which can be arbitrarily
// long. Since the generated name is part of history entries and FrameState,
// hash pathologically long names to avoid using a lot of memory.
if (name.size() > kMaxRequestedNameSize) {
hashed_name = CalculateFrameHash(name);
name = hashed_name;
return CalculateNameInternal(frame, name);
} // namespace
UniqueNameHelper::FrameAdapter::~FrameAdapter() {}
UniqueNameHelper::Replacement::Replacement(std::string old_name,
std::string new_name)
: old_name(std::move(old_name)), new_name(std::move(new_name)) {}
UniqueNameHelper::UniqueNameHelper(FrameAdapter* frame) : frame_(frame) {}
UniqueNameHelper::~UniqueNameHelper() {}
std::string UniqueNameHelper::GenerateNameForNewChildFrame(
const std::string& name,
bool is_created_by_script) const {
std::string unique_name_of_new_child;
// The deterministic part of unique name should be included if
// 1. The new subframe is not created by script or
// 2. The new subframe is created by script, but we are still asked for the
// old, stable part for web tests (via
// |g_preserve_stable_unique_name_for_testing|).
if (!is_created_by_script || g_preserve_stable_unique_name_for_testing) {
PendingChildFrameAdapter adapter(frame_);
unique_name_of_new_child = CalculateNewName(&adapter, name);
// The random part of unique name is only included for subframes created from
// scripts.
if (is_created_by_script) {
unique_name_of_new_child += kDynamicFrameMarker;
unique_name_of_new_child += base::UnguessableToken::Create().ToString();
unique_name_of_new_child += "-->";
return unique_name_of_new_child;
void UniqueNameHelper::UpdateName(const std::string& name) {
// Don't update the unique name if it should remain frozen.
if (frozen_)
// The unique name of the main frame is always the empty string.
if (frame_->IsMainFrame())
// It's important to clear this before calculating a new name, as the
// calculation checks for collisions with existing unique names.
unique_name_ = CalculateNewName(frame_, name);
// |replacements| is used for two purposes:
// - when processing a non-frame path unique name that exceeds the max size,
// this collection records the original name and the hashed name.
// - when processing a frame path unique name, this collection is used to fix up
// ancestor frames in the frame path with an updated unique name.
std::string UniqueNameHelper::UpdateLegacyNameFromV24(
std::string legacy_name,
std::vector<Replacement>* replacements) {
if (IsNameWithFramePath(legacy_name)) {
// Frame paths can embed ancestor's unique names. Since the contract of this
// function is that names must be updated beginning from the root of the
// tree and go down from there, it is impossible for a frame path to contain
// a unique name (which needs a replacement) that has not already been seen
// and inserted into |replacements|.
for (const auto& replacement : *replacements) {
// Note: this find() call should only start searching from immediately
// after the most recent replacement, to guarantee each section of the
// name is only replaced once. But it was accidentally omitted from the
// initial version of the migration code.
size_t next_index = legacy_name.find(replacement.old_name);
if (next_index == std::string::npos)
legacy_name.replace(next_index, replacement.old_name.size(),
return legacy_name;
if (legacy_name.size() > kMaxRequestedNameSize) {
std::string hashed_name = CalculateFrameHash(legacy_name);
// Suppose 'aaa' and 'caaab' are unique names in the same tree. A
// hypothetical frame path might look like:
// <!--framePath //aaa/caaab/<!--frame0-->-->
// In this case, it's important to avoid matching 'aaa' against the
// substring in 'caaab'. To try to avoid this, the search and the
// replacement strings are wrapped in '/' to try to match the path delimiter
// in generated frame paths.
// However, nothing prevents a browsing context name from containing a
// literal '/', which could lead to an ambiguous parse. Consider the case
// where 'aaa', 'bbb', and 'aaa/bbb' are unique names in the same tree. The
// following frame path is ambiguous:
// <!--framePath //aaa/bbb/<!--frame0-->-->
// While it's possible to use the depth of the frame tree as a hint for
// disambiguating this, the number of ways to split up the frame path
// quickly becomes quite large. This code takes the simple approach and
// simply aims to implement a best effort update, accepting that there may
// be some names that are updated incorrectly.
std::string original_string = "/";
original_string += legacy_name;
original_string += "/";
std::string new_string = "/";
new_string += hashed_name;
new_string += "/";
return hashed_name;
return legacy_name;
std::string UniqueNameHelper::CalculateLegacyNameForTesting(
const FrameAdapter* frame,
const std::string& name) {
return CalculateNameInternal(frame, name);
// static
void UniqueNameHelper::PreserveStableUniqueNameForTesting() {
g_preserve_stable_unique_name_for_testing = true;
std::string UniqueNameHelper::ExtractStableNameForTesting(
const std::string& unique_name) {
size_t i = unique_name.rfind(kDynamicFrameMarker);
if (i == std::string::npos)
return unique_name;
return unique_name.substr(0, i);
} // namespace content