chromium/ios/chrome/browser/prerender/model/prerender_service_impl.mm

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/prerender/model/prerender_service_impl.h"

#import "base/metrics/histogram_macros.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_util.h"
#import "ios/chrome/browser/prerender/model/preload_controller.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web/model/load_timing_tab_helper.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "ui/base/page_transition_types.h"

PrerenderServiceImpl::PrerenderServiceImpl(ChromeBrowserState* browser_state)
    : controller_(
          [[PreloadController alloc] initWithBrowserState:browser_state]) {}

PrerenderServiceImpl::~PrerenderServiceImpl() = default;

void PrerenderServiceImpl::Shutdown() {
  [controller_ browserStateDestroyed];
  controller_ = nil;
}

void PrerenderServiceImpl::SetDelegate(id<PreloadControllerDelegate> delegate) {
  controller_.delegate = delegate;
}

void PrerenderServiceImpl::StartPrerender(const GURL& url,
                                          const web::Referrer& referrer,
                                          ui::PageTransition transition,
                                          web::WebState* web_state_to_replace,
                                          bool immediately) {
  [controller_ prerenderURL:url
                   referrer:referrer
                 transition:transition
            currentWebState:web_state_to_replace
                immediately:immediately];
}

bool PrerenderServiceImpl::MaybeLoadPrerenderedURL(
    const GURL& url,
    ui::PageTransition transition,
    Browser* browser) {
  if (!HasPrerenderForUrl(url)) {
    CancelPrerender();
    return false;
  }

  std::unique_ptr<web::WebState> new_web_state =
      [controller_ releasePrerenderContents];
  if (!new_web_state) {
    CancelPrerender();
    return false;
  }

  WebStateList* web_state_list = browser->GetWebStateList();

  // Due to some security workarounds inside ios/web, sometimes a restored
  // webState may mark new navigations as renderer initiated instead of browser
  // initiated. As a result 'visible url' of preloaded web state will be
  // 'last committed  url', and not 'url typed by the user'. As these
  // navigations are uncommitted, and make the omnibox (or NTP) look strange,
  // simply drop them.  See crbug.com/1020497 for the strange UI, and
  // crbug.com/1010765 for the triggering security fixes.
  if (web_state_list->GetActiveWebState()->GetVisibleURL() ==
      new_web_state->GetVisibleURL()) {
    CancelPrerender();
    return false;
  }

  DCHECK_NE(WebStateList::kInvalidIndex, web_state_list->active_index());

  loading_prerender_ = true;
  web_state_list->ReplaceWebStateAt(web_state_list->active_index(),
                                    std::move(new_web_state));
  loading_prerender_ = false;
  // new_web_state is now null after the std::move, so grab a new pointer to
  // it for further updates.
  web::WebState* active_web_state = web_state_list->GetActiveWebState();

  bool typed_or_generated_transition =
      PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
      PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
  if (typed_or_generated_transition) {
    LoadTimingTabHelper::FromWebState(active_web_state)
        ->DidPromotePrerenderTab();
  }
  ChromeBrowserState* browser_state = browser->GetBrowserState();
  SessionRestorationServiceFactory::GetForBrowserState(browser_state)
      ->ScheduleSaveSessions();
  return true;
}

bool PrerenderServiceImpl::IsLoadingPrerender() {
  return loading_prerender_;
}

void PrerenderServiceImpl::CancelPrerender() {
  [controller_ cancelPrerender];
}

bool PrerenderServiceImpl::HasPrerenderForUrl(const GURL& url) {
  return url == controller_.prerenderedURL;
}

bool PrerenderServiceImpl::IsWebStatePrerendered(web::WebState* web_state) {
  return [controller_ isWebStatePrerendered:web_state];
}