1 // Copyright 2013 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.
5 #include "ios/web/web_state/web_state_impl.h"
7 #include "base/strings/sys_string_conversions.h"
8 #include "ios/web/interstitials/web_interstitial_impl.h"
9 #import "ios/web/navigation/crw_session_controller.h"
10 #import "ios/web/navigation/crw_session_entry.h"
11 #include "ios/web/navigation/navigation_item_impl.h"
12 #include "ios/web/net/request_group_util.h"
13 #include "ios/web/public/browser_state.h"
14 #include "ios/web/public/navigation_item.h"
15 #include "ios/web/public/url_util.h"
16 #include "ios/web/public/web_client.h"
17 #include "ios/web/public/web_state/credential.h"
18 #include "ios/web/public/web_state/web_state_observer.h"
19 #import "ios/web/web_state/ui/crw_web_controller.h"
20 #include "ios/web/web_state/web_state_facade_delegate.h"
21 #import "ios/web/webui/web_ui_ios_controller_factory_registry.h"
22 #import "ios/web/webui/web_ui_ios_impl.h"
23 #include "net/http/http_response_headers.h"
27 WebStateImpl::WebStateImpl(BrowserState* browser_state)
29 facade_delegate_(nullptr),
31 navigation_manager_(this, browser_state),
32 interstitial_(nullptr),
33 cache_mode_(net::RequestTracker::CACHE_NORMAL) {
36 WebStateImpl::~WebStateImpl() {
37 // WebUI depends on web state so it must be destroyed first in case any WebUI
38 // implementations depends on accessing web state during destruction.
41 // The facade layer (owned by the delegate) should be destroyed before the web
43 DCHECK(!facade_delegate_);
45 FOR_EACH_OBSERVER(WebStateObserver, observers_, WebStateDestroyed());
46 FOR_EACH_OBSERVER(WebStateObserver, observers_, ResetWebState());
47 DCHECK(script_command_callbacks_.empty());
48 if (request_tracker_.get())
49 CloseRequestTracker();
52 void WebStateImpl::AddObserver(WebStateObserver* observer) {
53 DCHECK(!observers_.HasObserver(observer));
54 observers_.AddObserver(observer);
57 void WebStateImpl::RemoveObserver(WebStateObserver* observer) {
58 DCHECK(observers_.HasObserver(observer));
59 observers_.RemoveObserver(observer);
62 bool WebStateImpl::Configured() const {
63 return web_controller_ != nil;
66 void WebStateImpl::SetWebController(CRWWebController* web_controller) {
67 DCHECK(!web_controller_);
68 web_controller_ = web_controller;
71 WebStateFacadeDelegate* WebStateImpl::GetFacadeDelegate() const {
72 return facade_delegate_;
75 void WebStateImpl::SetFacadeDelegate(WebStateFacadeDelegate* facade_delegate) {
76 facade_delegate_ = facade_delegate;
79 WebStateImpl* WebStateImpl::CopyForSessionWindow() {
80 WebStateImpl* copy = new WebStateImpl(GetBrowserState());
81 copy->GetNavigationManagerImpl().CopyState(&navigation_manager_);
85 void WebStateImpl::OnUrlHashChanged() {
86 FOR_EACH_OBSERVER(WebStateObserver, observers_, UrlHashChanged());
89 void WebStateImpl::OnHistoryStateChanged() {
90 FOR_EACH_OBSERVER(WebStateObserver, observers_, HistoryStateChanged());
93 bool WebStateImpl::OnScriptCommandReceived(const std::string& command,
94 const base::DictionaryValue& value,
96 bool user_is_interacting) {
97 size_t dot_position = command.find_first_of('.');
98 if (dot_position == 0 || dot_position == std::string::npos)
101 std::string prefix = command.substr(0, dot_position);
102 auto it = script_command_callbacks_.find(prefix);
103 if (it == script_command_callbacks_.end())
106 return it->second.Run(value, url, user_is_interacting);
109 void WebStateImpl::SetIsLoading(bool is_loading) {
110 DCHECK(Configured());
111 if (is_loading == is_loading_)
114 is_loading_ = is_loading;
115 if (facade_delegate_)
116 facade_delegate_->OnLoadingStateChanged();
119 FOR_EACH_OBSERVER(WebStateObserver, observers_, DidStartLoading());
121 FOR_EACH_OBSERVER(WebStateObserver, observers_, DidStopLoading());
125 bool WebStateImpl::IsLoading() const {
129 void WebStateImpl::OnPageLoaded(const GURL& url, bool load_success) {
130 UpdateHttpResponseHeaders(url);
131 if (facade_delegate_)
132 facade_delegate_->OnPageLoaded();
134 PageLoadCompletionStatus load_completion_status =
135 load_success ? PageLoadCompletionStatus::SUCCESS
136 : PageLoadCompletionStatus::FAILURE;
137 FOR_EACH_OBSERVER(WebStateObserver, observers_,
138 PageLoaded(load_completion_status));
141 void WebStateImpl::OnFormActivityRegistered(const std::string& form_name,
142 const std::string& field_name,
143 const std::string& type,
144 const std::string& value,
146 bool input_missing) {
147 FOR_EACH_OBSERVER(WebStateObserver, observers_,
148 FormActivityRegistered(form_name, field_name, type, value,
149 key_code, input_missing));
152 void WebStateImpl::OnAutocompleteRequested(const GURL& source_url,
153 const std::string& form_name,
154 bool user_initiated) {
156 WebStateObserver, observers_,
157 AutocompleteRequested(source_url, form_name, user_initiated));
160 void WebStateImpl::OnFaviconUrlUpdated(
161 const std::vector<FaviconURL>& candidates) {
162 FOR_EACH_OBSERVER(WebStateObserver, observers_,
163 FaviconUrlUpdated(candidates));
166 void WebStateImpl::OnCredentialsRequested(
168 const GURL& source_url,
170 const std::vector<std::string>& federations,
171 bool user_interaction) {
172 FOR_EACH_OBSERVER(WebStateObserver, observers_,
173 CredentialsRequested(request_id, source_url, suppress_ui,
174 federations, user_interaction));
177 void WebStateImpl::OnSignedIn(int request_id,
178 const GURL& source_url,
179 const web::Credential& credential) {
180 FOR_EACH_OBSERVER(WebStateObserver, observers_,
181 SignedIn(request_id, source_url, credential));
184 void WebStateImpl::OnSignedIn(int request_id, const GURL& source_url) {
185 FOR_EACH_OBSERVER(WebStateObserver, observers_,
186 SignedIn(request_id, source_url));
189 void WebStateImpl::OnSignedOut(int request_id, const GURL& source_url) {
190 FOR_EACH_OBSERVER(WebStateObserver, observers_,
191 SignedOut(request_id, source_url));
194 void WebStateImpl::OnSignInFailed(int request_id,
195 const GURL& source_url,
196 const web::Credential& credential) {
197 FOR_EACH_OBSERVER(WebStateObserver, observers_,
198 SignInFailed(request_id, source_url, credential));
201 void WebStateImpl::OnSignInFailed(int request_id, const GURL& source_url) {
202 FOR_EACH_OBSERVER(WebStateObserver, observers_,
203 SignInFailed(request_id, source_url));
206 void WebStateImpl::OnDocumentSubmitted(const std::string& form_name,
207 bool user_initiated) {
208 FOR_EACH_OBSERVER(WebStateObserver, observers_,
209 DocumentSubmitted(form_name, user_initiated));
212 NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() {
213 return navigation_manager_;
216 const NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() const {
217 return navigation_manager_;
220 // There are currently two kinds of WebUI: those that have been adapted to
221 // web::WebUIIOS, and those that are still using content::WebUI. Try to create
222 // it as the first, and then fall back to the latter if necessary.
223 void WebStateImpl::CreateWebUI(const GURL& url) {
224 web_ui_.reset(CreateWebUIIOS(url));
225 if (!web_ui_ && facade_delegate_)
226 facade_delegate_->CreateLegacyWebUI(url);
229 void WebStateImpl::ClearWebUI() {
230 if (facade_delegate_)
231 facade_delegate_->ClearLegacyWebUI();
235 bool WebStateImpl::HasWebUI() {
236 return web_ui_ || (facade_delegate_ && facade_delegate_->HasLegacyWebUI());
239 void WebStateImpl::ProcessWebUIMessage(const GURL& source_url,
240 const std::string& message,
241 const base::ListValue& args) {
243 DCHECK(!facade_delegate_ || !facade_delegate_->HasLegacyWebUI());
244 web_ui_->ProcessWebUIIOSMessage(source_url, message, args);
245 } else if (facade_delegate_) {
246 facade_delegate_->ProcessLegacyWebUIMessage(source_url, message, args);
250 void WebStateImpl::LoadWebUIHtml(const base::string16& html, const GURL& url) {
251 CHECK(web::GetWebClient()->IsAppSpecificURL(url));
252 [web_controller_ loadHTML:base::SysUTF16ToNSString(html)
253 forAppSpecificURL:url];
256 const base::string16& WebStateImpl::GetTitle() const {
257 // TODO(stuartmorgan): Implement the NavigationManager logic necessary to
258 // match the WebContents implementation of this method.
259 DCHECK(Configured());
260 web::NavigationItem* item = navigation_manager_.GetLastCommittedItem();
262 return item->GetTitleForDisplay(
263 web::GetWebClient()->GetAcceptLangs(GetBrowserState()));
265 return empty_string16_;
268 bool WebStateImpl::IsShowingWebInterstitial() const {
269 // Technically we could have |interstitial_| set but its view isn't
270 // being displayed, but there's no code path where that could occur.
271 return interstitial_ != nullptr;
274 WebInterstitial* WebStateImpl::GetWebInterstitial() const {
275 return interstitial_;
278 net::HttpResponseHeaders* WebStateImpl::GetHttpResponseHeaders() const {
279 return http_response_headers_.get();
282 void WebStateImpl::OnHttpResponseHeadersReceived(
283 net::HttpResponseHeaders* response_headers,
284 const GURL& resource_url) {
285 // Store the headers in a map until the page finishes loading, as we do not
286 // know which URL corresponds to the main page yet.
287 // Remove the hash (if any) as it is sometimes altered by in-page navigations.
288 const GURL& url = GURLByRemovingRefFromGURL(resource_url);
289 response_headers_map_[url] = response_headers;
292 void WebStateImpl::UpdateHttpResponseHeaders(const GURL& url) {
294 http_response_headers_ = NULL;
296 content_language_header_.clear();
298 // Discard all the response headers except the ones for |main_page_url|.
299 auto it = response_headers_map_.find(GURLByRemovingRefFromGURL(url));
300 if (it != response_headers_map_.end())
301 http_response_headers_ = it->second;
302 response_headers_map_.clear();
304 if (!http_response_headers_.get())
308 std::string mime_type;
309 http_response_headers_->GetMimeType(&mime_type);
310 mime_type_ = mime_type;
313 std::string content_language;
314 http_response_headers_->GetNormalizedHeader("content-language",
316 // Remove everything after the comma ',' if any.
317 size_t comma_index = content_language.find_first_of(',');
318 if (comma_index != std::string::npos)
319 content_language.resize(comma_index);
320 content_language_header_ = content_language;
323 void WebStateImpl::ClearWebInterstitialForNavigation() {
325 // DontProceed() dismisses the interstitial page in the same way as if it
326 // was closed by user action. Clearing interstitial_ early makes
327 // IsShowingWebInterstitial() return false so the code that is triggered in
328 // DontProceed knows that the interstitial page is not visible anymore.
329 WebInterstitialImpl* interstitial = interstitial_;
330 DismissWebInterstitial();
331 // In this case, DontProceed() may not remove an unsafe page from the nav
333 CRWSessionController* sessionController =
334 navigation_manager_.GetSessionController();
335 web::NavigationItem* currentItem =
336 [sessionController.currentEntry navigationItem];
337 if (currentItem->IsUnsafe()) {
338 // The unsafe page should be removed from history, and, in fact,
339 // SafeBrowsingBlockingPage will do just that *provided* that it
340 // isn't the current page. So to make this happen, before removing the
341 // interstitial, have the session controller go back one page.
342 [sessionController goBack];
344 [sessionController discardNonCommittedEntries];
345 interstitial->DontProceed();
349 void WebStateImpl::DisplayWebInterstitial(WebInterstitialImpl* interstitial) {
350 DCHECK(Configured());
351 DCHECK(interstitial);
352 interstitial_ = interstitial;
353 CGSize content_view_size = [web_controller_ view].bounds.size;
354 interstitial_->SetSize(
355 gfx::Size(content_view_size.width, content_view_size.height));
356 [web_controller_ displayInterstitialView:interstitial_->GetView()
357 withScrollView:interstitial_->GetScrollView()];
360 void WebStateImpl::DismissWebInterstitial() {
362 FOR_EACH_OBSERVER(WebStateObserver, observers_, InsterstitialDismissed());
363 [interstitial_->GetView() removeFromSuperview];
364 interstitial_ = nullptr;
368 WebUIIOS* WebStateImpl::CreateWebUIIOS(const GURL& url) {
369 WebUIIOSControllerFactory* factory =
370 WebUIIOSControllerFactoryRegistry::GetInstance();
373 WebUIIOSImpl* web_ui = new WebUIIOSImpl(this);
374 WebUIIOSController* controller =
375 factory->CreateWebUIIOSControllerForURL(web_ui, url);
377 web_ui->SetController(controller);
385 void WebStateImpl::ExecuteJavaScriptAsync(const base::string16& javascript) {
386 DCHECK(Configured());
387 [web_controller_ evaluateJavaScript:base::SysUTF16ToNSString(javascript)
388 stringResultHandler:nil];
391 #pragma mark - RequestTracker management
393 void WebStateImpl::InitializeRequestTracker(
394 id<CRWRequestTrackerDelegate> delegate) {
395 BrowserState* browser_state = navigation_manager_.GetBrowserState();
396 request_tracker_ = RequestTrackerImpl::CreateTrackerForRequestGroupID(
397 GetRequestGroupID(), browser_state, browser_state->GetRequestContext(),
401 void WebStateImpl::CloseRequestTracker() {
402 request_tracker_->Close();
403 request_tracker_ = NULL;
406 RequestTrackerImpl* WebStateImpl::GetRequestTracker() {
407 DCHECK(request_tracker_.get());
408 return request_tracker_.get();
411 net::RequestTracker::CacheMode WebStateImpl::GetCacheMode() {
415 void WebStateImpl::SetCacheMode(net::RequestTracker::CacheMode mode) {
419 NSString* WebStateImpl::GetRequestGroupID() {
420 if (request_group_id_.get() == nil)
421 request_group_id_.reset([GenerateNewRequestGroupID() copy]);
423 return request_group_id_;
426 int WebStateImpl::DownloadImage(
429 uint32_t max_bitmap_size,
431 const ImageDownloadCallback& callback) {
432 // |is_favicon| specifies whether the download of the image occurs with
433 // cookies or not. Currently, only downloads without cookies are supported.
434 // |bypass_cache| is ignored since the downloads never go through a cache.
436 return [[web_controller_ delegate] downloadImageAtUrl:url
437 maxBitmapSize:max_bitmap_size
441 #pragma mark - WebState implementation
443 UIView* WebStateImpl::GetView() {
444 return [web_controller_ view];
447 WebViewType WebStateImpl::GetWebViewType() const {
448 return [web_controller_ webViewType];
451 BrowserState* WebStateImpl::GetBrowserState() const {
452 return navigation_manager_.GetBrowserState();
455 void WebStateImpl::OpenURL(const WebState::OpenURLParams& params) {
456 DCHECK(Configured());
457 ClearWebInterstitialForNavigation();
458 [[web_controller_ delegate] openURLWithParams:params];
461 NavigationManager* WebStateImpl::GetNavigationManager() {
462 return &GetNavigationManagerImpl();
465 CRWJSInjectionReceiver* WebStateImpl::GetJSInjectionReceiver() const {
466 return [web_controller_ jsInjectionReceiver];
469 const std::string& WebStateImpl::GetContentLanguageHeader() const {
470 return content_language_header_;
473 const std::string& WebStateImpl::GetContentsMimeType() const {
477 bool WebStateImpl::ContentIsHTML() const {
478 return [web_controller_ contentIsHTML];
481 const GURL& WebStateImpl::GetVisibleURL() const {
482 web::NavigationItem* item = navigation_manager_.GetVisibleItem();
483 return item ? item->GetVirtualURL() : GURL::EmptyGURL();
486 const GURL& WebStateImpl::GetLastCommittedURL() const {
487 web::NavigationItem* item = navigation_manager_.GetLastCommittedItem();
488 return item ? item->GetVirtualURL() : GURL::EmptyGURL();
491 GURL WebStateImpl::GetCurrentURL(URLVerificationTrustLevel* trust_level) const {
492 return [web_controller_ currentURLWithTrustLevel:trust_level];
495 void WebStateImpl::AddScriptCommandCallback(
496 const ScriptCommandCallback& callback,
497 const std::string& command_prefix) {
498 DCHECK(!command_prefix.empty());
499 DCHECK(command_prefix.find_first_of('.') == std::string::npos);
500 DCHECK(script_command_callbacks_.find(command_prefix) ==
501 script_command_callbacks_.end());
502 script_command_callbacks_[command_prefix] = callback;
505 void WebStateImpl::RemoveScriptCommandCallback(
506 const std::string& command_prefix) {
507 DCHECK(script_command_callbacks_.find(command_prefix) !=
508 script_command_callbacks_.end());
509 script_command_callbacks_.erase(command_prefix);
512 id<CRWWebViewProxy> WebStateImpl::GetWebViewProxy() const {
513 return [web_controller_ webViewProxy];
516 void WebStateImpl::OnProvisionalNavigationStarted(const GURL& url) {
517 FOR_EACH_OBSERVER(WebStateObserver, observers_,
518 ProvisionalNavigationStarted(url));
521 #pragma mark - NavigationManagerDelegate implementation
523 // Mirror WebContentsImpl::NavigateToPendingEntry() so that
524 // NavigationControllerIO::GoBack() actually goes back.
525 void WebStateImpl::NavigateToPendingEntry() {
526 [web_controller_ loadCurrentURL];
529 void WebStateImpl::OnNavigationItemCommitted(
530 const LoadCommittedDetails& load_details) {
531 FOR_EACH_OBSERVER(WebStateObserver, observers_,
532 NavigationItemCommitted(load_details));
535 WebState* WebStateImpl::GetWebState() {