1 // Copyright 2017 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef IOS_WEB_NAVIGATION_WK_BASED_NAVIGATION_MANAGER_IMPL_H_ 6 #define IOS_WEB_NAVIGATION_WK_BASED_NAVIGATION_MANAGER_IMPL_H_ 7 8 #include <stddef.h> 9 10 #include <memory> 11 #include <vector> 12 13 #include "base/callback.h" 14 #include "base/macros.h" 15 #import "ios/web/navigation/navigation_item_impl.h" 16 #import "ios/web/navigation/navigation_manager_impl.h" 17 #include "ios/web/navigation/time_smoother.h" 18 #include "ios/web/public/navigation/reload_type.h" 19 #include "ui/base/page_transition_types.h" 20 #include "url/gurl.h" 21 22 @class WKBackForwardListItem; 23 24 namespace base { 25 class ElapsedTimer; 26 } 27 28 namespace web { 29 class BrowserState; 30 class NavigationItem; 31 struct Referrer; 32 class SessionStorageBuilder; 33 34 // Name of UMA histogram to log the time spent on asynchronous session 35 // restoration. 36 extern const char kRestoreNavigationTime[]; 37 38 // WKBackForwardList based implementation of NavigationManagerImpl. 39 // This class relies on the following WKWebView APIs, defined by the 40 // CRWWebViewNavigationProxy protocol: 41 // @property URL 42 // @property backForwardList 43 // - goToBackForwardListItem: 44 // 45 // This navigation manager uses WKBackForwardList as the ground truth for back- 46 // forward navigation history. It uses the Associated Objects runtime feature 47 // to link a NavigationItemImpl object to each WKBackForwardListItem to store 48 // additional states needed by the embedder. 49 // 50 // For all main frame navigations (both UI-initiated and renderer-initiated), 51 // the NavigationItemImpl objects are created proactively via AddPendingItem and 52 // CommitPendingItem. 53 // 54 // Non-main-frame navigations can only be initiated from the renderer. The 55 // NavigationItemImpl objects in this case are created lazily in GetItemAtIndex 56 // because the provisional load and commit events for iframe navigation are not 57 // visible via the WKNavigationDelegate interface. Consequently, pending item 58 // and previous item are only tracked for the main frame. 59 // 60 // Empty Window Open Navigation edge case: 61 // 62 // If window.open() is called with an empty URL, WKWebView does not seem to 63 // create a WKBackForwardListItem for the first about:blank navigation. Any 64 // subsequent navigation in this window will replace the about:blank entry. 65 // This is consistent with the HTML spec regarding Location-object navigation 66 // when the browser context's only Document is about:blank: 67 // https://html.spec.whatwg.org/multipage/history.html (Section 7.7.4) 68 // 69 // This navigation manager will still create a pendingNavigationItem for this 70 // "empty window open item" and allow CommitPendingItem() to be called on it. 71 // All accessors will behave identically as if the navigation history has a 72 // single normal entry. The only difference is that a subsequent call to 73 // CommitPendingItem() will *replace* the empty window open item. From this 74 // point onward, it is as if the empty window open item never occurred. 75 // 76 // Detach from web view edge case: 77 // 78 // As long as this navigation manager is alive, the navigation manager 79 // delegate should not delete its WKWebView. However, legacy use cases exist 80 // (e.g. https://crbug/770914). As a workaround, before deleting the 81 // WKWebView, the delegate must call 82 // NavigationManagerImpl::DetachFromWebView() to cache the current session 83 // history. This puts the navigation manager in a detached state. While in 84 // this state, all getters are serviced using the cached session history. 85 // Mutation methods are not allowed. The navigation manager returns to the 86 // attached state when a new navigation starts. 87 class WKBasedNavigationManagerImpl : public NavigationManagerImpl { 88 public: 89 WKBasedNavigationManagerImpl(); 90 ~WKBasedNavigationManagerImpl() override; 91 92 // NavigationManagerImpl: 93 void InitializeSession() override; 94 void OnNavigationItemCommitted() override; 95 void OnNavigationStarted(const GURL& url) override; 96 void DetachFromWebView() override; 97 void AddTransientItem(const GURL& url) override; 98 void AddPendingItem(const GURL& url, 99 const web::Referrer& referrer, 100 ui::PageTransition navigation_type, 101 NavigationInitiationType initiation_type) override; 102 void CommitPendingItem() override; 103 void CommitPendingItem(std::unique_ptr<NavigationItemImpl> item) override; 104 std::unique_ptr<web::NavigationItemImpl> ReleasePendingItem() override; 105 void SetPendingItem(std::unique_ptr<web::NavigationItemImpl> item) override; 106 int GetIndexForOffset(int offset) const override; 107 void AddPushStateItemIfNecessary(const GURL& url, 108 NSString* state_object, 109 ui::PageTransition transition) override; 110 bool IsRestoreSessionInProgress() const override; 111 bool ShouldBlockUrlDuringRestore(const GURL& url) override; 112 void SetPendingItemIndex(int index) override; 113 void ApplyWKWebViewForwardHistoryClobberWorkaround() override; 114 void SetWKWebViewNextPendingUrlNotSerializable(const GURL& url) override; 115 116 // NavigationManager: 117 BrowserState* GetBrowserState() const override; 118 WebState* GetWebState() const override; 119 NavigationItem* GetVisibleItem() const override; 120 void DiscardNonCommittedItems() override; 121 int GetItemCount() const override; 122 NavigationItem* GetItemAtIndex(size_t index) const override; 123 int GetIndexOfItem(const NavigationItem* item) const override; 124 int GetPendingItemIndex() const override; 125 bool RemoveItemAtIndex(int index) override; 126 bool CanGoBack() const override; 127 bool CanGoForward() const override; 128 bool CanGoToOffset(int offset) const override; 129 void GoBack() override; 130 void GoForward() override; 131 NavigationItemList GetBackwardItems() const override; 132 NavigationItemList GetForwardItems() const override; 133 void CopyStateFromAndPrune(const NavigationManager* source) override; 134 bool CanPruneAllButLastCommittedItem() const override; 135 void Restore(int last_committed_item_index, 136 std::vector<std::unique_ptr<NavigationItem>> items) override; 137 void LoadURLWithParams(const NavigationManager::WebLoadParams&) override; 138 void AddRestoreCompletionCallback(base::OnceClosure callback) override; 139 void LoadIfNecessary() override; 140 141 private: 142 // The SessionStorageBuilder functions require access to private variables of 143 // NavigationManagerImpl. 144 friend SessionStorageBuilder; 145 146 // Access shim for NavigationItems associated with the WKBackForwardList. It 147 // is responsible for caching NavigationItems when the navigation manager 148 // detaches from its web view. 149 class WKWebViewCache { 150 public: 151 explicit WKWebViewCache(WKBasedNavigationManagerImpl* navigation_manager); 152 ~WKWebViewCache(); 153 154 // Returns true if the navigation manager is attached to a WKWebView. 155 bool IsAttachedToWebView() const; 156 157 // Caches NavigationItems from the WKWebView in |this| and changes state to 158 // detached. 159 void DetachFromWebView(); 160 161 // Clears the cached NavigationItems and resets state to attached. Callers 162 // that wish to restore the cached navigation items into the new web view 163 // must call ReleaseCachedItems() first. 164 void ResetToAttached(); 165 166 // Returns ownership of the cached NavigationItems. This is convenient for 167 // restoring session history when reattaching to a new web view. 168 std::vector<std::unique_ptr<NavigationItem>> ReleaseCachedItems(); 169 170 // Returns the number of items in the back-forward history. 171 size_t GetBackForwardListItemCount() const; 172 173 // Returns the absolute index of WKBackForwardList's |currentItem| or -1 if 174 // |currentItem| is nil. If navigation manager is in detached mode, returns 175 // the cached value of this property captured at the last call of 176 // DetachFromWebView(). 177 int GetCurrentItemIndex() const; 178 179 // Returns the visible WKWebView URL. If navigation manager is detached, 180 // returns an empty GURL. 181 GURL GetVisibleWebViewURL() const; 182 183 // Returns the NavigationItem associated with the WKBackForwardListItem at 184 // |index|. If |create_if_missing| is true and the WKBackForwardListItem 185 // does not have an associated NavigationItem, creates a new one and returns 186 // it to the caller. 187 NavigationItemImpl* GetNavigationItemImplAtIndex( 188 size_t index, 189 bool create_if_missing) const; 190 191 // Returns the WKBackForwardListItem at |index|. Must only be called when 192 // IsAttachedToWebView() is true. 193 WKBackForwardListItem* GetWKItemAtIndex(size_t index) const; 194 195 private: 196 WKBasedNavigationManagerImpl* navigation_manager_; 197 bool attached_to_web_view_; 198 199 std::vector<std::unique_ptr<NavigationItemImpl>> cached_items_; 200 int cached_current_item_index_; 201 202 DISALLOW_COPY_AND_ASSIGN(WKWebViewCache); 203 }; 204 205 // Type of the list passed to restore items. 206 enum class RestoreItemListType { 207 kBackList, 208 kForwardList, 209 }; 210 211 // NavigationManagerImpl: 212 NavigationItemImpl* GetNavigationItemImplAtIndex(size_t index) const override; 213 NavigationItemImpl* GetLastCommittedItemInCurrentOrRestoredSession() 214 const override; 215 int GetLastCommittedItemIndexInCurrentOrRestoredSession() const override; 216 // Returns the pending navigation item in the main frame. Unlike 217 // GetPendingItem(), this method does not return null during session 218 // restoration (and returns last known pending item instead). 219 NavigationItemImpl* GetPendingItemInCurrentOrRestoredSession() const override; 220 NavigationItemImpl* GetTransientItemImpl() const override; 221 void FinishGoToIndex(int index, 222 NavigationInitiationType initiation_type, 223 bool has_user_gesture) override; 224 void FinishReload() override; 225 void FinishLoadURLWithParams( 226 NavigationInitiationType initiation_type) override; 227 bool IsPlaceholderUrl(const GURL& url) const override; 228 229 // Restores the state of the |items_restored| in the navigation items 230 // associated with the WKBackForwardList. |back_list| is used to specify if 231 // the items passed are the list containing the back list or the forward list. 232 void RestoreItemsState( 233 RestoreItemListType list_type, 234 std::vector<std::unique_ptr<NavigationItem>> items_restored); 235 236 // Restores the specified navigation session in the current web view. This 237 // differs from Restore() in that it doesn't reset the current navigation 238 // history to empty before restoring. It simply appends the restored session 239 // after the current item, effectively replacing only the forward history. 240 // |last_committed_item_index| is the 0-based index into |items| that the web 241 // view should be navigated to at the end of the restoration. 242 void UnsafeRestore(int last_committed_item_index, 243 std::vector<std::unique_ptr<NavigationItem>> items); 244 245 // Returns true if |last_committed_item| matches WKWebView.URL when expected. 246 // WKWebView is more aggressive than Chromium is in updating the committed 247 // URL, and there are cases where, even though WKWebView's URL has updated, 248 // Chromium still wants to display last committed. Normally this is managed 249 // by WKBasedNavigationManagerImpl last committed, but there are short periods 250 // during fast navigations where WKWebView.URL has updated and ios/web can't 251 // validate what should be shown for the visible item. More importantly, 252 // there are bugs in WkWebView where WKWebView's URL and 253 // backForwardList.currentItem can fall out of sync. In these situations, 254 // return false as a safeguard so committed item is always trusted. 255 bool CanTrustLastCommittedItem( 256 const NavigationItem* last_committed_item) const; 257 258 // Update state to reflect session restore is complete, and call any post 259 // restore callbacks. 260 void FinalizeSessionRestore(); 261 262 // The pending main frame navigation item. This is nullptr if there is no 263 // pending item or if the pending item is a back-forward navigation, in which 264 // case the NavigationItemImpl is stored on the WKBackForwardListItem. 265 std::unique_ptr<NavigationItemImpl> pending_item_; 266 267 // -1 if pending_item_ represents a new navigation or there is no pending 268 // navigation. Otherwise, this is the index of the pending_item in the 269 // back-forward list. 270 int pending_item_index_; 271 272 // Index of the last committed item in the main frame. If there is none, this 273 // field will equal to -1. 274 int last_committed_item_index_; 275 276 // The NavigationItem that corresponds to the empty window open navigation. It 277 // has to be stored separately because it has no WKBackForwardListItem. It is 278 // not null if when CommitPendingItem() is last called, the WKBackForwardList 279 // is empty but not nil. Any subsequent call to CommitPendingItem() will reset 280 // this field to null. 281 std::unique_ptr<NavigationItemImpl> empty_window_open_item_; 282 283 // The transient item in main frame. 284 // TODO(crbug.com/1028755): Remove the transient item once SafeBrowsing is 285 // launched. 286 std::unique_ptr<NavigationItemImpl> transient_item_; 287 288 // A placeholder item used when CanTrustLastCommittedItem 289 // returns false. The navigation item returned uses crw_web_controller's 290 // documentURL as the URL. 291 mutable std::unique_ptr<NavigationItemImpl> last_committed_web_view_item_; 292 293 // Time smoother for navigation item timestamps. See comment in 294 // navigation_controller_impl.h. 295 // NOTE: This is mutable because GetNavigationItemImplAtIndex() needs to call 296 // TimeSmoother::GetSmoothedTime() with a const 'this'. Since NavigationItems 297 // have to be lazily created on read, this is the only workaround. 298 mutable TimeSmoother time_smoother_; 299 300 WKWebViewCache web_view_cache_; 301 302 // Whether this navigation manager is in the process of restoring session 303 // history into WKWebView. It is set in Restore() and unset in 304 // FinalizeSessionRestore(). 305 bool is_restore_session_in_progress_ = false; 306 307 // Set to true when delegate_->GoToBackForwardListItem is being called, which 308 // is useful to know when comparing the VisibleWebViewURL with the last 309 // committed item. 310 bool going_to_back_forward_list_item_ = false; 311 312 // Set to an URL when the next created pending item should set 313 // ShouldSkipSerialization to true, provided it matches |url|. 314 GURL next_pending_url_should_skip_serialization_; 315 316 // Non null during the session restoration. Created when session restoration 317 // is started and reset when the restoration is finished. Used to log UMA 318 // histogram that measures session restoration time. 319 std::unique_ptr<base::ElapsedTimer> restoration_timer_; 320 321 // The active navigation entry in the restored session. GetVisibleItem() 322 // returns this item in the window between |is_restore_session_in_progress_| 323 // becomes true until the first post-restore navigation is finished, so that 324 // clients of this navigation manager gets sane values for visible title and 325 // URL. 326 std::unique_ptr<NavigationItem> restored_visible_item_; 327 328 // Non-empty only during the session restoration. The callbacks are 329 // registered in AddRestoreCompletionCallback() and are executed in 330 // FinalizeSessionRestore(). 331 std::vector<base::OnceClosure> restore_session_completion_callbacks_; 332 333 DISALLOW_COPY_AND_ASSIGN(WKBasedNavigationManagerImpl); 334 }; 335 336 } // namespace web 337 338 #endif // IOS_WEB_NAVIGATION_WK_BASED_NAVIGATION_MANAGER_IMPL_H_ 339