Commit 4dea8904 authored by Carlos IL's avatar Carlos IL Committed by Commit Bot

Remove transient entries

Transient navigation entries were only used for non-committed (overlay)
interstitials and a few tests. Since overlay interstitials have been
removed, this CL changes the tests to use regular entries, and removes
the transient entry concept.

Bug: 1077074

Change-Id: Ide09a628fae91a04308ae26efde7112f1e468250
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2149954
Auto-Submit: Carlos IL <carlosil@chromium.org>
Commit-Queue: Camille Lamy <clamy@chromium.org>
Reviewed-by: default avatarCamille Lamy <clamy@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarCharlie Reis <creis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#771811}
parent 6937044e
......@@ -25,6 +25,7 @@
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
......@@ -123,19 +124,13 @@ class TabDesktopMediaListTest : public testing::Test {
WebContentsTester::For(contents.get())
->SetLastActiveTime(base::TimeTicks::Now());
// Get or create the transient NavigationEntry and add a title and a
// favicon to it.
// Get or create a NavigationEntry and add a title and a favicon to it.
content::NavigationEntry* entry =
contents->GetController().GetTransientEntry();
contents->GetController().GetLastCommittedEntry();
if (!entry) {
std::unique_ptr<content::NavigationEntry> entry_new =
content::NavigationController::CreateNavigationEntry(
GURL("chrome://blank"), content::Referrer(), base::nullopt,
ui::PAGE_TRANSITION_LINK, false, std::string(), profile_,
nullptr /* blob_url_loader_factory */);
contents->GetController().SetTransientEntry(std::move(entry_new));
entry = contents->GetController().GetTransientEntry();
content::NavigationSimulator::NavigateAndCommitFromBrowser(
contents.get(), GURL("chrome://blank"));
entry = contents->GetController().GetLastCommittedEntry();
}
contents->UpdateTitleForEntry(entry, base::ASCIIToUTF16("Test tab"));
......@@ -336,7 +331,7 @@ TEST_F(TabDesktopMediaListTest, UpdateTitle) {
tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
ASSERT_TRUE(contents);
content::NavigationController& controller = contents->GetController();
contents->UpdateTitleForEntry(controller.GetTransientEntry(),
contents->UpdateTitleForEntry(controller.GetLastCommittedEntry(),
base::ASCIIToUTF16("New test tab"));
EXPECT_CALL(observer_, OnSourceNameChanged(list_.get(), 0))
......@@ -361,7 +356,8 @@ TEST_F(TabDesktopMediaListTest, UpdateThumbnail) {
content::FaviconStatus favicon_info;
favicon_info.image = CreateGrayscaleImage(gfx::Size(10, 10), 100);
contents->GetController().GetTransientEntry()->GetFavicon() = favicon_info;
contents->GetController().GetLastCommittedEntry()->GetFavicon() =
favicon_info;
EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
.WillOnce(QuitMessageLoop());
......
......@@ -284,30 +284,6 @@ TEST_F(SearchTest, InstantNTPExtendedEnabled) {
}
}
TEST_F(SearchTest, InstantNTPCustomNavigationEntry) {
AddTab(browser(), GURL("chrome://blank"));
for (const SearchTestCase& test : kInstantNTPTestCases) {
NavigateAndCommitActiveTab(GURL(test.url));
content::WebContents* contents =
browser()->tab_strip_model()->GetWebContentsAt(0);
content::NavigationController& controller = contents->GetController();
controller.SetTransientEntry(
content::NavigationController::CreateNavigationEntry(
GURL("chrome://blank"), content::Referrer(), base::nullopt,
ui::PAGE_TRANSITION_LINK, false, std::string(),
contents->GetBrowserContext(),
nullptr /* blob_url_loader_factory */));
// The visible entry is now chrome://blank, but this is still an NTP.
EXPECT_FALSE(NavEntryIsInstantNTP(contents, controller.GetVisibleEntry()));
EXPECT_EQ(test.expected_result,
NavEntryIsInstantNTP(contents,
controller.GetLastCommittedEntry()))
<< test.url << " " << test.comment;
EXPECT_EQ(test.expected_result, IsInstantNTP(contents))
<< test.url << " " << test.comment;
}
}
TEST_F(SearchTest, InstantCacheableNTPNavigationEntry) {
AddTab(browser(), GURL("chrome://blank"));
content::WebContents* contents =
......
......@@ -73,11 +73,8 @@ gfx::Image ContentFaviconDriver::GetFavicon() const {
// Like GetTitle(), we also want to use the favicon for the last committed
// entry rather than a pending navigation entry.
content::NavigationController& controller = web_contents()->GetController();
content::NavigationEntry* entry = controller.GetTransientEntry();
if (entry)
return entry->GetFavicon().image;
entry = controller.GetLastCommittedEntry();
content::NavigationEntry* entry = controller.GetLastCommittedEntry();
if (entry)
return entry->GetFavicon().image;
return gfx::Image();
......@@ -85,11 +82,8 @@ gfx::Image ContentFaviconDriver::GetFavicon() const {
bool ContentFaviconDriver::FaviconIsValid() const {
content::NavigationController& controller = web_contents()->GetController();
content::NavigationEntry* entry = controller.GetTransientEntry();
if (entry)
return entry->GetFavicon().valid;
entry = controller.GetLastCommittedEntry();
content::NavigationEntry* entry = controller.GetLastCommittedEntry();
if (entry)
return entry->GetFavicon().valid;
......
......@@ -155,12 +155,7 @@ bool ShouldTreatNavigationAsReload(const GURL& url,
bool is_post,
bool is_reload,
bool is_navigation_to_existing_entry,
bool has_interstitial,
NavigationEntryImpl* last_committed_entry) {
// Don't convert when an interstitial is showing.
if (has_interstitial)
return false;
// Only convert main frame navigations to a new entry.
if (!is_main_frame || is_reload || is_navigation_to_existing_entry)
return false;
......@@ -592,18 +587,6 @@ void NavigationControllerImpl::Restore(
void NavigationControllerImpl::Reload(ReloadType reload_type,
bool check_for_repost) {
DCHECK_NE(ReloadType::NONE, reload_type);
if (transient_entry_index_ != -1) {
// If an interstitial is showing, treat a reload as a navigation to the
// transient entry's URL.
NavigationEntryImpl* transient_entry = GetTransientEntry();
if (!transient_entry)
return;
LoadURL(transient_entry->GetURL(), Referrer(), ui::PAGE_TRANSITION_RELOAD,
transient_entry->extra_headers());
return;
}
NavigationEntryImpl* entry = nullptr;
int current_index = -1;
......@@ -722,16 +705,12 @@ void NavigationControllerImpl::SetPendingEntry(
}
NavigationEntryImpl* NavigationControllerImpl::GetActiveEntry() {
if (transient_entry_index_ != -1)
return entries_[transient_entry_index_].get();
if (pending_entry_)
return pending_entry_;
return GetLastCommittedEntry();
}
NavigationEntryImpl* NavigationControllerImpl::GetVisibleEntry() {
if (transient_entry_index_ != -1)
return entries_[transient_entry_index_].get();
// The pending entry is safe to return for new (non-history), browser-
// initiated navigations. Most renderer-initiated navigations should not
// show the pending entry, to prevent URL spoof attacks.
......@@ -760,8 +739,6 @@ NavigationEntryImpl* NavigationControllerImpl::GetVisibleEntry() {
}
int NavigationControllerImpl::GetCurrentEntryIndex() {
if (transient_entry_index_ != -1)
return transient_entry_index_;
if (pending_entry_index_ != -1)
return pending_entry_index_;
return last_committed_entry_index_;
......@@ -784,16 +761,14 @@ bool NavigationControllerImpl::CanViewSource() {
int NavigationControllerImpl::GetLastCommittedEntryIndex() {
// The last committed entry index must always be less than the number of
// entries. If there are no entries, it must be -1. However, there may be a
// transient entry while the last committed entry index is still -1.
// entries. If there are no entries, it must be -1.
DCHECK_LT(last_committed_entry_index_, GetEntryCount());
DCHECK(GetEntryCount() || last_committed_entry_index_ == -1);
return last_committed_entry_index_;
}
int NavigationControllerImpl::GetEntryCount() {
DCHECK_LE(entries_.size(),
max_entry_count() + (transient_entry_index_ == -1 ? 0 : 1));
DCHECK_LE(entries_.size(), max_entry_count());
return static_cast<int>(entries_.size());
}
......@@ -906,17 +881,6 @@ void NavigationControllerImpl::GoToIndex(int index,
return;
}
if (transient_entry_index_ != -1) {
if (index == transient_entry_index_) {
// Nothing to do when navigating to the transient.
return;
}
if (index > transient_entry_index_) {
// Removing the transient is goint to shift all entries by 1.
index--;
}
}
DiscardNonCommittedEntries();
DCHECK_EQ(nullptr, pending_entry_);
......@@ -1743,8 +1707,7 @@ void NavigationControllerImpl::RendererDidNavigateToExistingPage(
if (!keep_pending_entry)
DiscardNonCommittedEntries();
// If a transient entry was removed, the indices might have changed, so we
// have to query the entry index again.
// Update the last committed index to reflect the committed entry.
last_committed_entry_index_ = GetIndexOfEntry(entry);
}
......@@ -2037,9 +2000,8 @@ void NavigationControllerImpl::CopyStateFromAndPrune(NavigationController* temp,
if (!replace_entry)
source->PruneOldestSkippableEntryIfFull();
// Insert the entries from source. Don't use source->GetCurrentEntryIndex as
// we don't want to copy over the transient entry. Ignore any pending entry,
// since it has not committed in source.
// Insert the entries from source. Ignore any pending entry, since it has not
// committed in source.
int max_source_index = source->last_committed_entry_index_;
if (max_source_index == -1)
max_source_index = source->GetEntryCount();
......@@ -2075,10 +2037,6 @@ bool NavigationControllerImpl::CanPruneAllButLastCommitted() {
if (pending_entry_index_ != -1)
return false;
// We should not prune if we are currently showing a transient entry.
if (transient_entry_index_ != -1)
return false;
return true;
}
......@@ -2482,18 +2440,8 @@ void NavigationControllerImpl::SetNeedsReload(NeedsReloadType type) {
void NavigationControllerImpl::RemoveEntryAtIndexInternal(int index) {
DCHECK_LT(index, GetEntryCount());
DCHECK_NE(index, last_committed_entry_index_);
const bool was_transient = index == transient_entry_index_;
DiscardNonCommittedEntries();
if (was_transient) {
// There's nothing left to do if the index referred to a transient entry
// that we just discarded.
DCHECK(!GetTransientEntry());
return;
}
entries_.erase(entries_.begin() + index);
if (last_committed_entry_index_ > index)
last_committed_entry_index_--;
......@@ -2935,7 +2883,6 @@ void NavigationControllerImpl::NavigateWithoutEntry(
params.load_type ==
NavigationController::LOAD_TYPE_HTTP_POST /* is_post */,
false /* is_reload */, false /* is_navigation_to_existing_entry */,
transient_entry_index_ != -1 /* has_interstitial */,
GetLastCommittedEntry())) {
reload_type = ReloadType::NORMAL;
}
......@@ -3478,28 +3425,14 @@ void NavigationControllerImpl::DiscardNonCommittedEntries() {
// Avoid sending a notification if there is nothing to discard.
// TODO(mthiesse): Temporarily checking failed_pending_entry_id_ to help
// diagnose https://bugs.chromium.org/p/chromium/issues/detail?id=1007570.
if (!pending_entry_ && transient_entry_index_ == -1 &&
failed_pending_entry_id_ == 0) {
if (!pending_entry_ && failed_pending_entry_id_ == 0) {
return;
}
DiscardPendingEntry(false);
DiscardTransientEntry();
if (delegate_)
delegate_->NotifyNavigationStateChanged(INVALIDATE_TYPE_ALL);
}
void NavigationControllerImpl::DiscardTransientEntry() {
if (transient_entry_index_ == -1)
return;
entries_.erase(entries_.begin() + transient_entry_index_);
if (last_committed_entry_index_ > transient_entry_index_)
last_committed_entry_index_--;
if (pending_entry_index_ > transient_entry_index_)
pending_entry_index_--;
transient_entry_index_ = -1;
}
int NavigationControllerImpl::GetEntryIndexWithUniqueID(
int nav_entry_id) const {
for (int i = static_cast<int>(entries_.size()) - 1; i >= 0; --i) {
......@@ -3509,27 +3442,6 @@ int NavigationControllerImpl::GetEntryIndexWithUniqueID(
return -1;
}
NavigationEntryImpl* NavigationControllerImpl::GetTransientEntry() {
if (transient_entry_index_ == -1)
return nullptr;
return entries_[transient_entry_index_].get();
}
void NavigationControllerImpl::SetTransientEntry(
std::unique_ptr<NavigationEntry> entry) {
// Discard any current transient entry, we can only have one at a time.
DiscardTransientEntry();
int index = 0;
if (last_committed_entry_index_ != -1)
index = last_committed_entry_index_ + 1;
entries_.insert(entries_.begin() + index,
NavigationEntryImpl::FromNavigationEntry(std::move(entry)));
if (pending_entry_index_ >= index)
pending_entry_index_++;
transient_entry_index_ = index;
delegate_->NotifyNavigationStateChanged(INVALIDATE_TYPE_ALL);
}
void NavigationControllerImpl::InsertEntriesFrom(
NavigationControllerImpl* source,
int max_index) {
......@@ -3639,8 +3551,6 @@ void NavigationControllerImpl::PendingEntryRefDeleted(PendingEntryRef* ref) {
// Do not leave the pending entry visible if it has an invalid URL, since this
// might be formatted in an unexpected or unsafe way.
// TODO(creis): Block navigations to invalid URLs in https://crbug.com/850824.
//
// Note: don't touch the transient entry, since an interstitial may exist.
bool should_preserve_entry =
(pending_entry_ == GetVisibleEntry()) &&
pending_entry_->GetURL().is_valid() &&
......
......@@ -93,8 +93,6 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController {
void DiscardNonCommittedEntries() override;
NavigationEntryImpl* GetPendingEntry() override;
int GetPendingEntryIndex() override;
NavigationEntryImpl* GetTransientEntry() override;
void SetTransientEntry(std::unique_ptr<NavigationEntry> entry) override;
void LoadURL(const GURL& url,
const Referrer& referrer,
ui::PageTransition type,
......@@ -507,9 +505,6 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController {
// Removes the entry at |index|, as long as it is not the current entry.
void RemoveEntryAtIndexInternal(int index);
// Discards only the transient entry.
void DiscardTransientEntry();
// If we have the maximum number of entries, remove the oldest entry that is
// marked to be skipped on back/forward button, in preparation to add another.
// If no entry is skippable, then the oldest entry will be pruned.
......@@ -524,8 +519,7 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController {
// Inserts up to |max_index| entries from |source| into this. This does NOT
// adjust any of the members that reference entries_
// (last_committed_entry_index_, pending_entry_index_ or
// transient_entry_index_).
// (last_committed_entry_index_ or pending_entry_index_)
void InsertEntriesFrom(NavigationControllerImpl* source, int max_index);
// Returns the navigation index that differs from the current entry by the
......@@ -603,13 +597,6 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController {
// pending_entry_ is a new entry (created by LoadURL).
int pending_entry_index_ = -1;
// The index for the entry that is shown until a navigation occurs. This is
// used for interstitial pages. -1 if there are no such entry.
// Note that this entry really appears in the list of entries, but only
// temporarily (until the next navigation). Any index pointing to an entry
// after the transient entry will become invalid if you navigate forward.
int transient_entry_index_ = -1;
// The delegate associated with the controller. Possibly NULL during
// setup.
NavigationControllerDelegate* delegate_;
......
......@@ -805,12 +805,6 @@ Navigator::GetNavigationEntryForRendererInitiatedNavigation(
if (renderer_provisional_load_to_pending_url)
return nullptr;
// If there is a transient entry, creating a new pending entry will result
// in deleting it, which leads to inconsistent state.
bool has_transient_entry = !!controller_->GetTransientEntry();
if (has_transient_entry)
return nullptr;
// Since GetNavigationEntryForRendererInitiatedNavigation is called from
// OnBeginNavigation, we can assume that no frame proxies are involved and
// therefore that |current_site_instance| is also the |source_site_instance|.
......
......@@ -350,7 +350,6 @@ void Portal::Activate(blink::TransferableMessage data,
// restriction.
DCHECK_EQ(PAGE_TYPE_NORMAL,
predecessor_controller.GetLastCommittedEntry()->GetPageType());
DCHECK(!predecessor_controller.GetTransientEntry());
// If the portal is showing an error page, reject activation.
if (portal_controller.GetLastCommittedEntry()->GetPageType() !=
......@@ -359,7 +358,6 @@ void Portal::Activate(blink::TransferableMessage data,
blink::mojom::PortalActivateResult::kRejectedDueToErrorInPortal);
return;
}
DCHECK(!portal_controller.GetTransientEntry());
// If a navigation in the main frame is occurring, stop it if possible and
// reject the activation if it's too late or if an ongoing navigation takes
......
......@@ -1389,20 +1389,13 @@ void WebContentsImpl::SetDisplayCutoutSafeArea(gfx::Insets insets) {
#endif
const base::string16& WebContentsImpl::GetTitle() {
// Transient entries take precedence. They are used for interstitial pages
// that are shown on top of existing pages.
NavigationEntry* entry = controller_.GetTransientEntry();
if (entry) {
return entry->GetTitleForDisplay();
}
WebUI* our_web_ui =
GetRenderManager()->speculative_frame_host()
? GetRenderManager()->speculative_frame_host()->web_ui()
: GetRenderManager()->current_frame_host()->web_ui();
if (our_web_ui) {
// Don't override the title in view source mode.
entry = controller_.GetVisibleEntry();
NavigationEntry* entry = controller_.GetVisibleEntry();
if (!(entry && entry->IsViewSourceMode())) {
// Give the Web UI the chance to override our title.
const base::string16& title = our_web_ui->GetOverriddenTitle();
......@@ -1415,7 +1408,7 @@ const base::string16& WebContentsImpl::GetTitle() {
// navigation entry. For example, when the user types in a URL, we want to
// keep the old page's title until the new load has committed and we get a new
// title.
entry = controller_.GetLastCommittedEntry();
NavigationEntry* entry = controller_.GetLastCommittedEntry();
// We make an exception for initial navigations. We only want to use the title
// from the visible entry if:
......
......@@ -294,24 +294,18 @@ class NavigationController {
// has not been responded to, the NavigationEntry is pending. Once data is
// received for that entry, that NavigationEntry is committed.
// A transient entry is an entry that, when the user navigates away, is
// removed and discarded rather than being added to the back-forward list.
// Transient entries are useful for interstitial pages and the like.
// Active entry --------------------------------------------------------------
// THIS IS DEPRECATED. DO NOT USE. Use GetVisibleEntry instead.
// See http://crbug.com/273710.
//
// Returns the active entry, which is the transient entry if any, the pending
// entry if a navigation is in progress or the last committed entry otherwise.
// NOTE: This can be nullptr!!
// Returns the active entry, which is the pending entry if a navigation is in
// progress or the last committed entry otherwise. NOTE: This can be nullptr!!
virtual NavigationEntry* GetActiveEntry() = 0;
// Returns the entry that should be displayed to the user in the address bar.
// This is the transient entry if any, the pending entry if a navigation is
// in progress *and* is safe to display to the user (see below), or the last
// committed entry otherwise.
// This is the pending entry if a navigation is in progress *and* is safe to
// display to the user (see below), or the last committed entry otherwise.
// NOTE: This can be nullptr if no entry has committed!
//
// A pending entry is safe to display if it started in the browser process or
......@@ -329,8 +323,7 @@ class NavigationController {
virtual NavigationEntry* GetLastCommittedEntry() = 0;
// Returns the index of the last committed entry. It will be -1 if there are
// no entries, or if there is a transient entry before the first entry
// commits.
// no entries.
virtual int GetLastCommittedEntryIndex() = 0;
// Returns true if the source for the current entry can be viewed.
......@@ -339,8 +332,7 @@ class NavigationController {
// Navigation list -----------------------------------------------------------
// Returns the number of entries in the NavigationController, excluding
// the pending entry if there is one, but including the transient entry if
// any.
// the pending entry if there is one.
virtual int GetEntryCount() = 0;
virtual NavigationEntry* GetEntryAtIndex(int index) = 0;
......@@ -351,7 +343,7 @@ class NavigationController {
// Pending entry -------------------------------------------------------------
// Discards the pending and transient entries if any.
// Discards the pending entry if any.
virtual void DiscardNonCommittedEntries() = 0;
// Returns the pending entry corresponding to the navigation that is
......@@ -362,22 +354,6 @@ class NavigationController {
// corresponds to a new navigation (created via LoadURL).
virtual int GetPendingEntryIndex() = 0;
// Transient entry -----------------------------------------------------------
// Returns the transient entry if any. This is an entry which is removed and
// discarded if any navigation occurs. Note that the returned entry is owned
// by the navigation controller and may be deleted at any time.
virtual NavigationEntry* GetTransientEntry() = 0;
// Adds an entry that is returned by GetActiveEntry(). The entry is
// transient: any navigation causes it to be removed and discarded. The
// NavigationController becomes the owner of |entry| and deletes it when
// it discards it. This is useful with interstitial pages that need to be
// represented as an entry, but should go away when the user navigates away
// from them.
// Note that adding a transient entry does not change the active contents.
virtual void SetTransientEntry(std::unique_ptr<NavigationEntry> entry) = 0;
// New navigations -----------------------------------------------------------
// Loads the specified URL, specifying extra http headers to add to the
......@@ -428,8 +404,7 @@ class NavigationController {
// |check_for_repost| is true and the current entry has POST data the user is
// prompted to see if they really want to reload the page. In nearly all
// cases pass in true in production code, but would do false for testing, or
// in cases where no user interface is available for prompting. If a
// transient entry is showing, initiates a new navigation to its URL.
// in cases where no user interface is available for prompting.
// NOTE: |reload_type| should never be NONE.
virtual void Reload(ReloadType reload_type, bool check_for_repost) = 0;
......@@ -437,11 +412,11 @@ class NavigationController {
// Removes the entry at the specified |index|. If the index is the last
// committed index or the pending entry, this does nothing and returns false.
// Otherwise this call discards any transient or pending entries.
// Otherwise this call discards any pending entry.
virtual bool RemoveEntryAtIndex(int index) = 0;
// Discards any transient or pending entries, then discards all entries after
// the current entry index.
// Discards any pending entry, then discards all entries after the current
// entry index.
virtual void PruneForwardEntries() = 0;
// Random --------------------------------------------------------------------
......@@ -500,23 +475,20 @@ class NavigationController {
// If there is a pending entry after *G* in |this|, it is also preserved.
// If |replace_entry| is true, the current entry in |source| is replaced. So
// the result above would be A B *G*.
// This ignores any pending or transient entries in |source|. Callers must
// ensure that |CanPruneAllButLastCommitted| returns true before calling this,
// or it will crash.
// This ignores any pending entry in |source|. Callers must ensure that
// |CanPruneAllButLastCommitted| returns true before calling this, or it will
// crash.
virtual void CopyStateFromAndPrune(NavigationController* source,
bool replace_entry) = 0;
// Returns whether it is safe to call PruneAllButLastCommitted or
// CopyStateFromAndPrune. There must be a last committed entry, no transient
// entry, and if there is a pending entry, it must be new and not an existing
// entry.
// CopyStateFromAndPrune. There must be a last committed entry, and if there
// is a pending entry, it must be new and not an existing entry.
//
// If there were no last committed entry, the pending entry might not commit,
// leaving us with a blank page. This is unsafe when used with
// |CopyStateFromAndPrune|, which would show an existing entry above the blank
// page.
// If there were a transient entry, we would not want to prune the other
// entries, which the transient entry could be referring to.
// If there were an existing pending entry, we could not prune the last
// committed entry, in case it did not commit. That would leave us with no
// sensible place to put the pending entry when it did commit, after all other
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment