chromium/docs/ios/unrealized_web_state.md

# Unrealized `WebState`

> **Status**: launched.

On iOS, each tab is implemented by a `WebState` and some TabHelpers. As users
can have many tabs open at the same time, but only few of them visible, an
optimisation to reduce the memory pressure is to allow `WebState`s to exist
in an incomplete state upon session restoration.

This incomplete state is called "unrealized". When in the unrealized state,
a `WebState` will not have a corresponding WKWebView, nor any of the objects
that implement navigation (such as NavigationManager, ...).

WebState can transition from "unrealized" to "realized" either lazily when
the client code request a functionality that cannot be provided in that
state (such as accessing the NavigationManager, displaying, ...) or it can
be forced by calling the `WebState::ForceRealized()` method. This is a one
way transition, it is not possible for a "realized" WebState to get back
into the "unrealized" state (at least in the initial implementation).

To avoid unnecessary transition to the "realized" state , the
`WebState::IsRealized()` method can be called. This can be used by TabHelpers
to delay their initialisation until the `WebState` become "realized". To be
informed of the transition, they can listen for the
`WebStateObserver::WebStateRealized()` event which will be invoked upon
transition of the `WebState` from "unrealized" to "realized".

## Features available on "unrealized" WebState

An "unrealized" `WebState` supports the following features:

-   registering and removing Observers
-   registering and removing WebStatePolicyDecider
-   `const` property getters (*)
-   retrieving saved state (**)
-   attaching tab helpers

(*) : all of the `const` property getters can be called on an "unrealized"
`WebState` but they may return a default value (`false`, `nil`, `nullptr`,
empty string, ... if the information cannot be retrieved from the serialised
state).

(**): retrieving the saved state is supported to save the session (as the
code to save the session currently needs the state of all `WebState`s).

## When are `WebState` created in "unrealized" state

The `WebState` are usually created in the "unrealized" state when a session
is restored. The reason is that restoring a session may create many `WebState`
when only few of them will be immediately used, while other ways to create a
`WebState` (opening a new tab, preloading, ...) lead to immediate use.

As seen previously, `WebState` can only transition from the "unrealized" to
the "realized" state. This means that `WebState` in the "unrealized" state
would have been created directly in that state.

The `WebState` are not necessarily created in the "unrealized" state upon
session restoration. This is controlled by the `enable_unrealized_web_states`
gn variable (compilation) and the `#lazily-create-web-state-on-restoration`
flag (runtime).

The transition to "realized" state does not require any action from the client
of the `WebState`. Only internal state of the `WebState` will be affected. The
observers registered will still be valid, as are the policy decider, the script
callbacks, ... The client code may want to listen to the transition to activate
itself if its behaviour depends on internal objects of the `WebState` (such as
the `NavigationManager`).

## Example of `FindTabHelper`

`FindTabHelper` is a TabHelper that implements the "find in page" feature. It
wants to create a `FindInPageController` which needs a "realized" `WebState`.
To support "unrealized" `WebState`, the creation of the `FindInPageController`
is delayed until the `WebState` transitions to "realized" state.

This is done in the following way:

```cpp
FindTabHelper::FindTabHelper(web::WebState* web_state) {
  DCHECK(web_state);
  web_state_observation_.Observe(web_state);
  if (web_state->IsRealized()) {
    CreateFindInPageController(web_state);
  }
}

void FindTabHelper::SetResponseDelegate(
    id<FindInPageResponseDelegate> response_delegate) {
  if (!_controller) {
    response_delegate_ = response_delegate;
  } else {
    controller_.responseDelegate = response_delegate;
  }
}

bool FindTabHelper::CurrentPageSupportsFindInPage() const {
  // As sending a message to `nil` returns the default value for a type
  // (`false` for `bool`), it is not needed to check `controller_` first.
  return [controller_ canFindInPage];
}

// Other FindTabHelper methods that implement the "find in page" feature.

void FindTabHelper::CreateFindInPageController(web::WebState* web_state) {
  DCHECK(!controller_);
  DCHECK(web_state->IsRealized());
  controller_ = [[FindInPageController alloc] initWithWebState:web_state];
  if (response_delegate_) {
    controller_.responseDelegate = response_delegate_;
    response_delegate_ = nil;
  }
}

void FindTabHelper::WebStateRealized(web::WebState* web_state) {
  CreateFindInPageController(web_state);
}
```