1 // Copyright 2012 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 #import "ios/web/navigation/crw_session_entry.h"
7 #include "base/mac/objc_property_releaser.h"
8 #include "base/mac/scoped_nsobject.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "ios/web/navigation/navigation_item_impl.h"
12 #include "ios/web/navigation/nscoder_util.h"
13 #include "ios/web/public/navigation_item.h"
14 #include "ios/web/public/web_state/page_scroll_state.h"
15 #import "net/base/mac/url_conversions.h"
18 // Keys used to serialize web::PageScrollState properties.
19 NSString* const kScrollOffsetXKey = @"scrollX";
20 NSString* const kScrollOffsetYKey = @"scrollY";
21 NSString* const kMinimumZoomScaleKey = @"minZoom";
22 NSString* const kMaximumZoomScaleKey = @"maxZoom";
23 NSString* const kZoomScaleKey = @"zoom";
26 @interface CRWSessionEntry () {
27 // The index in the CRWSessionController.
29 // This is used when determining the selected CRWSessionEntry and only useful
30 // to the SessionServiceIOS.
33 // The original URL of the page. In cases where a redirect occurred, |url_|
34 // will contain the final post-redirect URL, and |originalUrl_| will contain
35 // the pre-redirect URL. This field is not persisted to disk.
38 // Headers passed along with the request. For POST requests, these are
39 // persisted, to be able to resubmit them. Some specialized non-POST requests
40 // may also pass custom headers.
41 base::scoped_nsobject<NSMutableDictionary> _httpHeaders;
43 // Data submitted with a POST request, persisted for resubmits.
46 // Serialized representation of the state object that was used in conjunction
47 // with a JavaScript window.history.pushState() or
48 // window.history.replaceState() call that created or modified this
49 // CRWSessionEntry. Intended to be used for JavaScript history operations and
50 // will be nil in most cases.
51 NSString* _serializedStateObject;
53 // Whether or not this entry was created by calling history.pushState().
54 BOOL _createdFromPushState;
56 // If |YES| use a desktop user agent in HTTP requests and UIWebView.
57 BOOL _useDesktopUserAgent;
59 // If |YES| the page was last fetched through the data reduction proxy.
60 BOOL _usedDataReductionProxy;
62 // Whether or not to bypass showing the resubmit data confirmation when
63 // loading a POST request. Set to YES for browser-generated POST requests such
64 // as search-by-image requests.
65 BOOL _skipResubmitDataConfirmation;
67 // The NavigationItemImpl corresponding to this CRWSessionEntry.
68 // TODO(stuartmorgan): Move ownership to NavigationManagerImpl.
69 scoped_ptr<web::NavigationItemImpl> _navigationItem;
71 base::mac::ObjCPropertyReleaser _propertyReleaser_CRWSessionEntry;
73 // Redefine originalUrl to be read-write.
74 @property(nonatomic, readwrite) const GURL& originalUrl;
76 // Converts a serialized NSDictionary to a web::PageScrollState.
77 + (web::PageScrollState)scrollStateFromDictionary:(NSDictionary*)dictionary;
78 // Serializes a web::PageScrollState to an NSDictionary.
79 + (NSDictionary*)dictionaryFromScrollState:
80 (const web::PageScrollState&)scrollState;
81 // Returns a readable description of |scrollState|.
82 + (NSString*)scrollStateDescription:(const web::PageScrollState&)scrollState;
85 @implementation CRWSessionEntry
87 @synthesize POSTData = _POSTData;
88 @synthesize originalUrl = _originalUrl;
89 @synthesize useDesktopUserAgent = _useDesktopUserAgent;
90 @synthesize usedDataReductionProxy = _usedDataReductionProxy;
91 @synthesize index = _index;
92 @synthesize serializedStateObject = _serializedStateObject;
93 @synthesize createdFromPushState = _createdFromPushState;
94 @synthesize skipResubmitDataConfirmation = _skipResubmitDataConfirmation;
96 // Creates a new session entry. These may be nil.
97 - (instancetype)initWithUrl:(const GURL&)url
98 referrer:(const web::Referrer&)referrer
99 transition:(ui::PageTransition)transition
100 useDesktopUserAgent:(BOOL)useDesktopUserAgent
101 rendererInitiated:(BOOL)rendererInitiated {
104 _propertyReleaser_CRWSessionEntry.Init(self, [CRWSessionEntry class]);
105 _navigationItem.reset(new web::NavigationItemImpl());
107 _navigationItem->SetURL(url);
108 _navigationItem->SetReferrer(referrer);
109 _navigationItem->SetTransitionType(transition);
110 _navigationItem->set_is_renderer_initiated(rendererInitiated);
112 self.originalUrl = url;
113 self.useDesktopUserAgent = useDesktopUserAgent;
118 - (instancetype)initWithNavigationItem:(scoped_ptr<web::NavigationItem>)item
122 _propertyReleaser_CRWSessionEntry.Init(self, [CRWSessionEntry class]);
123 _navigationItem.reset(
124 static_cast<web::NavigationItemImpl*>(item.release()));
127 self.originalUrl = _navigationItem->GetURL();
128 self.useDesktopUserAgent = NO;
133 - (instancetype)initWithCoder:(NSCoder*)aDecoder {
136 _propertyReleaser_CRWSessionEntry.Init(self, [CRWSessionEntry class]);
137 _navigationItem.reset(new web::NavigationItemImpl());
139 // Desktop chrome only persists virtualUrl_ and uses it to feed the url
140 // when creating a NavigationEntry.
142 if ([aDecoder containsValueForKey:@"virtualUrlString"]) {
144 web::nscoder_util::DecodeString(aDecoder, @"virtualUrlString"));
146 // Backward compatibility.
147 url = net::GURLWithNSURL([aDecoder decodeObjectForKey:@"virtualUrl"]);
149 _navigationItem->SetURL(url);
150 self.originalUrl = url;
152 if ([aDecoder containsValueForKey:@"referrerUrlString"]) {
153 const std::string referrerString(web::nscoder_util::DecodeString(
154 aDecoder, @"referrerUrlString"));
155 web::ReferrerPolicy referrerPolicy =
156 static_cast<web::ReferrerPolicy>(
157 [aDecoder decodeIntForKey:@"referrerPolicy"]);
158 _navigationItem->SetReferrer(
159 web::Referrer(GURL(referrerString), referrerPolicy));
161 // Backward compatibility.
162 NSURL* referrer = [aDecoder decodeObjectForKey:@"referrer"];
163 _navigationItem->SetReferrer(web::Referrer(
164 net::GURLWithNSURL(referrer), web::ReferrerPolicyDefault));
167 if ([aDecoder containsValueForKey:@"timestamp"]) {
168 int64 us = [aDecoder decodeInt64ForKey:@"timestamp"];
169 _navigationItem->SetTimestamp(base::Time::FromInternalValue(us));
172 NSString* title = [aDecoder decodeObjectForKey:@"title"];
173 // Use a transition type of reload so that we don't incorrectly increase
174 // the typed count. This is what desktop chrome does.
175 _navigationItem->SetPageID(-1);
176 _navigationItem->SetTitle(base::SysNSStringToUTF16(title));
177 _navigationItem->SetTransitionType(ui::PAGE_TRANSITION_RELOAD);
178 _navigationItem->SetPageScrollState([[self class]
179 scrollStateFromDictionary:[aDecoder decodeObjectForKey:@"state"]]);
180 self.index = [aDecoder decodeIntForKey:@"index"];
181 self.useDesktopUserAgent =
182 [aDecoder decodeBoolForKey:@"useDesktopUserAgent"];
183 self.usedDataReductionProxy =
184 [aDecoder decodeBoolForKey:@"usedDataReductionProxy"];
185 [self addHTTPHeaders:[aDecoder decodeObjectForKey:@"httpHeaders"]];
186 self.POSTData = [aDecoder decodeObjectForKey:@"POSTData"];
187 self.skipResubmitDataConfirmation =
188 [aDecoder decodeBoolForKey:@"skipResubmitDataConfirmation"];
193 - (void)encodeWithCoder:(NSCoder*)aCoder {
194 // Desktop Chrome doesn't persist |url_| or |originalUrl_|, only
196 [aCoder encodeInt:self.index forKey:@"index"];
197 web::nscoder_util::EncodeString(aCoder, @"virtualUrlString",
198 _navigationItem->GetVirtualURL().spec());
199 web::nscoder_util::EncodeString(aCoder, @"referrerUrlString",
200 _navigationItem->GetReferrer().url.spec());
201 [aCoder encodeInt:_navigationItem->GetReferrer().policy
202 forKey:@"referrerPolicy"];
203 [aCoder encodeInt64:_navigationItem->GetTimestamp().ToInternalValue()
204 forKey:@"timestamp"];
206 [aCoder encodeObject:base::SysUTF16ToNSString(_navigationItem->GetTitle())
208 [aCoder encodeObject:[[self class] dictionaryFromScrollState:
209 _navigationItem->GetPageScrollState()]
211 [aCoder encodeBool:self.useDesktopUserAgent forKey:@"useDesktopUserAgent"];
212 [aCoder encodeBool:self.usedDataReductionProxy
213 forKey:@"usedDataReductionProxy"];
214 [aCoder encodeObject:self.httpHeaders forKey:@"httpHeaders"];
215 [aCoder encodeObject:self.POSTData forKey:@"POSTData"];
216 [aCoder encodeBool:self.skipResubmitDataConfirmation
217 forKey:@"skipResubmitDataConfirmation"];
220 // TODO(ios): Shall we overwrite EqualTo:?
222 - (instancetype)copyWithZone:(NSZone*)zone {
223 CRWSessionEntry* copy = [[[self class] alloc] init];
224 copy->_propertyReleaser_CRWSessionEntry.Init(copy, [CRWSessionEntry class]);
225 copy->_navigationItem.reset(
226 new web::NavigationItemImpl(*_navigationItem.get()));
227 copy->_index = _index;
228 copy->_originalUrl = _originalUrl;
229 copy->_useDesktopUserAgent = _useDesktopUserAgent;
230 copy->_usedDataReductionProxy = _usedDataReductionProxy;
231 copy->_POSTData = [_POSTData copy];
232 copy->_httpHeaders.reset([_httpHeaders mutableCopy]);
233 copy->_skipResubmitDataConfirmation = _skipResubmitDataConfirmation;
237 - (NSString*)description {
240 @"url:%@ originalurl:%@ title:%@ transition:%d scrollState:%@ "
241 @"desktopUA:%d " @"proxy:%d",
242 base::SysUTF8ToNSString(_navigationItem->GetURL().spec()),
243 base::SysUTF8ToNSString(self.originalUrl.spec()),
244 base::SysUTF16ToNSString(_navigationItem->GetTitle()),
245 _navigationItem->GetTransitionType(),
247 scrollStateDescription:_navigationItem->GetPageScrollState()],
248 _useDesktopUserAgent, _usedDataReductionProxy];
251 - (web::NavigationItem*)navigationItem {
252 return _navigationItem.get();
255 - (NSDictionary*)httpHeaders {
256 return _httpHeaders ? [NSDictionary dictionaryWithDictionary:_httpHeaders]
260 - (void)addHTTPHeaders:(NSDictionary*)moreHTTPHeaders {
262 [_httpHeaders addEntriesFromDictionary:moreHTTPHeaders];
264 _httpHeaders.reset([moreHTTPHeaders mutableCopy]);
267 - (void)removeHTTPHeaderForKey:(NSString*)key {
268 [_httpHeaders removeObjectForKey:key];
269 if (![_httpHeaders count])
270 _httpHeaders.reset();
273 - (void)resetHTTPHeaders {
274 _httpHeaders.reset();
277 #pragma mark - Serialization helpers
279 + (web::PageScrollState)scrollStateFromDictionary:(NSDictionary*)dictionary {
280 web::PageScrollState scrollState;
281 NSNumber* serializedValue = nil;
282 if ((serializedValue = dictionary[kScrollOffsetXKey]))
283 scrollState.set_scroll_offset_x([serializedValue doubleValue]);
284 if ((serializedValue = dictionary[kScrollOffsetYKey]))
285 scrollState.set_scroll_offset_y([serializedValue doubleValue]);
286 if ((serializedValue = dictionary[kMinimumZoomScaleKey]))
287 scrollState.set_minimum_zoom_scale([serializedValue doubleValue]);
288 if ((serializedValue = dictionary[kMaximumZoomScaleKey]))
289 scrollState.set_maximum_zoom_scale([serializedValue doubleValue]);
290 if ((serializedValue = dictionary[kZoomScaleKey]))
291 scrollState.set_zoom_scale([serializedValue doubleValue]);
295 + (NSDictionary*)dictionaryFromScrollState:
296 (const web::PageScrollState&)scrollState {
298 kScrollOffsetXKey : @(scrollState.scroll_offset_x()),
299 kScrollOffsetYKey : @(scrollState.scroll_offset_y()),
300 kMinimumZoomScaleKey : @(scrollState.minimum_zoom_scale()),
301 kMaximumZoomScaleKey : @(scrollState.maximum_zoom_scale()),
302 kZoomScaleKey : @(scrollState.zoom_scale())
306 + (NSString*)scrollStateDescription:(const web::PageScrollState&)scrollState {
307 NSString* const kPageScrollStateDescriptionFormat =
308 @"{ scrollOffset:(%0.2f, %0.2f), zoomScaleRange:(%0.2f, %0.2f), "
309 @"zoomScale:%0.2f }";
310 return [NSString stringWithFormat:kPageScrollStateDescriptionFormat,
311 scrollState.scroll_offset_x(),
312 scrollState.scroll_offset_y(),
313 scrollState.minimum_zoom_scale(),
314 scrollState.maximum_zoom_scale(),
315 scrollState.zoom_scale()];