Sylvain Defresne | 3e6c490 | 2021-11-04 14:46:39 | [diff] [blame] | 1 | # Unrealized `WebState` |
| 2 | |
Sylvain Defresne | 425a00b | 2022-07-20 13:54:56 | [diff] [blame] | 3 | > **Status**: launched. |
Sylvain Defresne | 3e6c490 | 2021-11-04 14:46:39 | [diff] [blame] | 4 | |
| 5 | On iOS, each tab is implemented by a `WebState` and some TabHelpers. As users |
| 6 | can have many tabs open at the same time, but only few of them visible, an |
| 7 | optimisation to reduce the memory pressure is to allow `WebState`s to exist |
| 8 | in an incomplete state upon session restoration. |
| 9 | |
| 10 | This incomplete state is called "unrealized". When in the unrealized state, |
| 11 | a `WebState` will not have a corresponding WKWebView, nor any of the objects |
| 12 | that implement navigation (such as NavigationManager, ...). |
| 13 | |
| 14 | WebState can transition from "unrealized" to "realized" either lazily when |
| 15 | the client code request a functionality that cannot be provided in that |
| 16 | state (such as accessing the NavigationManager, displaying, ...) or it can |
| 17 | be forced by calling the `WebState::ForceRealized()` method. This is a one |
| 18 | way transition, it is not possible for a "realized" WebState to get back |
| 19 | into the "unrealized" state (at least in the initial implementation). |
| 20 | |
| 21 | To avoid unnecessary transition to the "realized" state , the |
| 22 | `WebState::IsRealized()` method can be called. This can be used by TabHelpers |
| 23 | to delay their initialisation until the `WebState` become "realized". To be |
| 24 | informed of the transition, they can listen for the |
| 25 | `WebStateObserver::WebStateRealized()` event which will be invoked upon |
| 26 | transition of the `WebState` from "unrealized" to "realized". |
| 27 | |
| 28 | ## Features available on "unrealized" WebState |
| 29 | |
| 30 | An "unrealized" `WebState` supports the following features: |
| 31 | |
| 32 | - registering and removing Observers |
| 33 | - registering and removing WebStatePolicyDecider |
Sylvain Defresne | 3e6c490 | 2021-11-04 14:46:39 | [diff] [blame] | 34 | - `const` property getters (*) |
| 35 | - retrieving saved state (**) |
| 36 | - attaching tab helpers |
| 37 | |
| 38 | (*) : all of the `const` property getters can be called on an "unrealized" |
| 39 | `WebState` but they may return a default value (`false`, `nil`, `nullptr`, |
| 40 | empty string, ... if the information cannot be retrieved from the serialised |
| 41 | state). |
| 42 | |
| 43 | (**): retrieving the saved state is supported to save the session (as the |
| 44 | code to save the session currently needs the state of all `WebState`s). |
| 45 | |
| 46 | ## When are `WebState` created in "unrealized" state |
| 47 | |
| 48 | The `WebState` are usually created in the "unrealized" state when a session |
| 49 | is restored. The reason is that restoring a session may create many `WebState` |
| 50 | when only few of them will be immediately used, while other ways to create a |
| 51 | `WebState` (opening a new tab, preloading, ...) lead to immediate use. |
| 52 | |
| 53 | As seen previously, `WebState` can only transition from the "unrealized" to |
| 54 | the "realized" state. This means that `WebState` in the "unrealized" state |
| 55 | would have been created directly in that state. |
| 56 | |
| 57 | The `WebState` are not necessarily created in the "unrealized" state upon |
| 58 | session restoration. This is controlled by the `enable_unrealized_web_states` |
| 59 | gn variable (compilation) and the `#lazily-create-web-state-on-restoration` |
| 60 | flag (runtime). |
| 61 | |
| 62 | The transition to "realized" state does not require any action from the client |
| 63 | of the `WebState`. Only internal state of the `WebState` will be affected. The |
| 64 | observers registered will still be valid, as are the policy decider, the script |
| 65 | callbacks, ... The client code may want to listen to the transition to activate |
| 66 | itself if its behaviour depends on internal objects of the `WebState` (such as |
| 67 | the `NavigationManager`). |
| 68 | |
| 69 | ## Example of `FindTabHelper` |
| 70 | |
| 71 | `FindTabHelper` is a TabHelper that implements the "find in page" feature. It |
| 72 | wants to create a `FindInPageController` which needs a "realized" `WebState`. |
| 73 | To support "unrealized" `WebState`, the creation of the `FindInPageController` |
| 74 | is delayed until the `WebState` transitions to "realized" state. |
| 75 | |
| 76 | This is done in the following way: |
| 77 | |
| 78 | ```cpp |
| 79 | FindTabHelper::FindTabHelper(web::WebState* web_state) { |
| 80 | DCHECK(web_state); |
| 81 | web_state_observation_.Observe(web_state); |
| 82 | if (web_state->IsRealized()) { |
| 83 | CreateFindInPageController(web_state); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | void FindTabHelper::SetResponseDelegate( |
| 88 | id<FindInPageResponseDelegate> response_delegate) { |
| 89 | if (!_controller) { |
| 90 | response_delegate_ = response_delegate; |
| 91 | } else { |
| 92 | controller_.responseDelegate = response_delegate; |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | bool FindTabHelper::CurrentPageSupportsFindInPage() const { |
| 97 | // As sending a message to `nil` returns the default value for a type |
| 98 | // (`false` for `bool`), it is not needed to check `controller_` first. |
| 99 | return [controller_ canFindInPage]; |
| 100 | } |
| 101 | |
| 102 | // Other FindTabHelper methods that implement the "find in page" feature. |
| 103 | |
| 104 | void FindTabHelper::CreateFindInPageController(web::WebState* web_state) { |
| 105 | DCHECK(!controller_); |
| 106 | DCHECK(web_state->IsRealized()); |
| 107 | controller_ = [[FindInPageController alloc] initWithWebState:web_state]; |
| 108 | if (response_delegate_) { |
| 109 | controller_.responseDelegate = response_delegate_; |
| 110 | response_delegate_ = nil; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | void FindTabHelper::WebStateRealized(web::WebState* web_state) { |
| 115 | CreateFindInPageController(web_state); |
| 116 | } |
| 117 | ``` |