Add ICU message format support
[chromium-blink-merge.git] / ios / web / web_state / web_state_impl.mm
blob03063041296b5592adcc96b09710d71562af8abc
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 (WebStatePolicyDecider* policy_decider : policy_deciders_) {
51     policy_decider->WebStateDestroyed();
52     policy_decider->ResetWebState();
53   }
54   policy_deciders_.clear();
55   DCHECK(script_command_callbacks_.empty());
56   if (request_tracker_.get())
57     CloseRequestTracker();
60 void WebStateImpl::AddObserver(WebStateObserver* observer) {
61   DCHECK(!observers_.HasObserver(observer));
62   observers_.AddObserver(observer);
65 void WebStateImpl::RemoveObserver(WebStateObserver* observer) {
66   DCHECK(observers_.HasObserver(observer));
67   observers_.RemoveObserver(observer);
70 void WebStateImpl::AddPolicyDecider(WebStatePolicyDecider* decider) {
71   DCHECK_EQ(0u, policy_deciders_.count(decider));
72   policy_deciders_.insert(decider);
75 void WebStateImpl::RemovePolicyDecider(WebStatePolicyDecider* decider) {
76   DCHECK_LT(0u, policy_deciders_.count(decider));
77   policy_deciders_.erase(decider);
80 bool WebStateImpl::Configured() const {
81   return web_controller_ != nil;
84 void WebStateImpl::SetWebController(CRWWebController* web_controller) {
85   DCHECK(!web_controller_);
86   web_controller_ = web_controller;
89 WebStateFacadeDelegate* WebStateImpl::GetFacadeDelegate() const {
90   return facade_delegate_;
93 void WebStateImpl::SetFacadeDelegate(WebStateFacadeDelegate* facade_delegate) {
94   facade_delegate_ = facade_delegate;
97 WebStateImpl* WebStateImpl::CopyForSessionWindow() {
98   WebStateImpl* copy = new WebStateImpl(GetBrowserState());
99   copy->GetNavigationManagerImpl().CopyState(&navigation_manager_);
100   return copy;
103 void WebStateImpl::OnUrlHashChanged() {
104   FOR_EACH_OBSERVER(WebStateObserver, observers_, UrlHashChanged());
107 void WebStateImpl::OnHistoryStateChanged() {
108   FOR_EACH_OBSERVER(WebStateObserver, observers_, HistoryStateChanged());
111 bool WebStateImpl::OnScriptCommandReceived(const std::string& command,
112                                            const base::DictionaryValue& value,
113                                            const GURL& url,
114                                            bool user_is_interacting) {
115   size_t dot_position = command.find_first_of('.');
116   if (dot_position == 0 || dot_position == std::string::npos)
117     return false;
119   std::string prefix = command.substr(0, dot_position);
120   auto it = script_command_callbacks_.find(prefix);
121   if (it == script_command_callbacks_.end())
122     return false;
124   return it->second.Run(value, url, user_is_interacting);
127 void WebStateImpl::SetIsLoading(bool is_loading) {
128   DCHECK(Configured());
129   if (is_loading == is_loading_)
130     return;
132   is_loading_ = is_loading;
133   if (facade_delegate_)
134     facade_delegate_->OnLoadingStateChanged();
136   if (is_loading) {
137     FOR_EACH_OBSERVER(WebStateObserver, observers_, DidStartLoading());
138   } else {
139     FOR_EACH_OBSERVER(WebStateObserver, observers_, DidStopLoading());
140   }
143 bool WebStateImpl::IsLoading() const {
144   return is_loading_;
147 void WebStateImpl::OnPageLoaded(const GURL& url, bool load_success) {
148   UpdateHttpResponseHeaders(url);
149   if (facade_delegate_)
150     facade_delegate_->OnPageLoaded();
152   PageLoadCompletionStatus load_completion_status =
153       load_success ? PageLoadCompletionStatus::SUCCESS
154                    : PageLoadCompletionStatus::FAILURE;
155   FOR_EACH_OBSERVER(WebStateObserver, observers_,
156                     PageLoaded(load_completion_status));
159 void WebStateImpl::OnFormActivityRegistered(const std::string& form_name,
160                                             const std::string& field_name,
161                                             const std::string& type,
162                                             const std::string& value,
163                                             int key_code,
164                                             bool input_missing) {
165   FOR_EACH_OBSERVER(WebStateObserver, observers_,
166                     FormActivityRegistered(form_name, field_name, type, value,
167                                            key_code, input_missing));
170 void WebStateImpl::OnFaviconUrlUpdated(
171     const std::vector<FaviconURL>& candidates) {
172   FOR_EACH_OBSERVER(WebStateObserver, observers_,
173                     FaviconUrlUpdated(candidates));
176 void WebStateImpl::OnCredentialsRequested(
177     int request_id,
178     const GURL& source_url,
179     bool suppress_ui,
180     const std::vector<std::string>& federations,
181     bool user_interaction) {
182   FOR_EACH_OBSERVER(WebStateObserver, observers_,
183                     CredentialsRequested(request_id, source_url, suppress_ui,
184                                          federations, user_interaction));
187 void WebStateImpl::OnSignedIn(int request_id,
188                               const GURL& source_url,
189                               const web::Credential& credential) {
190   FOR_EACH_OBSERVER(WebStateObserver, observers_,
191                     SignedIn(request_id, source_url, credential));
194 void WebStateImpl::OnSignedIn(int request_id, const GURL& source_url) {
195   FOR_EACH_OBSERVER(WebStateObserver, observers_,
196                     SignedIn(request_id, source_url));
199 void WebStateImpl::OnSignedOut(int request_id, const GURL& source_url) {
200   FOR_EACH_OBSERVER(WebStateObserver, observers_,
201                     SignedOut(request_id, source_url));
204 void WebStateImpl::OnSignInFailed(int request_id,
205                                   const GURL& source_url,
206                                   const web::Credential& credential) {
207   FOR_EACH_OBSERVER(WebStateObserver, observers_,
208                     SignInFailed(request_id, source_url, credential));
211 void WebStateImpl::OnSignInFailed(int request_id, const GURL& source_url) {
212   FOR_EACH_OBSERVER(WebStateObserver, observers_,
213                     SignInFailed(request_id, source_url));
216 void WebStateImpl::OnDocumentSubmitted(const std::string& form_name,
217                                        bool user_initiated) {
218   FOR_EACH_OBSERVER(WebStateObserver, observers_,
219                     DocumentSubmitted(form_name, user_initiated));
222 NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() {
223   return navigation_manager_;
226 const NavigationManagerImpl& WebStateImpl::GetNavigationManagerImpl() const {
227   return navigation_manager_;
230 // There are currently two kinds of WebUI: those that have been adapted to
231 // web::WebUIIOS, and those that are still using content::WebUI. Try to create
232 // it as the first, and then fall back to the latter if necessary.
233 void WebStateImpl::CreateWebUI(const GURL& url) {
234   web_ui_.reset(CreateWebUIIOS(url));
235   if (!web_ui_ && facade_delegate_)
236     facade_delegate_->CreateLegacyWebUI(url);
239 void WebStateImpl::ClearWebUI() {
240   if (facade_delegate_)
241     facade_delegate_->ClearLegacyWebUI();
242   web_ui_.reset();
245 bool WebStateImpl::HasWebUI() {
246   return web_ui_ || (facade_delegate_ && facade_delegate_->HasLegacyWebUI());
249 void WebStateImpl::ProcessWebUIMessage(const GURL& source_url,
250                                        const std::string& message,
251                                        const base::ListValue& args) {
252   if (web_ui_) {
253     DCHECK(!facade_delegate_ || !facade_delegate_->HasLegacyWebUI());
254     web_ui_->ProcessWebUIIOSMessage(source_url, message, args);
255   } else if (facade_delegate_) {
256     facade_delegate_->ProcessLegacyWebUIMessage(source_url, message, args);
257   }
260 void WebStateImpl::LoadWebUIHtml(const base::string16& html, const GURL& url) {
261   CHECK(web::GetWebClient()->IsAppSpecificURL(url));
262   [web_controller_ loadHTML:base::SysUTF16ToNSString(html)
263           forAppSpecificURL:url];
266 const base::string16& WebStateImpl::GetTitle() const {
267   // TODO(stuartmorgan): Implement the NavigationManager logic necessary to
268   // match the WebContents implementation of this method.
269   DCHECK(Configured());
270   web::NavigationItem* item = navigation_manager_.GetLastCommittedItem();
271   if (item) {
272     return item->GetTitleForDisplay(
273         web::GetWebClient()->GetAcceptLangs(GetBrowserState()));
274   }
275   return empty_string16_;
278 void WebStateImpl::ShowTransientContentView(CRWContentView* content_view) {
279   DCHECK(Configured());
280   DCHECK(content_view);
281   DCHECK(content_view.scrollView);
282   DCHECK([content_view.scrollView isDescendantOfView:content_view]);
283   [web_controller_ showTransientContentView:content_view];
286 bool WebStateImpl::IsShowingWebInterstitial() const {
287   // Technically we could have |interstitial_| set but its view isn't
288   // being displayed, but there's no code path where that could occur.
289   return interstitial_ != nullptr;
292 WebInterstitial* WebStateImpl::GetWebInterstitial() const {
293   return interstitial_;
296 net::HttpResponseHeaders* WebStateImpl::GetHttpResponseHeaders() const {
297   return http_response_headers_.get();
300 void WebStateImpl::OnHttpResponseHeadersReceived(
301     net::HttpResponseHeaders* response_headers,
302     const GURL& resource_url) {
303   // Store the headers in a map until the page finishes loading, as we do not
304   // know which URL corresponds to the main page yet.
305   // Remove the hash (if any) as it is sometimes altered by in-page navigations.
306   const GURL& url = GURLByRemovingRefFromGURL(resource_url);
307   response_headers_map_[url] = response_headers;
310 void WebStateImpl::UpdateHttpResponseHeaders(const GURL& url) {
311   // Reset the state.
312   http_response_headers_ = NULL;
313   mime_type_.clear();
314   content_language_header_.clear();
316   // Discard all the response headers except the ones for |main_page_url|.
317   auto it = response_headers_map_.find(GURLByRemovingRefFromGURL(url));
318   if (it != response_headers_map_.end())
319     http_response_headers_ = it->second;
320   response_headers_map_.clear();
322   if (!http_response_headers_.get())
323     return;
325   // MIME type.
326   std::string mime_type;
327   http_response_headers_->GetMimeType(&mime_type);
328   mime_type_ = mime_type;
330   // Content-Language
331   std::string content_language;
332   http_response_headers_->GetNormalizedHeader("content-language",
333                                               &content_language);
334   // Remove everything after the comma ',' if any.
335   size_t comma_index = content_language.find_first_of(',');
336   if (comma_index != std::string::npos)
337     content_language.resize(comma_index);
338   content_language_header_ = content_language;
341 void WebStateImpl::ShowWebInterstitial(WebInterstitialImpl* interstitial) {
342   DCHECK(Configured());
343   DCHECK(interstitial);
344   interstitial_ = interstitial;
345   ShowTransientContentView(interstitial_->GetContentView());
348 void WebStateImpl::ClearTransientContentView() {
349   if (interstitial_) {
350     CRWSessionController* sessionController =
351         navigation_manager_.GetSessionController();
352     web::NavigationItem* currentItem =
353         [sessionController.currentEntry navigationItem];
354     if (currentItem->IsUnsafe()) {
355       // The unsafe page should be removed from history, and, in fact,
356       // SafeBrowsingBlockingPage will do just that *provided* that it
357       // isn't the current page. So to make this happen, before removing the
358       // interstitial, have the session controller go back one page.
359       [sessionController goBack];
360     }
361     [sessionController discardNonCommittedEntries];
362     // Store the currently displayed interstitial in a local variable and reset
363     // |interstitial_| early.  This is to prevent an infinite loop, as
364     // |DontProceed()| internally calls |ClearTransientContentView()|.
365     web::WebInterstitial* interstitial = interstitial_;
366     interstitial_ = nullptr;
367     interstitial->DontProceed();
368     // Don't access |interstitial| after calling |DontProceed()|, as it triggers
369     // deletion.
370     FOR_EACH_OBSERVER(WebStateObserver, observers_, InsterstitialDismissed());
371   }
372   [web_controller_ clearTransientContentView];
375 WebUIIOS* WebStateImpl::CreateWebUIIOS(const GURL& url) {
376   WebUIIOSControllerFactory* factory =
377       WebUIIOSControllerFactoryRegistry::GetInstance();
378   if (!factory)
379     return NULL;
380   WebUIIOSImpl* web_ui = new WebUIIOSImpl(this);
381   WebUIIOSController* controller =
382       factory->CreateWebUIIOSControllerForURL(web_ui, url);
383   if (controller) {
384     web_ui->SetController(controller);
385     return web_ui;
386   }
388   delete web_ui;
389   return NULL;
392 void WebStateImpl::ExecuteJavaScriptAsync(const base::string16& javascript) {
393   DCHECK(Configured());
394   [web_controller_ evaluateJavaScript:base::SysUTF16ToNSString(javascript)
395                   stringResultHandler:nil];
398 bool WebStateImpl::ShouldAllowRequest(NSURLRequest* request) {
399   for (WebStatePolicyDecider* policy_decider : policy_deciders_) {
400     if (!policy_decider->ShouldAllowRequest(request))
401       return false;
402   }
403   return true;
406 bool WebStateImpl::ShouldAllowResponse(NSURLResponse* response) {
407   for (WebStatePolicyDecider* policy_decider : policy_deciders_) {
408     if (!policy_decider->ShouldAllowResponse(response))
409       return false;
410   }
411   return true;
414 #pragma mark - RequestTracker management
416 void WebStateImpl::InitializeRequestTracker(
417     id<CRWRequestTrackerDelegate> delegate) {
418   BrowserState* browser_state = navigation_manager_.GetBrowserState();
419   request_tracker_ = RequestTrackerImpl::CreateTrackerForRequestGroupID(
420       GetRequestGroupID(), browser_state, browser_state->GetRequestContext(),
421       delegate);
424 void WebStateImpl::CloseRequestTracker() {
425   request_tracker_->Close();
426   request_tracker_ = NULL;
429 RequestTrackerImpl* WebStateImpl::GetRequestTracker() {
430   DCHECK(request_tracker_.get());
431   return request_tracker_.get();
434 net::RequestTracker::CacheMode WebStateImpl::GetCacheMode() {
435   return cache_mode_;
438 void WebStateImpl::SetCacheMode(net::RequestTracker::CacheMode mode) {
439   cache_mode_ = mode;
442 NSString* WebStateImpl::GetRequestGroupID() {
443   if (request_group_id_.get() == nil)
444     request_group_id_.reset([GenerateNewRequestGroupID() copy]);
446   return request_group_id_;
449 int WebStateImpl::DownloadImage(
450     const GURL& url,
451     bool is_favicon,
452     uint32_t max_bitmap_size,
453     bool bypass_cache,
454     const ImageDownloadCallback& callback) {
455   // |is_favicon| specifies whether the download of the image occurs with
456   // cookies or not. Currently, only downloads without cookies are supported.
457   // |bypass_cache| is ignored since the downloads never go through a cache.
458   DCHECK(is_favicon);
459   return [[web_controller_ delegate] downloadImageAtUrl:url
460                                           maxBitmapSize:max_bitmap_size
461                                               callback:callback];
464 #pragma mark - WebState implementation
466 UIView* WebStateImpl::GetView() {
467   return [web_controller_ view];
470 WebViewType WebStateImpl::GetWebViewType() const {
471   return [web_controller_ webViewType];
474 BrowserState* WebStateImpl::GetBrowserState() const {
475   return navigation_manager_.GetBrowserState();
478 void WebStateImpl::OpenURL(const WebState::OpenURLParams& params) {
479   DCHECK(Configured());
480   ClearTransientContentView();
481   [[web_controller_ delegate] openURLWithParams:params];
484 NavigationManager* WebStateImpl::GetNavigationManager() {
485   return &GetNavigationManagerImpl();
488 CRWJSInjectionReceiver* WebStateImpl::GetJSInjectionReceiver() const {
489   return [web_controller_ jsInjectionReceiver];
492 const std::string& WebStateImpl::GetContentLanguageHeader() const {
493   return content_language_header_;
496 const std::string& WebStateImpl::GetContentsMimeType() const {
497   return mime_type_;
500 bool WebStateImpl::ContentIsHTML() const {
501   return [web_controller_ contentIsHTML];
504 const GURL& WebStateImpl::GetVisibleURL() const {
505   web::NavigationItem* item = navigation_manager_.GetVisibleItem();
506   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
509 const GURL& WebStateImpl::GetLastCommittedURL() const {
510   web::NavigationItem* item = navigation_manager_.GetLastCommittedItem();
511   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
514 GURL WebStateImpl::GetCurrentURL(URLVerificationTrustLevel* trust_level) const {
515   return [web_controller_ currentURLWithTrustLevel:trust_level];
518 void WebStateImpl::AddScriptCommandCallback(
519     const ScriptCommandCallback& callback,
520     const std::string& command_prefix) {
521   DCHECK(!command_prefix.empty());
522   DCHECK(command_prefix.find_first_of('.') == std::string::npos);
523   DCHECK(script_command_callbacks_.find(command_prefix) ==
524          script_command_callbacks_.end());
525   script_command_callbacks_[command_prefix] = callback;
528 void WebStateImpl::RemoveScriptCommandCallback(
529     const std::string& command_prefix) {
530   DCHECK(script_command_callbacks_.find(command_prefix) !=
531          script_command_callbacks_.end());
532   script_command_callbacks_.erase(command_prefix);
535 id<CRWWebViewProxy> WebStateImpl::GetWebViewProxy() const {
536   return [web_controller_ webViewProxy];
539 void WebStateImpl::OnProvisionalNavigationStarted(const GURL& url) {
540   FOR_EACH_OBSERVER(WebStateObserver, observers_,
541                     ProvisionalNavigationStarted(url));
544 #pragma mark - NavigationManagerDelegate implementation
546 // Mirror WebContentsImpl::NavigateToPendingEntry() so that
547 // NavigationControllerIO::GoBack() actually goes back.
548 void WebStateImpl::NavigateToPendingEntry() {
549   [web_controller_ loadCurrentURL];
552 void WebStateImpl::OnNavigationItemCommitted(
553     const LoadCommittedDetails& load_details) {
554   FOR_EACH_OBSERVER(WebStateObserver, observers_,
555                     NavigationItemCommitted(load_details));
558 WebState* WebStateImpl::GetWebState() {
559   return this;
562 }  // namespace web