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