OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #import "ios/web/navigation/navigation_manager_impl.h" | |
6 | |
7 #include <stddef.h> | |
8 | |
9 #include <utility> | |
10 | |
11 #include "base/logging.h" | |
12 #import "ios/web/navigation/crw_session_controller+private_constructors.h" | |
13 #import "ios/web/navigation/crw_session_controller.h" | |
14 #import "ios/web/navigation/navigation_item_impl.h" | |
15 #import "ios/web/navigation/navigation_item_impl_list.h" | |
16 #import "ios/web/navigation/navigation_manager_delegate.h" | |
17 #include "ios/web/public/load_committed_details.h" | |
18 #import "ios/web/public/navigation_item.h" | |
19 #include "ios/web/public/reload_type.h" | |
20 #import "ios/web/public/web_client.h" | |
21 #import "ios/web/public/web_state/web_state.h" | |
22 #include "ui/base/page_transition_types.h" | |
23 | |
24 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
25 #error "This file requires ARC support." | |
26 #endif | |
27 | |
28 namespace { | |
29 | |
30 // Checks whether or not two URL are an in-page navigation (differing only | |
31 // in the fragment). | |
32 bool AreURLsInPageNavigation(const GURL& existing_url, const GURL& new_url) { | |
33 if (existing_url == new_url || !new_url.has_ref()) | |
34 return false; | |
35 | |
36 return existing_url.EqualsIgnoringRef(new_url); | |
37 } | |
38 | |
39 } // anonymous namespace | |
40 | |
41 namespace web { | |
42 | |
43 NavigationManager::WebLoadParams::WebLoadParams(const GURL& url) | |
44 : url(url), | |
45 transition_type(ui::PAGE_TRANSITION_LINK), | |
46 user_agent_override_option(UserAgentOverrideOption::INHERIT), | |
47 is_renderer_initiated(false), | |
48 post_data(nil) {} | |
49 | |
50 NavigationManager::WebLoadParams::~WebLoadParams() {} | |
51 | |
52 NavigationManager::WebLoadParams::WebLoadParams(const WebLoadParams& other) | |
53 : url(other.url), | |
54 referrer(other.referrer), | |
55 transition_type(other.transition_type), | |
56 user_agent_override_option(other.user_agent_override_option), | |
57 is_renderer_initiated(other.is_renderer_initiated), | |
58 extra_headers([other.extra_headers copy]), | |
59 post_data([other.post_data copy]) {} | |
60 | |
61 NavigationManager::WebLoadParams& NavigationManager::WebLoadParams::operator=( | |
62 const WebLoadParams& other) { | |
63 url = other.url; | |
64 referrer = other.referrer; | |
65 is_renderer_initiated = other.is_renderer_initiated; | |
66 transition_type = other.transition_type; | |
67 user_agent_override_option = other.user_agent_override_option; | |
68 extra_headers.reset([other.extra_headers copy]); | |
69 post_data.reset([other.post_data copy]); | |
70 | |
71 return *this; | |
72 } | |
73 | |
74 NavigationManagerImpl::NavigationManagerImpl() | |
75 : delegate_(nullptr), browser_state_(nullptr) {} | |
76 | |
77 NavigationManagerImpl::~NavigationManagerImpl() { | |
78 [session_controller_ setNavigationManager:nullptr]; | |
79 } | |
80 | |
81 void NavigationManagerImpl::SetDelegate(NavigationManagerDelegate* delegate) { | |
82 delegate_ = delegate; | |
83 } | |
84 | |
85 void NavigationManagerImpl::SetBrowserState(BrowserState* browser_state) { | |
86 browser_state_ = browser_state; | |
87 [session_controller_ setBrowserState:browser_state]; | |
88 } | |
89 | |
90 void NavigationManagerImpl::SetSessionController( | |
91 CRWSessionController* session_controller) { | |
92 session_controller_.reset(session_controller); | |
93 [session_controller_ setNavigationManager:this]; | |
94 } | |
95 | |
96 void NavigationManagerImpl::InitializeSession() { | |
97 SetSessionController( | |
98 [[CRWSessionController alloc] initWithBrowserState:browser_state_]); | |
99 } | |
100 | |
101 void NavigationManagerImpl::ReplaceSessionHistory( | |
102 std::vector<std::unique_ptr<web::NavigationItem>> items, | |
103 int lastCommittedItemIndex) { | |
104 SetSessionController([[CRWSessionController alloc] | |
105 initWithBrowserState:browser_state_ | |
106 navigationItems:std::move(items) | |
107 lastCommittedItemIndex:lastCommittedItemIndex]); | |
108 } | |
109 | |
110 void NavigationManagerImpl::OnNavigationItemsPruned(size_t pruned_item_count) { | |
111 delegate_->OnNavigationItemsPruned(pruned_item_count); | |
112 } | |
113 | |
114 void NavigationManagerImpl::OnNavigationItemChanged() { | |
115 delegate_->OnNavigationItemChanged(); | |
116 } | |
117 | |
118 void NavigationManagerImpl::OnNavigationItemCommitted() { | |
119 LoadCommittedDetails details; | |
120 details.item = GetLastCommittedItem(); | |
121 DCHECK(details.item); | |
122 details.previous_item_index = [session_controller_ previousItemIndex]; | |
123 if (details.previous_item_index >= 0) { | |
124 DCHECK([session_controller_ previousItem]); | |
125 details.previous_url = [session_controller_ previousItem]->GetURL(); | |
126 details.is_in_page = | |
127 AreURLsInPageNavigation(details.previous_url, details.item->GetURL()); | |
128 } else { | |
129 details.previous_url = GURL(); | |
130 details.is_in_page = NO; | |
131 } | |
132 | |
133 delegate_->OnNavigationItemCommitted(details); | |
134 } | |
135 | |
136 CRWSessionController* NavigationManagerImpl::GetSessionController() { | |
137 return session_controller_; | |
138 } | |
139 | |
140 void NavigationManagerImpl::AddTransientItem(const GURL& url) { | |
141 [session_controller_ addTransientItemWithURL:url]; | |
142 | |
143 // TODO(crbug.com/676129): Transient item is only supposed to be added for | |
144 // pending non-app-specific loads, but pending item can be null because of the | |
145 // bug. The workaround should be removed once the bug is fixed. | |
146 NavigationItem* item = GetPendingItem(); | |
147 if (!item) | |
148 item = GetLastCommittedNonAppSpecificItem(); | |
149 DCHECK(item->GetUserAgentType() != UserAgentType::NONE); | |
150 GetTransientItem()->SetUserAgentType(item->GetUserAgentType()); | |
151 } | |
152 | |
153 void NavigationManagerImpl::AddPendingItem( | |
154 const GURL& url, | |
155 const web::Referrer& referrer, | |
156 ui::PageTransition navigation_type, | |
157 NavigationInitiationType initiation_type, | |
158 UserAgentOverrideOption user_agent_override_option) { | |
159 [session_controller_ addPendingItem:url | |
160 referrer:referrer | |
161 transition:navigation_type | |
162 initiationType:initiation_type | |
163 userAgentOverrideOption:user_agent_override_option]; | |
164 | |
165 // Set the user agent type for web URLs. | |
166 NavigationItem* pending_item = GetPendingItem(); | |
167 if (!pending_item) | |
168 return; | |
169 | |
170 // |user_agent_override_option| must be INHERIT if |pending_item|'s | |
171 // UserAgentType is NONE, as requesting a desktop or mobile user agent should | |
172 // be disabled for app-specific URLs. | |
173 DCHECK(pending_item->GetUserAgentType() != UserAgentType::NONE || | |
174 user_agent_override_option == UserAgentOverrideOption::INHERIT); | |
175 | |
176 // Newly created pending items are created with UserAgentType::NONE for native | |
177 // pages or UserAgentType::MOBILE for non-native pages. If the pending item's | |
178 // URL is non-native, check which user agent type it should be created with | |
179 // based on |user_agent_override_option|. | |
180 DCHECK_NE(UserAgentType::DESKTOP, pending_item->GetUserAgentType()); | |
181 if (pending_item->GetUserAgentType() == UserAgentType::NONE) | |
182 return; | |
183 | |
184 switch (user_agent_override_option) { | |
185 case UserAgentOverrideOption::DESKTOP: | |
186 pending_item->SetUserAgentType(UserAgentType::DESKTOP); | |
187 break; | |
188 case UserAgentOverrideOption::MOBILE: | |
189 pending_item->SetUserAgentType(UserAgentType::MOBILE); | |
190 break; | |
191 case UserAgentOverrideOption::INHERIT: { | |
192 // Propagate the last committed non-native item's UserAgentType if there | |
193 // is one, otherwise keep the default value, which is mobile. | |
194 NavigationItem* last_non_native_item = | |
195 GetLastCommittedNonAppSpecificItem(); | |
196 DCHECK(!last_non_native_item || | |
197 last_non_native_item->GetUserAgentType() != UserAgentType::NONE); | |
198 if (last_non_native_item) { | |
199 pending_item->SetUserAgentType( | |
200 last_non_native_item->GetUserAgentType()); | |
201 } | |
202 break; | |
203 } | |
204 } | |
205 } | |
206 | |
207 void NavigationManagerImpl::CommitPendingItem() { | |
208 [session_controller_ commitPendingItem]; | |
209 } | |
210 | |
211 BrowserState* NavigationManagerImpl::GetBrowserState() const { | |
212 return browser_state_; | |
213 } | |
214 | |
215 WebState* NavigationManagerImpl::GetWebState() const { | |
216 return delegate_->GetWebState(); | |
217 } | |
218 | |
219 NavigationItem* NavigationManagerImpl::GetVisibleItem() const { | |
220 return [session_controller_ visibleItem]; | |
221 } | |
222 | |
223 NavigationItem* NavigationManagerImpl::GetLastCommittedItem() const { | |
224 return [session_controller_ lastCommittedItem]; | |
225 } | |
226 | |
227 NavigationItem* NavigationManagerImpl::GetPendingItem() const { | |
228 return [session_controller_ pendingItem]; | |
229 } | |
230 | |
231 NavigationItem* NavigationManagerImpl::GetTransientItem() const { | |
232 return [session_controller_ transientItem]; | |
233 } | |
234 | |
235 void NavigationManagerImpl::DiscardNonCommittedItems() { | |
236 [session_controller_ discardNonCommittedItems]; | |
237 } | |
238 | |
239 void NavigationManagerImpl::LoadURLWithParams( | |
240 const NavigationManager::WebLoadParams& params) { | |
241 delegate_->LoadURLWithParams(params); | |
242 } | |
243 | |
244 void NavigationManagerImpl::AddTransientURLRewriter( | |
245 BrowserURLRewriter::URLRewriter rewriter) { | |
246 DCHECK(rewriter); | |
247 if (!transient_url_rewriters_) { | |
248 transient_url_rewriters_.reset( | |
249 new std::vector<BrowserURLRewriter::URLRewriter>()); | |
250 } | |
251 transient_url_rewriters_->push_back(rewriter); | |
252 } | |
253 | |
254 int NavigationManagerImpl::GetItemCount() const { | |
255 return [session_controller_ items].size(); | |
256 } | |
257 | |
258 NavigationItem* NavigationManagerImpl::GetItemAtIndex(size_t index) const { | |
259 return [session_controller_ itemAtIndex:index]; | |
260 } | |
261 | |
262 int NavigationManagerImpl::GetIndexOfItem( | |
263 const web::NavigationItem* item) const { | |
264 return [session_controller_ indexOfItem:item]; | |
265 } | |
266 | |
267 int NavigationManagerImpl::GetPendingItemIndex() const { | |
268 if (GetPendingItem()) { | |
269 if ([session_controller_ pendingItemIndex] != -1) { | |
270 return [session_controller_ pendingItemIndex]; | |
271 } | |
272 // TODO(crbug.com/665189): understand why last committed item index is | |
273 // returned here. | |
274 return GetLastCommittedItemIndex(); | |
275 } | |
276 return -1; | |
277 } | |
278 | |
279 int NavigationManagerImpl::GetLastCommittedItemIndex() const { | |
280 if (GetItemCount() == 0) | |
281 return -1; | |
282 return [session_controller_ lastCommittedItemIndex]; | |
283 } | |
284 | |
285 bool NavigationManagerImpl::RemoveItemAtIndex(int index) { | |
286 if (index == GetLastCommittedItemIndex() || index == GetPendingItemIndex()) | |
287 return false; | |
288 | |
289 if (index < 0 || index >= GetItemCount()) | |
290 return false; | |
291 | |
292 [session_controller_ removeItemAtIndex:index]; | |
293 return true; | |
294 } | |
295 | |
296 bool NavigationManagerImpl::CanGoBack() const { | |
297 return CanGoToOffset(-1); | |
298 } | |
299 | |
300 bool NavigationManagerImpl::CanGoForward() const { | |
301 return CanGoToOffset(1); | |
302 } | |
303 | |
304 bool NavigationManagerImpl::CanGoToOffset(int offset) const { | |
305 int index = GetIndexForOffset(offset); | |
306 return 0 <= index && index < GetItemCount(); | |
307 } | |
308 | |
309 void NavigationManagerImpl::GoBack() { | |
310 delegate_->GoToIndex(GetIndexForOffset(-1)); | |
311 } | |
312 | |
313 void NavigationManagerImpl::GoForward() { | |
314 delegate_->GoToIndex(GetIndexForOffset(1)); | |
315 } | |
316 | |
317 void NavigationManagerImpl::GoToIndex(int index) { | |
318 delegate_->GoToIndex(index); | |
319 } | |
320 | |
321 NavigationItemList NavigationManagerImpl::GetBackwardItems() const { | |
322 return [session_controller_ backwardItems]; | |
323 } | |
324 | |
325 NavigationItemList NavigationManagerImpl::GetForwardItems() const { | |
326 return [session_controller_ forwardItems]; | |
327 } | |
328 | |
329 void NavigationManagerImpl::Reload(ReloadType reload_type, | |
330 bool check_for_reposts) { | |
331 if (!GetTransientItem() && !GetPendingItem() && !GetLastCommittedItem()) | |
332 return; | |
333 | |
334 // Reload with ORIGINAL_REQUEST_URL type should reload with the original | |
335 // request url of the transient item, or pending item if transient doesn't | |
336 // exist, or last committed item if both of them don't exist. The reason is | |
337 // that a server side redirect may change the item's url. | |
338 // For example, the user visits www.chromium.org and is then redirected | |
339 // to m.chromium.org, when the user wants to refresh the page with a different | |
340 // configuration (e.g. user agent), the user would be expecting to visit | |
341 // www.chromium.org instead of m.chromium.org. | |
342 if (reload_type == web::ReloadType::ORIGINAL_REQUEST_URL) { | |
343 NavigationItem* reload_item = nullptr; | |
344 if (GetTransientItem()) | |
345 reload_item = GetTransientItem(); | |
346 else if (GetPendingItem()) | |
347 reload_item = GetPendingItem(); | |
348 else | |
349 reload_item = GetLastCommittedItem(); | |
350 DCHECK(reload_item); | |
351 | |
352 reload_item->SetURL(reload_item->GetOriginalRequestURL()); | |
353 } | |
354 | |
355 delegate_->Reload(); | |
356 } | |
357 | |
358 void NavigationManagerImpl::CopyStateFromAndPrune( | |
359 const NavigationManager* manager) { | |
360 DCHECK(manager); | |
361 CRWSessionController* other_session = | |
362 static_cast<const NavigationManagerImpl*>(manager)->session_controller_; | |
363 [session_controller_ copyStateFromSessionControllerAndPrune:other_session]; | |
364 } | |
365 | |
366 bool NavigationManagerImpl::CanPruneAllButLastCommittedItem() const { | |
367 return [session_controller_ canPruneAllButLastCommittedItem]; | |
368 } | |
369 | |
370 std::unique_ptr<std::vector<BrowserURLRewriter::URLRewriter>> | |
371 NavigationManagerImpl::GetTransientURLRewriters() { | |
372 return std::move(transient_url_rewriters_); | |
373 } | |
374 | |
375 void NavigationManagerImpl::RemoveTransientURLRewriters() { | |
376 transient_url_rewriters_.reset(); | |
377 } | |
378 | |
379 int NavigationManagerImpl::GetIndexForOffset(int offset) const { | |
380 int result = [session_controller_ pendingItemIndex] == -1 | |
381 ? GetLastCommittedItemIndex() | |
382 : static_cast<int>([session_controller_ pendingItemIndex]); | |
383 | |
384 if (offset < 0) { | |
385 if (GetTransientItem() && [session_controller_ pendingItemIndex] == -1) { | |
386 // Going back from transient item that added to the end navigation stack | |
387 // is a matter of discarding it as there is no need to move navigation | |
388 // index back. | |
389 offset++; | |
390 } | |
391 | |
392 while (offset < 0 && result > 0) { | |
393 // To stop the user getting 'stuck' on redirecting pages they weren't | |
394 // even aware existed, it is necessary to pass over pages that would | |
395 // immediately result in a redirect (the item *before* the redirected | |
396 // page). | |
397 while (result > 0 && IsRedirectItemAtIndex(result)) { | |
398 --result; | |
399 } | |
400 --result; | |
401 ++offset; | |
402 } | |
403 // Result may be out of bounds, so stop trying to skip redirect items and | |
404 // simply add the remainder. | |
405 result += offset; | |
406 if (result > GetItemCount() /* overflow */) | |
407 result = INT_MIN; | |
408 } else if (offset > 0) { | |
409 while (offset > 0 && result < GetItemCount()) { | |
410 ++result; | |
411 --offset; | |
412 // As with going back, skip over redirects. | |
413 while (result + 1 < GetItemCount() && IsRedirectItemAtIndex(result + 1)) { | |
414 ++result; | |
415 } | |
416 } | |
417 // Result may be out of bounds, so stop trying to skip redirect items and | |
418 // simply add the remainder. | |
419 result += offset; | |
420 if (result < 0 /* overflow */) | |
421 result = INT_MAX; | |
422 } | |
423 | |
424 return result; | |
425 } | |
426 | |
427 bool NavigationManagerImpl::IsRedirectItemAtIndex(int index) const { | |
428 DCHECK_GE(index, 0); | |
429 DCHECK_LT(index, GetItemCount()); | |
430 ui::PageTransition transition = GetItemAtIndex(index)->GetTransitionType(); | |
431 return transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK; | |
432 } | |
433 | |
434 NavigationItem* NavigationManagerImpl::GetLastCommittedNonAppSpecificItem() | |
435 const { | |
436 int index = GetLastCommittedItemIndex(); | |
437 if (index == -1) | |
438 return nullptr; | |
439 WebClient* client = GetWebClient(); | |
440 const ScopedNavigationItemImplList& items = [session_controller_ items]; | |
441 while (index >= 0) { | |
442 NavigationItem* item = items[index--].get(); | |
443 if (!client->IsAppSpecificURL(item->GetVirtualURL())) | |
444 return item; | |
445 } | |
446 return nullptr; | |
447 } | |
448 | |
449 } // namespace web | |
OLD | NEW |