| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/web/navigation/navigation_item_impl.h" |
| |
| #import <stddef.h> |
| |
| #import <memory> |
| #import <utility> |
| |
| #import "base/check_op.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/strings/utf_string_conversions.h" |
| #import "components/url_formatter/url_formatter.h" |
| #import "ios/web/common/features.h" |
| #import "ios/web/navigation/navigation_manager_impl.h" |
| #import "ios/web/navigation/proto_util.h" |
| #import "ios/web/navigation/wk_navigation_util.h" |
| #import "ios/web/public/session/proto/navigation.pb.h" |
| #import "ios/web/public/session/proto/proto_util.h" |
| #import "ios/web/public/web_client.h" |
| #import "ui/base/page_transition_types.h" |
| #import "ui/gfx/text_elider.h" |
| |
| namespace web { |
| namespace { |
| |
| // Returns a new unique ID for use in NavigationItem during construction. The |
| // returned ID is guaranteed to be nonzero (which is the "no ID" indicator). |
| static int GetUniqueIDInConstructor() { |
| static int unique_id_counter = 0; |
| return ++unique_id_counter; |
| } |
| |
| // Returns whether `referrer` needs to be serialized. |
| bool ShouldSerializeReferrer(const Referrer& referrer) { |
| return referrer.url.is_valid() && |
| referrer.url.spec().size() < url::kMaxURLChars; |
| } |
| |
| } // namespace |
| |
| using HttpRequestHeaders = NavigationItem::HttpRequestHeaders; |
| |
| // Value 512 was picked as a tradeoff between saving memory from excessively |
| // long titles, while preserving the entire title as often as possible for |
| // features like Sync, where titles can be shared to other platforms that |
| // have UI surfaces supporting longer titles than iOS. |
| const size_t kMaxTitleLength = 512; |
| |
| // static |
| std::unique_ptr<NavigationItem> NavigationItem::Create() { |
| return std::unique_ptr<NavigationItem>(new NavigationItemImpl()); |
| } |
| |
| NavigationItemImpl::NavigationItemImpl() |
| : unique_id_(GetUniqueIDInConstructor()) {} |
| |
| NavigationItemImpl::~NavigationItemImpl() {} |
| |
| NavigationItemImpl::NavigationItemImpl( |
| const proto::NavigationItemStorage& storage) |
| : unique_id_(GetUniqueIDInConstructor()), |
| referrer_(ReferrerFromProto(storage.referrer())), |
| title_(base::UTF8ToUTF16(storage.title())), |
| // Use reload transition type to avoid incorrect increase for other |
| // transition types (such as typed). |
| transition_type_(ui::PAGE_TRANSITION_RELOAD), |
| timestamp_(TimeFromProto(storage.timestamp())), |
| user_agent_type_(UserAgentTypeFromProto(storage.user_agent())), |
| http_request_headers_( |
| HttpRequestHeadersFromProto(storage.http_request_headers())) { |
| // While the virtual URL is persisted, the original request URL and the |
| // non-virtual URL needs to be set upon NavigationItem creation. Since |
| // GetVirtualURL() returns `url_` for the non-overridden case, this will |
| // also update the virtual URL reported by this object. |
| url_ = original_request_url_ = GURL(storage.url()); |
| |
| if (!storage.security_scoped_file_resource().empty()) { |
| const std::string& bytes = storage.security_scoped_file_resource(); |
| security_scoped_file_resource_ = [NSData dataWithBytes:bytes.data() |
| length:bytes.size()]; |
| } |
| |
| // Restore the `virtual_url`. In case the `url` is invalid, it should be set |
| // to the the value saved for `virtual_url` (we never store `virtual_url` if |
| // equal to `url`, so restore to `url` only in that case). |
| const GURL virtual_url(storage.virtual_url()); |
| if (virtual_url.is_valid()) { |
| if (!url_.is_valid()) { |
| url_ = virtual_url; |
| } else { |
| if (virtual_url != url_) { |
| virtual_url_ = virtual_url; |
| } |
| } |
| } |
| } |
| |
| void NavigationItemImpl::SerializeToProto( |
| proto::NavigationItemStorage& storage) const { |
| if (url_.is_valid()) { |
| storage.set_url(url_.spec()); |
| } |
| if (url_ != virtual_url_ && virtual_url_.is_valid()) { |
| storage.set_virtual_url(virtual_url_.spec()); |
| } |
| if (!title_.empty()) { |
| storage.set_title(base::UTF16ToUTF8(title_)); |
| } |
| SerializeTimeToProto(timestamp_, *storage.mutable_timestamp()); |
| storage.set_user_agent(UserAgentTypeToProto(user_agent_type_)); |
| if (ShouldSerializeReferrer(referrer_)) { |
| SerializeReferrerToProto(referrer_, *storage.mutable_referrer()); |
| } |
| if (http_request_headers_.count) { |
| SerializeHttpRequestHeadersToProto(http_request_headers_, |
| *storage.mutable_http_request_headers()); |
| } |
| if (security_scoped_file_resource_) { |
| storage.set_security_scoped_file_resource( |
| static_cast<const char*>(security_scoped_file_resource_.bytes), |
| security_scoped_file_resource_.length); |
| } |
| } |
| |
| std::unique_ptr<NavigationItemImpl> NavigationItemImpl::Clone() { |
| return base::WrapUnique(new NavigationItemImpl(*this)); |
| } |
| |
| int NavigationItemImpl::GetUniqueID() const { |
| return unique_id_; |
| } |
| |
| void NavigationItemImpl::SetOriginalRequestURL(const GURL& url) { |
| original_request_url_ = url; |
| } |
| |
| const GURL& NavigationItemImpl::GetOriginalRequestURL() const { |
| return original_request_url_; |
| } |
| |
| void NavigationItemImpl::SetURL(const GURL& url) { |
| url_ = url; |
| cached_display_title_.clear(); |
| } |
| |
| const GURL& NavigationItemImpl::GetURL() const { |
| return url_; |
| } |
| |
| void NavigationItemImpl::SetReferrer(const web::Referrer& referrer) { |
| referrer_ = referrer; |
| } |
| |
| const web::Referrer& NavigationItemImpl::GetReferrer() const { |
| return referrer_; |
| } |
| |
| void NavigationItemImpl::SetVirtualURL(const GURL& url) { |
| virtual_url_ = (url == url_) ? GURL() : url; |
| cached_display_title_.clear(); |
| } |
| |
| const GURL& NavigationItemImpl::GetVirtualURL() const { |
| return virtual_url_.is_empty() ? url_ : virtual_url_; |
| } |
| |
| void NavigationItemImpl::SetTitle(const std::u16string& title) { |
| if (title_ == title) { |
| return; |
| } |
| |
| if (title.size() > kMaxTitleLength) { |
| title_ = gfx::TruncateString(title, kMaxTitleLength, gfx::CHARACTER_BREAK); |
| } else { |
| title_ = title; |
| } |
| cached_display_title_.clear(); |
| } |
| |
| const std::u16string& NavigationItemImpl::GetTitle() const { |
| return title_; |
| } |
| |
| const std::u16string& NavigationItemImpl::GetTitleForDisplay() const { |
| // Most pages have real titles. Don't even bother caching anything if this is |
| // the case. |
| if (!title_.empty()) { |
| return title_; |
| } |
| |
| // More complicated cases will use the URLs as the title. This result we will |
| // cache since it's more complicated to compute. |
| if (!cached_display_title_.empty()) { |
| return cached_display_title_; |
| } |
| |
| // File urls have different display rules, so use one if it is present. |
| cached_display_title_ = NavigationItemImpl::GetDisplayTitleForURL( |
| GetURL().SchemeIsFile() ? GetURL() : GetVirtualURL()); |
| return cached_display_title_; |
| } |
| |
| void NavigationItemImpl::SetTransitionType(ui::PageTransition transition_type) { |
| transition_type_ = transition_type; |
| } |
| |
| ui::PageTransition NavigationItemImpl::GetTransitionType() const { |
| return transition_type_; |
| } |
| |
| const FaviconStatus& NavigationItemImpl::GetFaviconStatus() const { |
| return favicon_status_; |
| } |
| |
| void NavigationItemImpl::SetFaviconStatus(const FaviconStatus& favicon_status) { |
| favicon_status_ = favicon_status; |
| } |
| |
| const SSLStatus& NavigationItemImpl::GetSSL() const { |
| return ssl_; |
| } |
| |
| SSLStatus& NavigationItemImpl::GetSSL() { |
| return ssl_; |
| } |
| |
| void NavigationItemImpl::SetTimestamp(base::Time timestamp) { |
| timestamp_ = timestamp; |
| } |
| |
| base::Time NavigationItemImpl::GetTimestamp() const { |
| return timestamp_; |
| } |
| |
| void NavigationItemImpl::SetUserAgentType(UserAgentType type) { |
| user_agent_type_ = type; |
| } |
| |
| void NavigationItemImpl::SetSecurityScopedFileResource(NSData* data) { |
| security_scoped_file_resource_ = [data copy]; |
| } |
| |
| NSData* NavigationItemImpl::GetSecurityScopedFileResource() { |
| return security_scoped_file_resource_; |
| } |
| |
| void NavigationItemImpl::SetUntrusted() { |
| is_untrusted_ = true; |
| } |
| |
| bool NavigationItemImpl::IsUntrusted() { |
| return is_untrusted_; |
| } |
| |
| UserAgentType NavigationItemImpl::GetUserAgentType() const { |
| return user_agent_type_; |
| } |
| |
| bool NavigationItemImpl::HasPostData() const { |
| return post_data_ != nil; |
| } |
| |
| HttpRequestHeaders* NavigationItemImpl::GetHttpRequestHeaders() const { |
| return [http_request_headers_ copy]; |
| } |
| |
| void NavigationItemImpl::AddHttpRequestHeaders( |
| HttpRequestHeaders* additional_headers) { |
| if (!additional_headers) { |
| return; |
| } |
| |
| if (http_request_headers_) { |
| [http_request_headers_ addEntriesFromDictionary:additional_headers]; |
| } else { |
| http_request_headers_ = [additional_headers mutableCopy]; |
| } |
| } |
| |
| void NavigationItemImpl::SetHttpsUpgradeType( |
| HttpsUpgradeType https_upgrade_type) { |
| https_upgrade_type_ = https_upgrade_type; |
| } |
| |
| HttpsUpgradeType NavigationItemImpl::GetHttpsUpgradeType() const { |
| return https_upgrade_type_; |
| } |
| |
| void NavigationItemImpl::SetSerializedStateObject( |
| NSString* serialized_state_object) { |
| serialized_state_object_ = serialized_state_object; |
| } |
| |
| NSString* NavigationItemImpl::GetSerializedStateObject() const { |
| return serialized_state_object_; |
| } |
| |
| void NavigationItemImpl::SetNavigationInitiationType( |
| web::NavigationInitiationType navigation_initiation_type) { |
| navigation_initiation_type_ = navigation_initiation_type; |
| } |
| |
| web::NavigationInitiationType NavigationItemImpl::NavigationInitiationType() |
| const { |
| return navigation_initiation_type_; |
| } |
| |
| void NavigationItemImpl::SetIsCreatedFromHashChange(bool hash_change) { |
| is_created_from_hash_change_ = hash_change; |
| } |
| |
| bool NavigationItemImpl::IsCreatedFromHashChange() const { |
| return is_created_from_hash_change_; |
| } |
| |
| void NavigationItemImpl::SetShouldSkipSerialization(bool skip) { |
| should_skip_serialization_ = skip; |
| } |
| |
| bool NavigationItemImpl::ShouldSkipSerialization() const { |
| return should_skip_serialization_ || url_.spec().size() > url::kMaxURLChars; |
| } |
| |
| void NavigationItemImpl::SetPostData(NSData* post_data) { |
| post_data_ = post_data; |
| } |
| |
| NSData* NavigationItemImpl::GetPostData() const { |
| return post_data_; |
| } |
| |
| void NavigationItemImpl::RemoveHttpRequestHeaderForKey(NSString* key) { |
| DCHECK(key); |
| [http_request_headers_ removeObjectForKey:key]; |
| if (![http_request_headers_ count]) { |
| http_request_headers_ = nil; |
| } |
| } |
| |
| void NavigationItemImpl::ResetHttpRequestHeaders() { |
| http_request_headers_ = nil; |
| } |
| |
| void NavigationItemImpl::ResetForCommit() { |
| // Navigation initiation type is only valid for pending navigations, thus |
| // always reset to NONE after the item is committed. |
| SetNavigationInitiationType(web::NavigationInitiationType::NONE); |
| } |
| |
| void NavigationItemImpl::RestoreStateFromItem(NavigationItem* other) { |
| // Restore the UserAgent type in any case, as if the URLs are different it |
| // might mean that `this` is a next navigation. The page display state and the |
| // virtual URL only make sense if it is the same item. The other headers might |
| // not make sense after creating a new navigation to the page. |
| if (other->GetUserAgentType() != UserAgentType::NONE) { |
| SetUserAgentType(other->GetUserAgentType()); |
| } |
| if (url_ == other->GetURL()) { |
| SetVirtualURL(other->GetVirtualURL()); |
| SetSecurityScopedFileResource(other->GetSecurityScopedFileResource()); |
| } |
| } |
| |
| // static |
| std::u16string NavigationItemImpl::GetDisplayTitleForURL(const GURL& url) { |
| if (url.is_empty()) { |
| return std::u16string(); |
| } |
| |
| std::u16string title = url_formatter::FormatUrl(url); |
| |
| // For file:// URLs use the filename as the title, not the full path. |
| if (url.SchemeIsFile()) { |
| std::u16string::size_type slashpos = title.rfind('/'); |
| if (slashpos != std::u16string::npos && slashpos != (title.size() - 1)) { |
| title = title.substr(slashpos + 1); |
| } |
| } |
| |
| const size_t kMaxTitleChars = 4 * 1024; |
| gfx::ElideString(title, kMaxTitleChars, &title); |
| return title; |
| } |
| |
| #ifndef NDEBUG |
| NSString* NavigationItemImpl::GetDescription() const { |
| return [NSString |
| stringWithFormat: |
| @"url:%s virtual_url_:%s originalurl:%s referrer: %s title:%s " |
| @"transition:%d userAgent:%s " |
| "is_created_from_hash_change: %@ " |
| "navigation_initiation_type: %d " |
| "https_upgrade_type: %s", |
| url_.spec().c_str(), virtual_url_.spec().c_str(), |
| original_request_url_.spec().c_str(), referrer_.url.spec().c_str(), |
| base::UTF16ToUTF8(title_).c_str(), transition_type_, |
| GetUserAgentTypeDescription(user_agent_type_).c_str(), |
| is_created_from_hash_change_ ? @"true" : @"false", |
| static_cast<int>(navigation_initiation_type_), |
| GetHttpsUpgradeTypeDescription(https_upgrade_type_).c_str()]; |
| } |
| #endif |
| |
| NavigationItemImpl::NavigationItemImpl(const NavigationItemImpl& item) |
| : unique_id_(item.unique_id_), |
| original_request_url_(item.original_request_url_), |
| url_(item.url_), |
| referrer_(item.referrer_), |
| virtual_url_(item.virtual_url_), |
| title_(item.title_), |
| transition_type_(item.transition_type_), |
| favicon_status_(item.favicon_status_), |
| ssl_(item.ssl_), |
| timestamp_(item.timestamp_), |
| user_agent_type_(item.user_agent_type_), |
| http_request_headers_([item.http_request_headers_ mutableCopy]), |
| serialized_state_object_([item.serialized_state_object_ copy]), |
| is_created_from_hash_change_(item.is_created_from_hash_change_), |
| should_skip_serialization_(item.should_skip_serialization_), |
| post_data_([item.post_data_ copy]), |
| navigation_initiation_type_(item.navigation_initiation_type_), |
| is_untrusted_(item.is_untrusted_), |
| cached_display_title_(item.cached_display_title_), |
| https_upgrade_type_(item.https_upgrade_type_) { |
| CloneDataFrom(item); |
| } |
| |
| } // namespace web |