2c07c22081c7d4addfe5f7bc85cf71fcb08b2960
[WebKit-https.git] / Source / WebCore / history / HistoryItem.cpp
1 /*
2  * Copyright (C) 2005, 2006, 2008, 2011, 2014 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "HistoryItem.h"
28
29 #include "CachedPage.h"
30 #include "Document.h"
31 #include "IconDatabase.h"
32 #include "KeyedCoding.h"
33 #include "PageCache.h"
34 #include "ResourceRequest.h"
35 #include "SerializedScriptValue.h"
36 #include "SharedBuffer.h"
37 #include <stdio.h>
38 #include <wtf/CurrentTime.h>
39 #include <wtf/DateMath.h>
40 #include <wtf/text/CString.h>
41
42 namespace WebCore {
43
44 static long long generateSequenceNumber()
45 {
46     // Initialize to the current time to reduce the likelihood of generating
47     // identifiers that overlap with those from past/future browser sessions.
48     static long long next = static_cast<long long>(currentTime() * 1000000.0);
49     return ++next;
50 }
51
52 static void defaultNotifyHistoryItemChanged(HistoryItem*)
53 {
54 }
55
56 WEBCORE_EXPORT void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
57
58 HistoryItem::HistoryItem()
59     : m_itemSequenceNumber(generateSequenceNumber())
60     , m_documentSequenceNumber(generateSequenceNumber())
61     , m_pruningReason(PruningReason::None)
62 {
63 }
64
65 HistoryItem::HistoryItem(const String& urlString, const String& title)
66     : m_urlString(urlString)
67     , m_originalURLString(urlString)
68     , m_title(title)
69     , m_itemSequenceNumber(generateSequenceNumber())
70     , m_documentSequenceNumber(generateSequenceNumber())
71     , m_pruningReason(PruningReason::None)
72 {
73     iconDatabase().retainIconForPageURL(m_urlString);
74 }
75
76 HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle)
77     : m_urlString(urlString)
78     , m_originalURLString(urlString)
79     , m_title(title)
80     , m_displayTitle(alternateTitle)
81     , m_pageScaleFactor(0)
82     , m_itemSequenceNumber(generateSequenceNumber())
83     , m_documentSequenceNumber(generateSequenceNumber())
84     , m_pruningReason(PruningReason::None)
85 {
86     iconDatabase().retainIconForPageURL(m_urlString);
87 }
88
89 HistoryItem::~HistoryItem()
90 {
91     ASSERT(!m_cachedPage);
92     iconDatabase().releaseIconForPageURL(m_urlString);
93 }
94
95 inline HistoryItem::HistoryItem(const HistoryItem& item)
96     : RefCounted<HistoryItem>()
97     , m_urlString(item.m_urlString)
98     , m_originalURLString(item.m_originalURLString)
99     , m_referrer(item.m_referrer)
100     , m_target(item.m_target)
101     , m_title(item.m_title)
102     , m_displayTitle(item.m_displayTitle)
103     , m_scrollPosition(item.m_scrollPosition)
104     , m_pageScaleFactor(item.m_pageScaleFactor)
105     , m_lastVisitWasFailure(item.m_lastVisitWasFailure)
106     , m_isTargetItem(item.m_isTargetItem)
107     , m_itemSequenceNumber(item.m_itemSequenceNumber)
108     , m_documentSequenceNumber(item.m_documentSequenceNumber)
109     , m_formContentType(item.m_formContentType)
110     , m_pruningReason(PruningReason::None)
111 #if PLATFORM(IOS)
112     , m_obscuredInsets(item.m_obscuredInsets)
113     , m_scale(item.m_scale)
114     , m_scaleIsInitial(item.m_scaleIsInitial)
115 #endif
116 {
117     if (item.m_formData)
118         m_formData = item.m_formData->copy();
119         
120     unsigned size = item.m_children.size();
121     m_children.reserveInitialCapacity(size);
122     for (unsigned i = 0; i < size; ++i)
123         m_children.uncheckedAppend(item.m_children[i]->copy());
124 }
125
126 Ref<HistoryItem> HistoryItem::copy() const
127 {
128     return adoptRef(*new HistoryItem(*this));
129 }
130
131 void HistoryItem::reset()
132 {
133     iconDatabase().releaseIconForPageURL(m_urlString);
134
135     m_urlString = String();
136     m_originalURLString = String();
137     m_referrer = String();
138     m_target = String();
139     m_title = String();
140     m_displayTitle = String();
141
142     m_lastVisitWasFailure = false;
143     m_isTargetItem = false;
144
145     m_itemSequenceNumber = generateSequenceNumber();
146
147     m_stateObject = nullptr;
148     m_documentSequenceNumber = generateSequenceNumber();
149
150     m_formData = nullptr;
151     m_formContentType = String();
152
153     clearChildren();
154 }
155
156 const String& HistoryItem::urlString() const
157 {
158     return m_urlString;
159 }
160
161 // The first URL we loaded to get to where this history item points.  Includes both client
162 // and server redirects.
163 const String& HistoryItem::originalURLString() const
164 {
165     return m_originalURLString;
166 }
167
168 const String& HistoryItem::title() const
169 {
170     return m_title;
171 }
172
173 const String& HistoryItem::alternateTitle() const
174 {
175     return m_displayTitle;
176 }
177
178 bool HistoryItem::hasCachedPageExpired() const
179 {
180     return m_cachedPage ? m_cachedPage->hasExpired() : false;
181 }
182
183 URL HistoryItem::url() const
184 {
185     return URL(ParsedURLString, m_urlString);
186 }
187
188 URL HistoryItem::originalURL() const
189 {
190     return URL(ParsedURLString, m_originalURLString);
191 }
192
193 const String& HistoryItem::referrer() const
194 {
195     return m_referrer;
196 }
197
198 const String& HistoryItem::target() const
199 {
200     return m_target;
201 }
202
203 void HistoryItem::setAlternateTitle(const String& alternateTitle)
204 {
205     m_displayTitle = alternateTitle;
206     notifyHistoryItemChanged(this);
207 }
208
209 void HistoryItem::setURLString(const String& urlString)
210 {
211     if (m_urlString != urlString) {
212         iconDatabase().releaseIconForPageURL(m_urlString);
213         m_urlString = urlString;
214         iconDatabase().retainIconForPageURL(m_urlString);
215     }
216     
217     notifyHistoryItemChanged(this);
218 }
219
220 void HistoryItem::setURL(const URL& url)
221 {
222     PageCache::singleton().remove(*this);
223     setURLString(url.string());
224     clearDocumentState();
225 }
226
227 void HistoryItem::setOriginalURLString(const String& urlString)
228 {
229     m_originalURLString = urlString;
230     notifyHistoryItemChanged(this);
231 }
232
233 void HistoryItem::setReferrer(const String& referrer)
234 {
235     m_referrer = referrer;
236     notifyHistoryItemChanged(this);
237 }
238
239 void HistoryItem::setTitle(const String& title)
240 {
241     m_title = title;
242     notifyHistoryItemChanged(this);
243 }
244
245 void HistoryItem::setTarget(const String& target)
246 {
247     m_target = target;
248     notifyHistoryItemChanged(this);
249 }
250
251 const IntPoint& HistoryItem::scrollPosition() const
252 {
253     return m_scrollPosition;
254 }
255
256 void HistoryItem::setScrollPosition(const IntPoint& position)
257 {
258     m_scrollPosition = position;
259 }
260
261 void HistoryItem::clearScrollPosition()
262 {
263     m_scrollPosition = IntPoint();
264 }
265
266 bool HistoryItem::shouldRestoreScrollPosition() const
267 {
268     return m_shouldRestoreScrollPosition;
269 }
270
271 void HistoryItem::setShouldRestoreScrollPosition(bool shouldRestore)
272 {
273     m_shouldRestoreScrollPosition = shouldRestore;
274     notifyHistoryItemChanged(this);
275 }
276
277 float HistoryItem::pageScaleFactor() const
278 {
279     return m_pageScaleFactor;
280 }
281
282 void HistoryItem::setPageScaleFactor(float scaleFactor)
283 {
284     m_pageScaleFactor = scaleFactor;
285 }
286
287 void HistoryItem::setDocumentState(const Vector<String>& state)
288 {
289     m_documentState = state;
290 }
291
292 const Vector<String>& HistoryItem::documentState() const
293 {
294     return m_documentState;
295 }
296
297 void HistoryItem::clearDocumentState()
298 {
299     m_documentState.clear();
300 }
301
302 void HistoryItem::setShouldOpenExternalURLsPolicy(ShouldOpenExternalURLsPolicy policy)
303 {
304     m_shouldOpenExternalURLsPolicy = policy;
305 }
306
307 ShouldOpenExternalURLsPolicy HistoryItem::shouldOpenExternalURLsPolicy() const
308 {
309     return m_shouldOpenExternalURLsPolicy;
310 }
311
312 bool HistoryItem::isTargetItem() const
313 {
314     return m_isTargetItem;
315 }
316
317 void HistoryItem::setIsTargetItem(bool flag)
318 {
319     m_isTargetItem = flag;
320 }
321
322 void HistoryItem::setStateObject(RefPtr<SerializedScriptValue>&& object)
323 {
324     m_stateObject = WTFMove(object);
325 }
326
327 void HistoryItem::addChildItem(Ref<HistoryItem>&& child)
328 {
329     ASSERT(!childItemWithTarget(child->target()));
330     m_children.append(WTFMove(child));
331 }
332
333 void HistoryItem::setChildItem(Ref<HistoryItem>&& child)
334 {
335     ASSERT(!child->isTargetItem());
336     unsigned size = m_children.size();
337     for (unsigned i = 0; i < size; ++i)  {
338         if (m_children[i]->target() == child->target()) {
339             child->setIsTargetItem(m_children[i]->isTargetItem());
340             m_children[i] = WTFMove(child);
341             return;
342         }
343     }
344     m_children.append(WTFMove(child));
345 }
346
347 HistoryItem* HistoryItem::childItemWithTarget(const String& target)
348 {
349     unsigned size = m_children.size();
350     for (unsigned i = 0; i < size; ++i) {
351         if (m_children[i]->target() == target)
352             return m_children[i].ptr();
353     }
354     return nullptr;
355 }
356
357 HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number)
358 {
359     unsigned size = m_children.size();
360     for (unsigned i = 0; i < size; ++i) {
361         if (m_children[i]->documentSequenceNumber() == number)
362             return m_children[i].ptr();
363     }
364     return nullptr;
365 }
366
367 const Vector<Ref<HistoryItem>>& HistoryItem::children() const
368 {
369     return m_children;
370 }
371
372 bool HistoryItem::hasChildren() const
373 {
374     return !m_children.isEmpty();
375 }
376
377 void HistoryItem::clearChildren()
378 {
379     m_children.clear();
380 }
381
382 // We do same-document navigation if going to a different item and if either of the following is true:
383 // - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
384 // - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
385 bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem& otherItem) const
386 {
387     if (this == &otherItem)
388         return false;
389
390     if (stateObject() || otherItem.stateObject())
391         return documentSequenceNumber() == otherItem.documentSequenceNumber();
392     
393     if ((url().hasFragmentIdentifier() || otherItem.url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem.url()))
394         return documentSequenceNumber() == otherItem.documentSequenceNumber();
395     
396     return hasSameDocumentTree(otherItem);
397 }
398
399 // Does a recursive check that this item and its descendants have the same
400 // document sequence numbers as the other item.
401 bool HistoryItem::hasSameDocumentTree(HistoryItem& otherItem) const
402 {
403     if (documentSequenceNumber() != otherItem.documentSequenceNumber())
404         return false;
405         
406     if (children().size() != otherItem.children().size())
407         return false;
408
409     for (size_t i = 0; i < children().size(); i++) {
410         auto& child = children()[i].get();
411         auto* otherChild = otherItem.childItemWithDocumentSequenceNumber(child.documentSequenceNumber());
412         if (!otherChild || !child.hasSameDocumentTree(*otherChild))
413             return false;
414     }
415
416     return true;
417 }
418
419 // Does a non-recursive check that this item and its immediate children have the
420 // same frames as the other item.
421 bool HistoryItem::hasSameFrames(HistoryItem& otherItem) const
422 {
423     if (target() != otherItem.target())
424         return false;
425         
426     if (children().size() != otherItem.children().size())
427         return false;
428
429     for (size_t i = 0; i < children().size(); i++) {
430         if (!otherItem.childItemWithTarget(children()[i]->target()))
431             return false;
432     }
433
434     return true;
435 }
436
437 String HistoryItem::formContentType() const
438 {
439     return m_formContentType;
440 }
441
442 void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
443 {
444     m_referrer = request.httpReferrer();
445     
446     if (equalLettersIgnoringASCIICase(request.httpMethod(), "post")) {
447         // FIXME: Eventually we have to make this smart enough to handle the case where
448         // we have a stream for the body to handle the "data interspersed with files" feature.
449         m_formData = request.httpBody();
450         m_formContentType = request.httpContentType();
451     } else {
452         m_formData = nullptr;
453         m_formContentType = String();
454     }
455 }
456
457 void HistoryItem::setFormData(RefPtr<FormData>&& formData)
458 {
459     m_formData = WTFMove(formData);
460 }
461
462 void HistoryItem::setFormContentType(const String& formContentType)
463 {
464     m_formContentType = formContentType;
465 }
466
467 FormData* HistoryItem::formData()
468 {
469     return m_formData.get();
470 }
471
472 bool HistoryItem::isCurrentDocument(Document& document) const
473 {
474     // FIXME: We should find a better way to check if this is the current document.
475     return equalIgnoringFragmentIdentifier(url(), document.url());
476 }
477
478 void HistoryItem::notifyChanged()
479 {
480     notifyHistoryItemChanged(this);
481 }
482
483 #ifndef NDEBUG
484
485 int HistoryItem::showTree() const
486 {
487     return showTreeWithIndent(0);
488 }
489
490 int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
491 {
492     Vector<char> prefix;
493     for (unsigned i = 0; i < indentLevel; ++i)
494         prefix.append("  ", 2);
495     prefix.append("\0", 1);
496
497     WTFLogAlways("%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
498     
499     int totalSubItems = 0;
500     for (unsigned i = 0; i < m_children.size(); ++i)
501         totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
502     return totalSubItems + 1;
503 }
504
505 #endif
506                 
507 } // namespace WebCore
508
509 #ifndef NDEBUG
510
511 int showTree(const WebCore::HistoryItem* item)
512 {
513     return item->showTree();
514 }
515
516 #endif