1/* 2 * Copyright (C) 2010, 2011, 2012 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "WebBackForwardList.h" 28 29#include "Logging.h" 30#include <wtf/RetainPtr.h> 31#include <CoreFoundation/CoreFoundation.h> 32 33using namespace WebCore; 34 35namespace WebKit { 36 37static uint64_t generateWebBackForwardItemID() 38{ 39 // These IDs exist in the UIProcess for items created by the UIProcess. 40 // The IDs generated here need to never collide with the IDs created in WebBackForwardListProxy in the WebProcess. 41 // We accomplish this by starting from 2, and only ever using even ids. 42 static uint64_t uniqueHistoryItemID = 0; 43 uniqueHistoryItemID += 2; 44 return uniqueHistoryItemID; 45} 46 47static CFIndex currentVersion = 1; 48DEFINE_STATIC_GETTER(CFNumberRef, SessionHistoryCurrentVersion, (CFNumberCreate(0, kCFNumberCFIndexType, ¤tVersion))); 49 50DEFINE_STATIC_GETTER(CFStringRef, SessionHistoryVersionKey, (CFSTR("SessionHistoryVersion"))); 51DEFINE_STATIC_GETTER(CFStringRef, SessionHistoryCurrentIndexKey, (CFSTR("SessionHistoryCurrentIndex"))); 52DEFINE_STATIC_GETTER(CFStringRef, SessionHistoryEntriesKey, (CFSTR("SessionHistoryEntries"))); 53DEFINE_STATIC_GETTER(CFStringRef, SessionHistoryEntryTitleKey, (CFSTR("SessionHistoryEntryTitle"))); 54DEFINE_STATIC_GETTER(CFStringRef, SessionHistoryEntryURLKey, (CFSTR("SessionHistoryEntryURL"))); 55DEFINE_STATIC_GETTER(CFStringRef, SessionHistoryEntryOriginalURLKey, (CFSTR("SessionHistoryEntryOriginalURL"))); 56DEFINE_STATIC_GETTER(CFStringRef, SessionHistoryEntryDataKey, (CFSTR("SessionHistoryEntryData"))); 57 58static bool extractBackForwardListEntriesFromArray(CFArrayRef, BackForwardListItemVector&); 59 60static CFDictionaryRef createEmptySessionHistoryDictionary() 61{ 62 static const void* keys[1] = { SessionHistoryVersionKey() }; 63 static const void* values[1] = { SessionHistoryCurrentVersion() }; 64 65 return CFDictionaryCreate(0, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 66} 67 68CFDictionaryRef WebBackForwardList::createCFDictionaryRepresentation(WebPageProxy::WebPageProxySessionStateFilterCallback filter, void* context) const 69{ 70 ASSERT(!m_hasCurrentIndex || m_currentIndex < m_entries.size()); 71 72 if (!m_hasCurrentIndex) { 73 // We represent having no current index by writing out an empty dictionary (besides the version). 74 return createEmptySessionHistoryDictionary(); 75 } 76 77 RetainPtr<CFMutableArrayRef> entries = adoptCF(CFArrayCreateMutable(0, m_entries.size(), &kCFTypeArrayCallBacks)); 78 79 // We may need to update the current index to account for entries that are filtered by the callback. 80 CFIndex currentIndex = m_currentIndex; 81 bool hasCurrentIndex = true; 82 83 for (size_t i = 0; i < m_entries.size(); ++i) { 84 // If we somehow ended up with a null entry then we should consider the data invalid and not save session history at all. 85 ASSERT(m_entries[i]); 86 if (!m_entries[i]) { 87 LOG(SessionState, "WebBackForwardList contained a null entry at index %lu", i); 88 return 0; 89 } 90 91 if (filter) { 92 if (!filter(toAPI(m_page), WKPageGetSessionBackForwardListItemValueType(), toAPI(m_entries[i].get()), context) 93 || !filter(toAPI(m_page), WKPageGetSessionHistoryURLValueType(), toURLRef(m_entries[i]->originalURL().impl()), context)) { 94 if (i <= m_currentIndex) 95 currentIndex--; 96 continue; 97 } 98 } 99 100 RetainPtr<CFStringRef> url = m_entries[i]->url().createCFString(); 101 RetainPtr<CFStringRef> title = m_entries[i]->title().createCFString(); 102 RetainPtr<CFStringRef> originalURL = m_entries[i]->originalURL().createCFString(); 103 104 // FIXME: This uses the CoreIPC data encoding format, which means that whenever we change the CoreIPC encoding we need to bump the CurrentSessionStateDataVersion 105 // constant in WebPageProxyCF.cpp. The CoreIPC data format is meant to be an implementation detail, and not something that should be written to disk. 106 RetainPtr<CFDataRef> entryData = adoptCF(CFDataCreate(kCFAllocatorDefault, m_entries[i]->backForwardData().data(), m_entries[i]->backForwardData().size())); 107 108 const void* keys[4] = { SessionHistoryEntryURLKey(), SessionHistoryEntryTitleKey(), SessionHistoryEntryOriginalURLKey(), SessionHistoryEntryDataKey() }; 109 const void* values[4] = { url.get(), title.get(), originalURL.get(), entryData.get() }; 110 111 RetainPtr<CFDictionaryRef> entryDictionary = adoptCF(CFDictionaryCreate(0, keys, values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); 112 CFArrayAppendValue(entries.get(), entryDictionary.get()); 113 } 114 115 ASSERT(currentIndex == -1 || (currentIndex > -1 && currentIndex < CFArrayGetCount(entries.get()))); 116 if (currentIndex < -1 || currentIndex >= CFArrayGetCount(entries.get())) { 117 LOG(SessionState, "Filtering entries to be saved resulted in an inconsistent state that we cannot represent"); 118 return 0; 119 } 120 121 // If we have an index and all items before and including the current item were filtered then currentIndex will be -1. 122 // In this case the new current index should point at the first item. 123 // It's also possible that all items were filtered so we should represent not having a current index. 124 if (currentIndex == -1) { 125 if (CFArrayGetCount(entries.get())) 126 currentIndex = 0; 127 else 128 hasCurrentIndex = false; 129 } 130 131 if (hasCurrentIndex) { 132 RetainPtr<CFNumberRef> currentIndexNumber = adoptCF(CFNumberCreate(0, kCFNumberCFIndexType, ¤tIndex)); 133 const void* keys[3] = { SessionHistoryVersionKey(), SessionHistoryCurrentIndexKey(), SessionHistoryEntriesKey() }; 134 const void* values[3] = { SessionHistoryCurrentVersion(), currentIndexNumber.get(), entries.get() }; 135 136 return CFDictionaryCreate(0, keys, values, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 137 } 138 139 // We represent having no current index by writing out an empty dictionary (besides the version). 140 return createEmptySessionHistoryDictionary(); 141} 142 143bool WebBackForwardList::restoreFromCFDictionaryRepresentation(CFDictionaryRef dictionary) 144{ 145 CFNumberRef cfVersion = (CFNumberRef)CFDictionaryGetValue(dictionary, SessionHistoryVersionKey()); 146 if (!cfVersion) { 147 // v0 session history dictionaries did not contain versioning 148 return restoreFromV0CFDictionaryRepresentation(dictionary); 149 } 150 151 if (CFGetTypeID(cfVersion) != CFNumberGetTypeID()) { 152 LOG(SessionState, "WebBackForwardList dictionary representation contains a version that is not a number"); 153 return false; 154 } 155 156 CFIndex version; 157 if (!CFNumberGetValue(cfVersion, kCFNumberCFIndexType, &version)) { 158 LOG(SessionState, "WebBackForwardList dictionary representation does not have a correctly typed current version"); 159 return false; 160 } 161 162 if (version == 1) 163 return restoreFromV1CFDictionaryRepresentation(dictionary); 164 165 LOG(SessionState, "WebBackForwardList dictionary representation has an invalid current version (%ld)", version); 166 return false; 167} 168 169bool WebBackForwardList::restoreFromV0CFDictionaryRepresentation(CFDictionaryRef dictionary) 170{ 171 CFNumberRef cfIndex = (CFNumberRef)CFDictionaryGetValue(dictionary, SessionHistoryCurrentIndexKey()); 172 if (!cfIndex || CFGetTypeID(cfIndex) != CFNumberGetTypeID()) { 173 LOG(SessionState, "WebBackForwardList dictionary representation does not have a valid current index"); 174 return false; 175 } 176 177 CFIndex currentCFIndex; 178 if (!CFNumberGetValue(cfIndex, kCFNumberCFIndexType, ¤tCFIndex)) { 179 LOG(SessionState, "WebBackForwardList dictionary representation does not have a correctly typed current index"); 180 return false; 181 } 182 183 if (currentCFIndex < -1) { 184 LOG(SessionState, "WebBackForwardList dictionary representation contains an unexpected negative current index (%ld)", currentCFIndex); 185 return false; 186 } 187 188 CFArrayRef cfEntries = (CFArrayRef)CFDictionaryGetValue(dictionary, SessionHistoryEntriesKey()); 189 if (!cfEntries || CFGetTypeID(cfEntries) != CFArrayGetTypeID()) { 190 LOG(SessionState, "WebBackForwardList dictionary representation does not have a valid list of entries"); 191 return false; 192 } 193 194 CFIndex size = CFArrayGetCount(cfEntries); 195 if (size < 0 || currentCFIndex >= size) { 196 LOG(SessionState, "WebBackForwardList dictionary representation contains an invalid current index (%ld) for the number of entries (%ld)", currentCFIndex, size); 197 return false; 198 } 199 200 // Version 0 session history relied on currentIndex == -1 to represent the same thing as not having a current index. 201 bool hasCurrentIndex = currentCFIndex != -1; 202 203 if (!hasCurrentIndex && size) { 204 LOG(SessionState, "WebBackForwardList dictionary representation says there is no current index, but there is a list of %ld entries", size); 205 return false; 206 } 207 208 BackForwardListItemVector entries; 209 if (!extractBackForwardListEntriesFromArray(cfEntries, entries)) { 210 // extractBackForwardListEntriesFromArray has already logged the appropriate error message. 211 return false; 212 } 213 214 ASSERT(entries.size() == static_cast<unsigned>(size)); 215 216 m_hasCurrentIndex = hasCurrentIndex; 217 m_currentIndex = m_hasCurrentIndex ? static_cast<uint32_t>(currentCFIndex) : 0; 218 m_entries = entries; 219 220 return true; 221} 222 223bool WebBackForwardList::restoreFromV1CFDictionaryRepresentation(CFDictionaryRef dictionary) 224{ 225 CFNumberRef cfIndex = (CFNumberRef)CFDictionaryGetValue(dictionary, SessionHistoryCurrentIndexKey()); 226 if (!cfIndex) { 227 // No current index means the dictionary represents an empty session. 228 m_hasCurrentIndex = false; 229 m_currentIndex = 0; 230 m_entries.clear(); 231 232 return true; 233 } 234 235 if (CFGetTypeID(cfIndex) != CFNumberGetTypeID()) { 236 LOG(SessionState, "WebBackForwardList dictionary representation does not have a valid current index"); 237 return false; 238 } 239 240 CFIndex currentCFIndex; 241 if (!CFNumberGetValue(cfIndex, kCFNumberCFIndexType, ¤tCFIndex)) { 242 LOG(SessionState, "WebBackForwardList dictionary representation does not have a correctly typed current index"); 243 return false; 244 } 245 246 if (currentCFIndex < 0) { 247 LOG(SessionState, "WebBackForwardList dictionary representation contains an unexpected negative current index (%ld)", currentCFIndex); 248 return false; 249 } 250 251 CFArrayRef cfEntries = (CFArrayRef)CFDictionaryGetValue(dictionary, SessionHistoryEntriesKey()); 252 if (!cfEntries || CFGetTypeID(cfEntries) != CFArrayGetTypeID()) { 253 LOG(SessionState, "WebBackForwardList dictionary representation does not have a valid list of entries"); 254 return false; 255 } 256 257 CFIndex size = CFArrayGetCount(cfEntries); 258 if (currentCFIndex >= size) { 259 LOG(SessionState, "WebBackForwardList dictionary representation contains an invalid current index (%ld) for the number of entries (%ld)", currentCFIndex, size); 260 return false; 261 } 262 263 BackForwardListItemVector entries; 264 if (!extractBackForwardListEntriesFromArray(cfEntries, entries)) { 265 // extractBackForwardListEntriesFromArray has already logged the appropriate error message. 266 return false; 267 } 268 269 ASSERT(entries.size() == static_cast<unsigned>(size)); 270 271 m_hasCurrentIndex = true; 272 m_currentIndex = static_cast<uint32_t>(currentCFIndex); 273 m_entries = entries; 274 275 return true; 276} 277 278static bool extractBackForwardListEntriesFromArray(CFArrayRef cfEntries, BackForwardListItemVector& entries) 279{ 280 CFIndex size = CFArrayGetCount(cfEntries); 281 282 entries.reserveCapacity(size); 283 for (CFIndex i = 0; i < size; ++i) { 284 CFDictionaryRef entryDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(cfEntries, i); 285 if (!entryDictionary || CFGetTypeID(entryDictionary) != CFDictionaryGetTypeID()) { 286 LOG(SessionState, "WebBackForwardList entry array does not have a valid entry at index %i", (int)i); 287 return false; 288 } 289 290 CFStringRef entryURL = (CFStringRef)CFDictionaryGetValue(entryDictionary, SessionHistoryEntryURLKey()); 291 if (!entryURL || CFGetTypeID(entryURL) != CFStringGetTypeID()) { 292 LOG(SessionState, "WebBackForwardList entry at index %i does not have a valid URL", (int)i); 293 return false; 294 } 295 296 CFStringRef entryTitle = (CFStringRef)CFDictionaryGetValue(entryDictionary, SessionHistoryEntryTitleKey()); 297 if (!entryTitle || CFGetTypeID(entryTitle) != CFStringGetTypeID()) { 298 LOG(SessionState, "WebBackForwardList entry at index %i does not have a valid title", (int)i); 299 return false; 300 } 301 302 CFStringRef originalURL = (CFStringRef)CFDictionaryGetValue(entryDictionary, SessionHistoryEntryOriginalURLKey()); 303 if (!originalURL || CFGetTypeID(originalURL) != CFStringGetTypeID()) { 304 LOG(SessionState, "WebBackForwardList entry at index %i does not have a valid original URL", (int)i); 305 return false; 306 } 307 308 CFDataRef backForwardData = (CFDataRef)CFDictionaryGetValue(entryDictionary, SessionHistoryEntryDataKey()); 309 if (!backForwardData || CFGetTypeID(backForwardData) != CFDataGetTypeID()) { 310 LOG(SessionState, "WebBackForwardList entry at index %i does not have back/forward data", (int)i); 311 return false; 312 } 313 314 entries.append(WebBackForwardListItem::create(originalURL, entryURL, entryTitle, CFDataGetBytePtr(backForwardData), CFDataGetLength(backForwardData), generateWebBackForwardItemID())); 315 } 316 317 return true; 318} 319 320} // namespace WebKit 321