blob: e5cce840e75058eb499e2b3f4263d0c8e731798f [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 <stddef.h>
#include <memory>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#import "ios/web/navigation/navigation_item_impl.h"
#import "ios/web/navigation/navigation_manager_impl.h"
#include "ios/web/navigation/time_smoother.h"
#include "ios/web/public/reload_type.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
@class WKBackForwardListItem;
namespace base {
class ElapsedTimer;
namespace web {
class BrowserState;
class NavigationItem;
struct Referrer;
class SessionStorageBuilder;
// Name of UMA histogram to log the time spent on asynchronous session
// restoration.
extern const char kRestoreNavigationTime[];
// WKBackForwardList based implementation of NavigationManagerImpl.
// This class relies on the following WKWebView APIs, defined by the
// CRWWebViewNavigationProxy protocol:
// @property URL
// @property backForwardList
// - goToBackForwardListItem:
// This navigation manager uses WKBackForwardList as the ground truth for back-
// forward navigation history. It uses the Associated Objects runtime feature
// to link a NavigationItemImpl object to each WKBackForwardListItem to store
// additional states needed by the embedder.
// For all main frame navigations (both UI-initiated and renderer-initiated),
// the NavigationItemImpl objects are created proactively via AddPendingItem and
// CommitPendingItem.
// Non-main-frame navigations can only be initiated from the renderer. The
// NavigationItemImpl objects in this case are created lazily in GetItemAtIndex
// because the provisional load and commit events for iframe navigation are not
// visible via the WKNavigationDelegate interface. Consequently, pending item
// and previous item are only tracked for the main frame.
// Empty Window Open Navigation edge case:
// If is called with an empty URL, WKWebView does not seem to
// create a WKBackForwardListItem for the first about:blank navigation. Any
// subsequent navigation in this window will replace the about:blank entry.
// This is consistent with the HTML spec regarding Location-object navigation
// when the browser context's only Document is about:blank:
// (Section 7.7.4)
// This navigation manager will still create a pendingNavigationItem for this
// "empty window open item" and allow CommitPendingItem() to be called on it.
// All accessors will behave identically as if the navigation history has a
// single normal entry. The only difference is that a subsequent call to
// CommitPendingItem() will *replace* the empty window open item. From this
// point onward, it is as if the empty window open item never occurred.
// Detach from web view edge case:
// As long as this navigation manager is alive, the navigation manager
// delegate should not delete its WKWebView. However, legacy use cases exist
// (e.g. https://crbug/770914). As a workaround, before deleting the
// WKWebView, the delegate must call
// NavigationManagerImpl::DetachFromWebView() to cache the current session
// history. This puts the navigation manager in a detached state. While in
// this state, all getters are serviced using the cached session history.
// Mutation methods are not allowed. The navigation manager returns to the
// attached state when a new navigation starts.
class WKBasedNavigationManagerImpl : public NavigationManagerImpl {
~WKBasedNavigationManagerImpl() override;
// NavigationManagerImpl:
void SetSessionController(CRWSessionController* session_controller) override;
void InitializeSession() override;
void OnNavigationItemsPruned(size_t pruned_item_count) override;
void OnNavigationItemCommitted() override;
void DetachFromWebView() override;
CRWSessionController* GetSessionController() const override;
void AddTransientItem(const GURL& url) override;
void AddPendingItem(
const GURL& url,
const web::Referrer& referrer,
ui::PageTransition navigation_type,
NavigationInitiationType initiation_type,
UserAgentOverrideOption user_agent_override_option) override;
void CommitPendingItem() override;
int GetIndexForOffset(int offset) const override;
// Returns the previous navigation item in the main frame.
int GetPreviousItemIndex() const override;
void SetPreviousItemIndex(int previous_item_index) override;
void AddPushStateItemIfNecessary(const GURL& url,
NSString* state_object,
ui::PageTransition transition) override;
bool IsRestoreSessionInProgress() const override;
void SetPendingItemIndex(int index) override;
void ApplyWKWebViewForwardHistoryClobberWorkaround() override;
// NavigationManager:
BrowserState* GetBrowserState() const override;
WebState* GetWebState() const override;
NavigationItem* GetVisibleItem() const override;
void DiscardNonCommittedItems() override;
int GetItemCount() const override;
NavigationItem* GetItemAtIndex(size_t index) const override;
int GetIndexOfItem(const NavigationItem* item) const override;
int GetPendingItemIndex() const override;
bool RemoveItemAtIndex(int index) override;
bool CanGoBack() const override;
bool CanGoForward() const override;
bool CanGoToOffset(int offset) const override;
void GoBack() override;
void GoForward() override;
NavigationItemList GetBackwardItems() const override;
NavigationItemList GetForwardItems() const override;
void CopyStateFromAndPrune(const NavigationManager* source) override;
bool CanPruneAllButLastCommittedItem() const override;
void Restore(int last_committed_item_index,
std::vector<std::unique_ptr<NavigationItem>> items) override;
void LoadURLWithParams(const NavigationManager::WebLoadParams&) override;
void AddRestoreCompletionCallback(base::OnceClosure callback) override;
void LoadIfNecessary() override;
// The SessionStorageBuilder functions require access to private variables of
// NavigationManagerImpl.
friend SessionStorageBuilder;
// Access shim for NavigationItems associated with the WKBackForwardList. It
// is responsible for caching NavigationItems when the navigation manager
// detaches from its web view.
class WKWebViewCache {
explicit WKWebViewCache(WKBasedNavigationManagerImpl* navigation_manager);
// Returns true if the navigation manager is attached to a WKWebView.
bool IsAttachedToWebView() const;
// Caches NavigationItems from the WKWebView in |this| and changes state to
// detached.
void DetachFromWebView();
// Clears the cached NavigationItems and resets state to attached. Callers
// that wish to restore the cached navigation items into the new web view
// must call ReleaseCachedItems() first.
void ResetToAttached();
// Returns ownership of the cached NavigationItems. This is convenient for
// restoring session history when reattaching to a new web view.
std::vector<std::unique_ptr<NavigationItem>> ReleaseCachedItems();
// Returns the number of items in the back-forward history.
size_t GetBackForwardListItemCount() const;
// Returns the absolute index of WKBackForwardList's |currentItem| or -1 if
// |currentItem| is nil. If navigation manager is in detached mode, returns
// the cached value of this property captured at the last call of
// DetachFromWebView().
int GetCurrentItemIndex() const;
// Returns the NavigationItem associated with the WKBackForwardListItem at
// |index|. If |create_if_missing| is true and the WKBackForwardListItem
// does not have an associated NavigationItem, creates a new one and returns
// it to the caller.
NavigationItemImpl* GetNavigationItemImplAtIndex(
size_t index,
bool create_if_missing) const;
// Returns the WKBackForwardListItem at |index|. Must only be called when
// IsAttachedToWebView() is true.
WKBackForwardListItem* GetWKItemAtIndex(size_t index) const;
WKBasedNavigationManagerImpl* navigation_manager_;
bool attached_to_web_view_;
std::vector<std::unique_ptr<NavigationItemImpl>> cached_items_;
int cached_current_item_index_;
// NavigationManagerImpl:
NavigationItemImpl* GetNavigationItemImplAtIndex(size_t index) const override;
NavigationItemImpl* GetLastCommittedItemInCurrentOrRestoredSession()
const override;
int GetLastCommittedItemIndexInCurrentOrRestoredSession() const override;
// Returns the pending navigation item in the main frame. Unlike
// GetPendingItem(), this method does not return null during session
// restoration (and returns last known pending item instead).
NavigationItemImpl* GetPendingItemInCurrentOrRestoredSession() const override;
NavigationItemImpl* GetTransientItemImpl() const override;
void FinishGoToIndex(int index,
NavigationInitiationType initiation_type,
bool has_user_gesture) override;
void FinishReload() override;
void FinishLoadURLWithParams() override;
bool IsPlaceholderUrl(const GURL& url) const override;
// Restores the specified navigation session in the current web view. This
// differs from Restore() in that it doesn't reset the current navigation
// history to empty before restoring. It simply appends the restored session
// after the current item, effectively replacing only the forward history.
// |last_committed_item_index| is the 0-based index into |items| that the web
// view should be navigated to at the end of the restoration.
void UnsafeRestore(int last_committed_item_index,
std::vector<std::unique_ptr<NavigationItem>> items);
// The pending main frame navigation item. This is nullptr if there is no
// pending item or if the pending item is a back-forward navigation, in which
// case the NavigationItemImpl is stored on the WKBackForwardListItem.
std::unique_ptr<NavigationItemImpl> pending_item_;
// -1 if pending_item_ represents a new navigation or there is no pending
// navigation. Otherwise, this is the index of the pending_item in the
// back-forward list.
int pending_item_index_;
// Index of the previous navigation item in the main frame. If there is none,
// this field will have value -1.
int previous_item_index_;
// Index of the last committed item in the main frame. If there is none, this
// field will equal to -1.
int last_committed_item_index_;
// The NavigationItem that corresponds to the empty window open navigation. It
// has to be stored separately because it has no WKBackForwardListItem. It is
// not null if when CommitPendingItem() is last called, the WKBackForwardList
// is empty but not nil. Any subsequent call to CommitPendingItem() will reset
// this field to null.
std::unique_ptr<NavigationItemImpl> empty_window_open_item_;
// The transient item in main frame.
std::unique_ptr<NavigationItemImpl> transient_item_;
// Time smoother for navigation item timestamps. See comment in
// navigation_controller_impl.h.
// NOTE: This is mutable because GetNavigationItemImplAtIndex() needs to call
// TimeSmoother::GetSmoothedTime() with a const 'this'. Since NavigationItems
// have to be lazily created on read, this is the only workaround.
mutable TimeSmoother time_smoother_;
WKWebViewCache web_view_cache_;
// Whether this navigation manager is in the process of restoring session
// history into WKWebView. It is set in Restore() and unset in the first
// OnNavigationItemCommitted() callback.
bool is_restore_session_in_progress_ = false;
// Non null during the session restoration. Created when session restoration
// is started and reset when the restoration is finished. Used to log UMA
// histogram that measures session restoration time.
std::unique_ptr<base::ElapsedTimer> restoration_timer_;
// The active navigation entry in the restored session. GetVisibleItem()
// returns this item when |is_restore_session_in_progress_| is true so that
// clients of this navigation manager gets sane values for visible title and
// URL.
std::unique_ptr<NavigationItem> restored_visible_item_;
// Non-empty only during the session restoration. The callbacks are
// registered in AddRestoreCompletionCallback() and are executed in the first
// OnNavigationItemCommitted() callback.
std::vector<base::OnceClosure> restore_session_completion_callbacks_;
} // namespace web