Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ios / web / web_state / web_state_impl.mm
blob2071eae1de642f73e36b409c2dde3e61b4026f2f
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/ui/crw_content_view.h"
19 #include "ios/web/public/web_state/web_state_observer.h"
20 #include "ios/web/public/web_state/web_state_policy_decider.h"
21 #import "ios/web/web_state/ui/crw_web_controller.h"
22 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
23 #include "ios/web/web_state/web_state_facade_delegate.h"
24 #import "ios/web/webui/web_ui_ios_controller_factory_registry.h"
25 #import "ios/web/webui/web_ui_ios_impl.h"
26 #include "net/http/http_response_headers.h"
28 namespace web {
30 WebStateImpl::WebStateImpl(BrowserState* browser_state)
31     : is_loading_(false),
32       facade_delegate_(nullptr),
33       web_controller_(nil),
34       navigation_manager_(this, browser_state),
35       interstitial_(nullptr),
36       cache_mode_(net::RequestTracker::CACHE_NORMAL) {
39 WebStateImpl::~WebStateImpl() {
40   // WebUI depends on web state so it must be destroyed first in case any WebUI
41   // implementations depends on accessing web state during destruction.
42   ClearWebUI();
44   // The facade layer (owned by the delegate) should be destroyed before the web
45   // layer.
46   DCHECK(!facade_delegate_);
48   FOR_EACH_OBSERVER(WebStateObserver, observers_, WebStateDestroyed());
49   FOR_EACH_OBSERVER(WebStateObserver, observers_, ResetWebState());
50   FOR_EACH_OBSERVER(WebStatePolicyDecider, policy_deciders_,
51                     WebStateDestroyed());
52   FOR_EACH_OBSERVER(WebStatePolicyDecider, policy_deciders_, ResetWebState());
53   DCHECK(script_command_callbacks_.empty());
54   if (request_tracker_.get())
55     CloseRequestTracker();
58 void WebStateImpl::AddObserver(WebStateObserver* observer) {
59   DCHECK(!observers_.HasObserver(observer));
60   observers_.AddObserver(observer);
63 void WebStateImpl::RemoveObserver(WebStateObserver* observer) {
64   DCHECK(observers_.HasObserver(observer));
65   observers_.RemoveObserver(observer);
68 void WebStateImpl::AddPolicyDecider(WebStatePolicyDecider* decider) {
69   // Despite the name, ObserverList is actually generic, so it is used for
70   // deciders. This makes the call here odd looking, but it's really just
71   // managing the list, not setting observers on deciders.
72   DCHECK(!policy_deciders_.HasObserver(decider));
73   policy_deciders_.AddObserver(decider);
76 void WebStateImpl::RemovePolicyDecider(WebStatePolicyDecider* decider) {
77   // Despite the name, ObserverList is actually generic, so it is used for
78   // deciders. This makes the call here odd looking, but it's really just
79   // managing the list, not setting observers on deciders.
80   DCHECK(policy_deciders_.HasObserver(decider));
81   policy_deciders_.RemoveObserver(decider);
84 bool WebStateImpl::Configured() const {
85   return web_controller_ != nil;
88 void WebStateImpl::SetWebController(CRWWebController* web_controller) {
89   DCHECK(!web_controller_);
90   web_controller_ = web_controller;
93 WebStateFacadeDelegate* WebStateImpl::GetFacadeDelegate() const {
94   return facade_delegate_;
97 void WebStateImpl::SetFacadeDelegate(WebStateFacadeDelegate* facade_delegate) {
98   facade_delegate_ = facade_delegate;
101 WebStateImpl* WebStateImpl::CopyForSessionWindow() {
102   WebStateImpl* copy = new WebStateImpl(GetBrowserState());
103   copy->GetNavigationManagerImpl().CopyState(&navigation_manager_);
104   return copy;
107 void WebStateImpl::OnUrlHashChanged() {
108   FOR_EACH_OBSERVER(WebStateObserver, observers_, UrlHashChanged());
111 void WebStateImpl::OnHistoryStateChanged() {
112   FOR_EACH_OBSERVER(WebStateObserver, observers_, HistoryStateChanged());
115 bool WebStateImpl::OnScriptCommandReceived(const std::string& command,
116                                            const base::DictionaryValue& value,
117                                            const GURL& url,
118                                            bool user_is_interacting) {
119   size_t dot_position = command.find_first_of('.');
120   if (dot_position == 0 || dot_position == std::string::npos)
121     return false;
123   std::string prefix = command.substr(0, dot_position);
124   auto it = script_command_callbacks_.find(prefix);
125   if (it == script_command_callbacks_.end())
126     return false;
128   return it->second.Run(value, url, user_is_interacting);
131 void WebStateImpl::SetIsLoading(bool is_loading) {
132   DCHECK(Configured());
133   if (is_loading == is_loading_)
134     return;
136   is_loading_ = is_loading;
137   if (facade_delegate_)
138     facade_delegate_->OnLoadingStateChanged();
140   if (is_loading) {
141     FOR_EACH_OBSERVER(WebStateObserver, observers_, DidStartLoading());
142   } else {
143     FOR_EACH_OBSERVER(WebStateObserver, observers_, DidStopLoading());
144   }
147 bool WebStateImpl::IsLoading() const {
148   return is_loading_;
151 void WebStateImpl::OnPageLoaded(const GURL& url, bool load_success) {
152   UpdateHttpResponseHeaders(url);
153   if (facade_delegate_)
154     facade_delegate_->OnPageLoaded();
156   PageLoadCompletionStatus load_completion_status =
157       load_success ? PageLoadCompletionStatus::SUCCESS
158                    : PageLoadCompletionStatus::FAILURE;
159   FOR_EACH_OBSERVER(WebStateObserver, observers_,
160                     PageLoaded(load_completion_status));
163 void WebStateImpl::OnFormActivityRegistered(const std::string& form_name,
164                                             const std::string& field_name,
165                                             const std::string& type,
166                                             const std::string& value,
167                                             int key_code,
168                                             bool input_missing) {
169   FOR_EACH_OBSERVER(WebStateObserver, observers_,
170                     FormActivityRegistered(form_name, field_name, type, value,
171                                            key_code, input_missing));
174 void WebStateImpl::OnFaviconUrlUpdated(
175     const std::vector<FaviconURL>& candidates) {
176   FOR_EACH_OBSERVER(WebStateObserver, observers_,
177                     FaviconUrlUpdated(candidates));
180 void WebStateImpl::OnCredentialsRequested(
181     int request_id,
182     const GURL& source_url,
183     bool suppress_ui,
184     const std::vector<std::string>& federations,
185     bool user_interaction) {
186   FOR_EACH_OBSERVER(WebStateObserver, observers_,
187                     CredentialsRequested(request_id, source_url, suppress_ui,
188                                          federations, user_interaction));
191 void WebStateImpl::OnSignedIn(int request_id,
192                               const GURL& source_url,
193                               const web::Credential& credential) {
194   FOR_EACH_OBSERVER(WebStateObserver, observers_,
195                     SignedIn(request_id, source_url, credential));
198 void WebStateImpl::OnSignedIn(int request_id, const GURL& source_url) {
199   FOR_EACH_OBSERVER(WebStateObserver, observers_,
200                     SignedIn(request_id, source_url));
203 void WebStateImpl::OnSignedOut(int request_id, const GURL& source_url) {
204   FOR_EACH_OBSERVER(WebStateObserver, observers_,
205                     SignedOut(request_id, source_url));
208 void WebStateImpl::OnSignInFailed(int request_id,
209                                   const GURL& source_url,
210                                   const web::Credential& credential) {
211   FOR_EACH_OBSERVER(WebStateObserver, observers_,
212                     SignInFailed(request_id, source_url, credential));
215 void WebStateImpl::OnSignInFailed(int request_id, const GURL& source_url) {
216   FOR_EACH_OBSERVER(WebStateObserver, observers_,
217                     SignInFailed(request_id, source_url));
220 void WebStateImpl::OnDocumentSubmitted(const std::string& form_name,
221                                        bool user_initiated) {
222   FOR_EACH_OBSERVER(WebStateObserver, observers_,
223                     DocumentSubmitted(form_name, user_initiated));
226 NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() {
227   return navigation_manager_;
230 const NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() const {
231   return navigation_manager_;
234 // There are currently two kinds of WebUI: those that have been adapted to
235 // web::WebUIIOS, and those that are still using content::WebUI. Try to create
236 // it as the first, and then fall back to the latter if necessary.
237 void WebStateImpl::CreateWebUI(const GURL& url) {
238   web_ui_.reset(CreateWebUIIOS(url));
239   if (!web_ui_ && facade_delegate_)
240     facade_delegate_->CreateLegacyWebUI(url);
243 void WebStateImpl::ClearWebUI() {
244   if (facade_delegate_)
245     facade_delegate_->ClearLegacyWebUI();
246   web_ui_.reset();
249 bool WebStateImpl::HasWebUI() {
250   return web_ui_ || (facade_delegate_ && facade_delegate_->HasLegacyWebUI());
253 void WebStateImpl::ProcessWebUIMessage(const GURL& source_url,
254                                        const std::string& message,
255                                        const base::ListValue& args) {
256   if (web_ui_) {
257     DCHECK(!facade_delegate_ || !facade_delegate_->HasLegacyWebUI());
258     web_ui_->ProcessWebUIIOSMessage(source_url, message, args);
259   } else if (facade_delegate_) {
260     facade_delegate_->ProcessLegacyWebUIMessage(source_url, message, args);
261   }
264 void WebStateImpl::LoadWebUIHtml(const base::string16& html, const GURL& url) {
265   CHECK(web::GetWebClient()->IsAppSpecificURL(url));
266   [web_controller_ loadHTML:base::SysUTF16ToNSString(html)
267           forAppSpecificURL:url];
270 const base::string16& WebStateImpl::GetTitle() const {
271   // TODO(stuartmorgan): Implement the NavigationManager logic necessary to
272   // match the WebContents implementation of this method.
273   DCHECK(Configured());
274   web::NavigationItem* item = navigation_manager_.GetLastCommittedItem();
275   if (item) {
276     return item->GetTitleForDisplay(
277         web::GetWebClient()->GetAcceptLangs(GetBrowserState()));
278   }
279   return empty_string16_;
282 void WebStateImpl::ShowTransientContentView(CRWContentView* content_view) {
283   DCHECK(Configured());
284   DCHECK(content_view);
285   DCHECK(content_view.scrollView);
286   DCHECK([content_view.scrollView isDescendantOfView:content_view]);
287   [web_controller_ showTransientContentView:content_view];
290 bool WebStateImpl::IsShowingWebInterstitial() const {
291   // Technically we could have |interstitial_| set but its view isn't
292   // being displayed, but there's no code path where that could occur.
293   return interstitial_ != nullptr;
296 WebInterstitial* WebStateImpl::GetWebInterstitial() const {
297   return interstitial_;
300 net::HttpResponseHeaders* WebStateImpl::GetHttpResponseHeaders() const {
301   return http_response_headers_.get();
304 void WebStateImpl::OnHttpResponseHeadersReceived(
305     net::HttpResponseHeaders* response_headers,
306     const GURL& resource_url) {
307   // Store the headers in a map until the page finishes loading, as we do not
308   // know which URL corresponds to the main page yet.
309   // Remove the hash (if any) as it is sometimes altered by in-page navigations.
310   const GURL& url = GURLByRemovingRefFromGURL(resource_url);
311   response_headers_map_[url] = response_headers;
314 void WebStateImpl::UpdateHttpResponseHeaders(const GURL& url) {
315   // Reset the state.
316   http_response_headers_ = NULL;
317   mime_type_.clear();
318   content_language_header_.clear();
320   // Discard all the response headers except the ones for |main_page_url|.
321   auto it = response_headers_map_.find(GURLByRemovingRefFromGURL(url));
322   if (it != response_headers_map_.end())
323     http_response_headers_ = it->second;
324   response_headers_map_.clear();
326   if (!http_response_headers_.get())
327     return;
329   // MIME type.
330   std::string mime_type;
331   http_response_headers_->GetMimeType(&mime_type);
332   mime_type_ = mime_type;
334   // Content-Language
335   std::string content_language;
336   http_response_headers_->GetNormalizedHeader("content-language",
337                                               &content_language);
338   // Remove everything after the comma ',' if any.
339   size_t comma_index = content_language.find_first_of(',');
340   if (comma_index != std::string::npos)
341     content_language.resize(comma_index);
342   content_language_header_ = content_language;
345 void WebStateImpl::ShowWebInterstitial(WebInterstitialImpl* interstitial) {
346   DCHECK(Configured());
347   DCHECK(interstitial);
348   interstitial_ = interstitial;
349   ShowTransientContentView(interstitial_->GetContentView());
352 void WebStateImpl::ClearTransientContentView() {
353   if (interstitial_) {
354     CRWSessionController* sessionController =
355         navigation_manager_.GetSessionController();
356     web::NavigationItem* currentItem =
357         [sessionController.currentEntry navigationItem];
358     if (currentItem->IsUnsafe()) {
359       // The unsafe page or page with bad SSL cert should be removed from the
360       // history, and, in fact, Safe Browsing and SSL interstitials will do
361       // just that *provided* that it isn't the current page.
362       // So to make this happen, before removing the interstitial, have the
363       // session controller go back one page.
364       [sessionController goBack];
365     }
366     [sessionController discardNonCommittedEntries];
367     // Store the currently displayed interstitial in a local variable and reset
368     // |interstitial_| early.  This is to prevent an infinite loop, as
369     // |DontProceed()| internally calls |ClearTransientContentView()|.
370     web::WebInterstitial* interstitial = interstitial_;
371     interstitial_ = nullptr;
372     interstitial->DontProceed();
373     // Don't access |interstitial| after calling |DontProceed()|, as it triggers
374     // deletion.
375     FOR_EACH_OBSERVER(WebStateObserver, observers_, InsterstitialDismissed());
376   }
377   [web_controller_ clearTransientContentView];
380 WebUIIOS* WebStateImpl::CreateWebUIIOS(const GURL& url) {
381   WebUIIOSControllerFactory* factory =
382       WebUIIOSControllerFactoryRegistry::GetInstance();
383   if (!factory)
384     return NULL;
385   WebUIIOSImpl* web_ui = new WebUIIOSImpl(this);
386   WebUIIOSController* controller =
387       factory->CreateWebUIIOSControllerForURL(web_ui, url);
388   if (controller) {
389     web_ui->SetController(controller);
390     return web_ui;
391   }
393   delete web_ui;
394   return NULL;
397 void WebStateImpl::ExecuteJavaScriptAsync(const base::string16& javascript) {
398   DCHECK(Configured());
399   [web_controller_ evaluateJavaScript:base::SysUTF16ToNSString(javascript)
400                   stringResultHandler:nil];
403 bool WebStateImpl::ShouldAllowRequest(NSURLRequest* request) {
404   base::ObserverListBase<WebStatePolicyDecider>::Iterator it(&policy_deciders_);
405   WebStatePolicyDecider* policy_decider = nullptr;
406   while ((policy_decider = it.GetNext()) != nullptr) {
407     if (!policy_decider->ShouldAllowRequest(request))
408       return false;
409   }
410   return true;
413 bool WebStateImpl::ShouldAllowResponse(NSURLResponse* response) {
414   base::ObserverListBase<WebStatePolicyDecider>::Iterator it(&policy_deciders_);
415   WebStatePolicyDecider* policy_decider = nullptr;
416   while ((policy_decider = it.GetNext()) != nullptr) {
417     if (!policy_decider->ShouldAllowResponse(response))
418       return false;
419   }
420   return true;
423 #pragma mark - RequestTracker management
425 void WebStateImpl::InitializeRequestTracker(
426     id<CRWRequestTrackerDelegate> delegate) {
427   BrowserState* browser_state = navigation_manager_.GetBrowserState();
428   request_tracker_ = RequestTrackerImpl::CreateTrackerForRequestGroupID(
429       GetRequestGroupID(), browser_state, browser_state->GetRequestContext(),
430       delegate);
433 void WebStateImpl::CloseRequestTracker() {
434   request_tracker_->Close();
435   request_tracker_ = NULL;
438 RequestTrackerImpl* WebStateImpl::GetRequestTracker() {
439   DCHECK(request_tracker_.get());
440   return request_tracker_.get();
443 net::RequestTracker::CacheMode WebStateImpl::GetCacheMode() {
444   return cache_mode_;
447 void WebStateImpl::SetCacheMode(net::RequestTracker::CacheMode mode) {
448   cache_mode_ = mode;
451 NSString* WebStateImpl::GetRequestGroupID() {
452   if (request_group_id_.get() == nil)
453     request_group_id_.reset([GenerateNewRequestGroupID() copy]);
455   return request_group_id_;
458 int WebStateImpl::DownloadImage(
459     const GURL& url,
460     bool is_favicon,
461     uint32_t max_bitmap_size,
462     bool bypass_cache,
463     const ImageDownloadCallback& callback) {
464   // |is_favicon| specifies whether the download of the image occurs with
465   // cookies or not. Currently, only downloads without cookies are supported.
466   // |bypass_cache| is ignored since the downloads never go through a cache.
467   DCHECK(is_favicon);
468   return [[web_controller_ delegate] downloadImageAtUrl:url
469                                           maxBitmapSize:max_bitmap_size
470                                               callback:callback];
473 #pragma mark - WebState implementation
475 UIView* WebStateImpl::GetView() {
476   return [web_controller_ view];
479 WebViewType WebStateImpl::GetWebViewType() const {
480   return [web_controller_ webViewType];
483 BrowserState* WebStateImpl::GetBrowserState() const {
484   return navigation_manager_.GetBrowserState();
487 void WebStateImpl::OpenURL(const WebState::OpenURLParams& params) {
488   DCHECK(Configured());
489   ClearTransientContentView();
490   [[web_controller_ delegate] openURLWithParams:params];
493 NavigationManager* WebStateImpl::GetNavigationManager() {
494   return &GetNavigationManagerImpl();
497 CRWJSInjectionReceiver* WebStateImpl::GetJSInjectionReceiver() const {
498   return [web_controller_ jsInjectionReceiver];
501 const std::string& WebStateImpl::GetContentLanguageHeader() const {
502   return content_language_header_;
505 const std::string& WebStateImpl::GetContentsMimeType() const {
506   return mime_type_;
509 bool WebStateImpl::ContentIsHTML() const {
510   return [web_controller_ contentIsHTML];
513 const GURL& WebStateImpl::GetVisibleURL() const {
514   web::NavigationItem* item = navigation_manager_.GetVisibleItem();
515   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
518 const GURL& WebStateImpl::GetLastCommittedURL() const {
519   web::NavigationItem* item = navigation_manager_.GetLastCommittedItem();
520   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
523 GURL WebStateImpl::GetCurrentURL(URLVerificationTrustLevel* trust_level) const {
524   return [web_controller_ currentURLWithTrustLevel:trust_level];
527 void WebStateImpl::AddScriptCommandCallback(
528     const ScriptCommandCallback& callback,
529     const std::string& command_prefix) {
530   DCHECK(!command_prefix.empty());
531   DCHECK(command_prefix.find_first_of('.') == std::string::npos);
532   DCHECK(script_command_callbacks_.find(command_prefix) ==
533          script_command_callbacks_.end());
534   script_command_callbacks_[command_prefix] = callback;
537 void WebStateImpl::RemoveScriptCommandCallback(
538     const std::string& command_prefix) {
539   DCHECK(script_command_callbacks_.find(command_prefix) !=
540          script_command_callbacks_.end());
541   script_command_callbacks_.erase(command_prefix);
544 id<CRWWebViewProxy> WebStateImpl::GetWebViewProxy() const {
545   return [web_controller_ webViewProxy];
548 void WebStateImpl::OnProvisionalNavigationStarted(const GURL& url) {
549   FOR_EACH_OBSERVER(WebStateObserver, observers_,
550                     ProvisionalNavigationStarted(url));
553 #pragma mark - NavigationManagerDelegate implementation
555 // Mirror WebContentsImpl::NavigateToPendingEntry() so that
556 // NavigationControllerIO::GoBack() actually goes back.
557 void WebStateImpl::NavigateToPendingEntry() {
558   [web_controller_ loadCurrentURL];
561 void WebStateImpl::OnNavigationItemCommitted(
562     const LoadCommittedDetails& load_details) {
563   FOR_EACH_OBSERVER(WebStateObserver, observers_,
564                     NavigationItemCommitted(load_details));
567 WebState* WebStateImpl::GetWebState() {
568   return this;
571 }  // namespace web