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
42namespace WebCore {
43
44static 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
52static void defaultNotifyHistoryItemChanged(HistoryItem*)
53{
54}
55
56void (*notifyHistoryItemChanged)(HistoryItem*) = defaultNotifyHistoryItemChanged;
57
58HistoryItem::HistoryItem()
59    : m_pageScaleFactor(0)
60    , m_lastVisitWasFailure(false)
61    , m_isTargetItem(false)
62    , m_itemSequenceNumber(generateSequenceNumber())
63    , m_documentSequenceNumber(generateSequenceNumber())
64    , m_next(0)
65    , m_prev(0)
66#if PLATFORM(IOS)
67    , m_scale(0)
68    , m_scaleIsInitial(false)
69    , m_bookmarkID(0)
70#endif
71{
72}
73
74HistoryItem::HistoryItem(const String& urlString, const String& title)
75    : m_urlString(urlString)
76    , m_originalURLString(urlString)
77    , m_title(title)
78    , m_pageScaleFactor(0)
79    , m_lastVisitWasFailure(false)
80    , m_isTargetItem(false)
81    , m_itemSequenceNumber(generateSequenceNumber())
82    , m_documentSequenceNumber(generateSequenceNumber())
83    , m_next(0)
84    , m_prev(0)
85#if PLATFORM(IOS)
86    , m_scale(0)
87    , m_scaleIsInitial(false)
88    , m_bookmarkID(0)
89#endif
90{
91    iconDatabase().retainIconForPageURL(m_urlString);
92}
93
94HistoryItem::HistoryItem(const String& urlString, const String& title, const String& alternateTitle)
95    : m_urlString(urlString)
96    , m_originalURLString(urlString)
97    , m_title(title)
98    , m_displayTitle(alternateTitle)
99    , m_pageScaleFactor(0)
100    , m_lastVisitWasFailure(false)
101    , m_isTargetItem(false)
102    , m_itemSequenceNumber(generateSequenceNumber())
103    , m_documentSequenceNumber(generateSequenceNumber())
104    , m_next(0)
105    , m_prev(0)
106#if PLATFORM(IOS)
107    , m_scale(0)
108    , m_scaleIsInitial(false)
109    , m_bookmarkID(0)
110#endif
111{
112    iconDatabase().retainIconForPageURL(m_urlString);
113}
114
115HistoryItem::HistoryItem(const URL& url, const String& target, const String& parent, const String& title)
116    : m_urlString(url.string())
117    , m_originalURLString(url.string())
118    , m_target(target)
119    , m_parent(parent)
120    , m_title(title)
121    , m_pageScaleFactor(0)
122    , m_lastVisitWasFailure(false)
123    , m_isTargetItem(false)
124    , m_itemSequenceNumber(generateSequenceNumber())
125    , m_documentSequenceNumber(generateSequenceNumber())
126    , m_next(0)
127    , m_prev(0)
128#if PLATFORM(IOS)
129    , m_scale(0)
130    , m_scaleIsInitial(false)
131    , m_bookmarkID(0)
132#endif
133{
134    iconDatabase().retainIconForPageURL(m_urlString);
135}
136
137HistoryItem::~HistoryItem()
138{
139    ASSERT(!m_cachedPage);
140    iconDatabase().releaseIconForPageURL(m_urlString);
141}
142
143inline HistoryItem::HistoryItem(const HistoryItem& item)
144    : RefCounted<HistoryItem>()
145    , m_urlString(item.m_urlString)
146    , m_originalURLString(item.m_originalURLString)
147    , m_referrer(item.m_referrer)
148    , m_target(item.m_target)
149    , m_parent(item.m_parent)
150    , m_title(item.m_title)
151    , m_displayTitle(item.m_displayTitle)
152    , m_scrollPoint(item.m_scrollPoint)
153    , m_pageScaleFactor(item.m_pageScaleFactor)
154    , m_lastVisitWasFailure(item.m_lastVisitWasFailure)
155    , m_isTargetItem(item.m_isTargetItem)
156    , m_itemSequenceNumber(item.m_itemSequenceNumber)
157    , m_documentSequenceNumber(item.m_documentSequenceNumber)
158    , m_formContentType(item.m_formContentType)
159#if PLATFORM(IOS)
160    , m_scale(item.m_scale)
161    , m_scaleIsInitial(item.m_scaleIsInitial)
162    , m_bookmarkID(item.m_bookmarkID)
163    , m_sharedLinkUniqueIdentifier(item.m_sharedLinkUniqueIdentifier)
164#endif
165{
166    if (item.m_formData)
167        m_formData = item.m_formData->copy();
168
169    unsigned size = item.m_children.size();
170    m_children.reserveInitialCapacity(size);
171    for (unsigned i = 0; i < size; ++i)
172        m_children.uncheckedAppend(item.m_children[i]->copy());
173
174    if (item.m_redirectURLs)
175        m_redirectURLs = std::make_unique<Vector<String>>(*item.m_redirectURLs);
176}
177
178PassRefPtr<HistoryItem> HistoryItem::copy() const
179{
180    return adoptRef(new HistoryItem(*this));
181}
182
183void HistoryItem::reset()
184{
185    iconDatabase().releaseIconForPageURL(m_urlString);
186
187    m_urlString = String();
188    m_originalURLString = String();
189    m_referrer = String();
190    m_target = String();
191    m_parent = String();
192    m_title = String();
193    m_displayTitle = String();
194
195    m_lastVisitWasFailure = false;
196    m_isTargetItem = false;
197
198    m_redirectURLs = nullptr;
199
200    m_itemSequenceNumber = generateSequenceNumber();
201
202    m_stateObject = 0;
203    m_documentSequenceNumber = generateSequenceNumber();
204
205    m_formData = 0;
206    m_formContentType = String();
207
208    clearChildren();
209}
210
211const String& HistoryItem::urlString() const
212{
213    return m_urlString;
214}
215
216// The first URL we loaded to get to where this history item points.  Includes both client
217// and server redirects.
218const String& HistoryItem::originalURLString() const
219{
220    return m_originalURLString;
221}
222
223const String& HistoryItem::title() const
224{
225    return m_title;
226}
227
228const String& HistoryItem::alternateTitle() const
229{
230    return m_displayTitle;
231}
232
233bool HistoryItem::hasCachedPageExpired() const
234{
235    return m_cachedPage ? m_cachedPage->hasExpired() : false;
236}
237
238URL HistoryItem::url() const
239{
240    return URL(ParsedURLString, m_urlString);
241}
242
243URL HistoryItem::originalURL() const
244{
245    return URL(ParsedURLString, m_originalURLString);
246}
247
248const String& HistoryItem::referrer() const
249{
250    return m_referrer;
251}
252
253const String& HistoryItem::target() const
254{
255    return m_target;
256}
257
258const String& HistoryItem::parent() const
259{
260    return m_parent;
261}
262
263void HistoryItem::setAlternateTitle(const String& alternateTitle)
264{
265    m_displayTitle = alternateTitle;
266    notifyHistoryItemChanged(this);
267}
268
269void HistoryItem::setURLString(const String& urlString)
270{
271    if (m_urlString != urlString) {
272        iconDatabase().releaseIconForPageURL(m_urlString);
273        m_urlString = urlString;
274        iconDatabase().retainIconForPageURL(m_urlString);
275    }
276
277    notifyHistoryItemChanged(this);
278}
279
280void HistoryItem::setURL(const URL& url)
281{
282    pageCache()->remove(this);
283    setURLString(url.string());
284    clearDocumentState();
285}
286
287void HistoryItem::setOriginalURLString(const String& urlString)
288{
289    m_originalURLString = urlString;
290    notifyHistoryItemChanged(this);
291}
292
293void HistoryItem::setReferrer(const String& referrer)
294{
295    m_referrer = referrer;
296    notifyHistoryItemChanged(this);
297}
298
299void HistoryItem::setTitle(const String& title)
300{
301    m_title = title;
302    notifyHistoryItemChanged(this);
303}
304
305void HistoryItem::setTarget(const String& target)
306{
307    m_target = target;
308    notifyHistoryItemChanged(this);
309}
310
311void HistoryItem::setParent(const String& parent)
312{
313    m_parent = parent;
314}
315
316const IntPoint& HistoryItem::scrollPoint() const
317{
318    return m_scrollPoint;
319}
320
321void HistoryItem::setScrollPoint(const IntPoint& point)
322{
323    m_scrollPoint = point;
324}
325
326void HistoryItem::clearScrollPoint()
327{
328    m_scrollPoint.setX(0);
329    m_scrollPoint.setY(0);
330}
331
332float HistoryItem::pageScaleFactor() const
333{
334    return m_pageScaleFactor;
335}
336
337void HistoryItem::setPageScaleFactor(float scaleFactor)
338{
339    m_pageScaleFactor = scaleFactor;
340}
341
342void HistoryItem::setDocumentState(const Vector<String>& state)
343{
344    m_documentState = state;
345}
346
347const Vector<String>& HistoryItem::documentState() const
348{
349    return m_documentState;
350}
351
352void HistoryItem::clearDocumentState()
353{
354    m_documentState.clear();
355}
356
357bool HistoryItem::isTargetItem() const
358{
359    return m_isTargetItem;
360}
361
362void HistoryItem::setIsTargetItem(bool flag)
363{
364    m_isTargetItem = flag;
365}
366
367void HistoryItem::setStateObject(PassRefPtr<SerializedScriptValue> object)
368{
369    m_stateObject = object;
370}
371
372void HistoryItem::addChildItem(PassRefPtr<HistoryItem> child)
373{
374    ASSERT(!childItemWithTarget(child->target()));
375    m_children.append(child);
376}
377
378void HistoryItem::setChildItem(PassRefPtr<HistoryItem> child)
379{
380    ASSERT(!child->isTargetItem());
381    unsigned size = m_children.size();
382    for (unsigned i = 0; i < size; ++i)  {
383        if (m_children[i]->target() == child->target()) {
384            child->setIsTargetItem(m_children[i]->isTargetItem());
385            m_children[i] = child;
386            return;
387        }
388    }
389    m_children.append(child);
390}
391
392HistoryItem* HistoryItem::childItemWithTarget(const String& target) const
393{
394    unsigned size = m_children.size();
395    for (unsigned i = 0; i < size; ++i) {
396        if (m_children[i]->target() == target)
397            return m_children[i].get();
398    }
399    return 0;
400}
401
402HistoryItem* HistoryItem::childItemWithDocumentSequenceNumber(long long number) const
403{
404    unsigned size = m_children.size();
405    for (unsigned i = 0; i < size; ++i) {
406        if (m_children[i]->documentSequenceNumber() == number)
407            return m_children[i].get();
408    }
409    return 0;
410}
411
412// <rdar://problem/4895849> HistoryItem::findTargetItem() should be replaced with a non-recursive method.
413HistoryItem* HistoryItem::findTargetItem()
414{
415    if (m_isTargetItem)
416        return this;
417    unsigned size = m_children.size();
418    for (unsigned i = 0; i < size; ++i) {
419        if (HistoryItem* match = m_children[i]->targetItem())
420            return match;
421    }
422    return 0;
423}
424
425HistoryItem* HistoryItem::targetItem()
426{
427    HistoryItem* foundItem = findTargetItem();
428    return foundItem ? foundItem : this;
429}
430
431const HistoryItemVector& HistoryItem::children() const
432{
433    return m_children;
434}
435
436bool HistoryItem::hasChildren() const
437{
438    return !m_children.isEmpty();
439}
440
441void HistoryItem::clearChildren()
442{
443    m_children.clear();
444}
445
446bool HistoryItem::isAncestorOf(const HistoryItem* item) const
447{
448    for (size_t i = 0; i < m_children.size(); ++i) {
449        HistoryItem* child = m_children[i].get();
450        if (child == item)
451            return true;
452        if (child->isAncestorOf(item))
453            return true;
454    }
455    return false;
456}
457
458// We do same-document navigation if going to a different item and if either of the following is true:
459// - The other item corresponds to the same document (for history entries created via pushState or fragment changes).
460// - The other item corresponds to the same set of documents, including frames (for history entries created via regular navigation)
461bool HistoryItem::shouldDoSameDocumentNavigationTo(HistoryItem* otherItem) const
462{
463    if (this == otherItem)
464        return false;
465
466    if (stateObject() || otherItem->stateObject())
467        return documentSequenceNumber() == otherItem->documentSequenceNumber();
468
469    if ((url().hasFragmentIdentifier() || otherItem->url().hasFragmentIdentifier()) && equalIgnoringFragmentIdentifier(url(), otherItem->url()))
470        return documentSequenceNumber() == otherItem->documentSequenceNumber();
471
472    return hasSameDocumentTree(otherItem);
473}
474
475// Does a recursive check that this item and its descendants have the same
476// document sequence numbers as the other item.
477bool HistoryItem::hasSameDocumentTree(HistoryItem* otherItem) const
478{
479    if (documentSequenceNumber() != otherItem->documentSequenceNumber())
480        return false;
481
482    if (children().size() != otherItem->children().size())
483        return false;
484
485    for (size_t i = 0; i < children().size(); i++) {
486        HistoryItem* child = children()[i].get();
487        HistoryItem* otherChild = otherItem->childItemWithDocumentSequenceNumber(child->documentSequenceNumber());
488        if (!otherChild || !child->hasSameDocumentTree(otherChild))
489            return false;
490    }
491
492    return true;
493}
494
495// Does a non-recursive check that this item and its immediate children have the
496// same frames as the other item.
497bool HistoryItem::hasSameFrames(HistoryItem* otherItem) const
498{
499    if (target() != otherItem->target())
500        return false;
501
502    if (children().size() != otherItem->children().size())
503        return false;
504
505    for (size_t i = 0; i < children().size(); i++) {
506        if (!otherItem->childItemWithTarget(children()[i]->target()))
507            return false;
508    }
509
510    return true;
511}
512
513String HistoryItem::formContentType() const
514{
515    return m_formContentType;
516}
517
518void HistoryItem::setFormInfoFromRequest(const ResourceRequest& request)
519{
520    m_referrer = request.httpReferrer();
521
522    if (equalIgnoringCase(request.httpMethod(), "POST")) {
523        // FIXME: Eventually we have to make this smart enough to handle the case where
524        // we have a stream for the body to handle the "data interspersed with files" feature.
525        m_formData = request.httpBody();
526        m_formContentType = request.httpContentType();
527    } else {
528        m_formData = 0;
529        m_formContentType = String();
530    }
531}
532
533void HistoryItem::setFormData(PassRefPtr<FormData> formData)
534{
535    m_formData = formData;
536}
537
538void HistoryItem::setFormContentType(const String& formContentType)
539{
540    m_formContentType = formContentType;
541}
542
543FormData* HistoryItem::formData()
544{
545    return m_formData.get();
546}
547
548bool HistoryItem::isCurrentDocument(Document* doc) const
549{
550    // FIXME: We should find a better way to check if this is the current document.
551    return equalIgnoringFragmentIdentifier(url(), doc->url());
552}
553
554void HistoryItem::addRedirectURL(const String& url)
555{
556    if (!m_redirectURLs)
557        m_redirectURLs = std::make_unique<Vector<String>>();
558
559    // Our API allows us to store all the URLs in the redirect chain, but for
560    // now we only have a use for the final URL.
561    (*m_redirectURLs).resize(1);
562    (*m_redirectURLs)[0] = url;
563}
564
565Vector<String>* HistoryItem::redirectURLs() const
566{
567    return m_redirectURLs.get();
568}
569
570void HistoryItem::setRedirectURLs(std::unique_ptr<Vector<String>> redirectURLs)
571{
572    m_redirectURLs = WTF::move(redirectURLs);
573}
574
575#ifndef NDEBUG
576
577int HistoryItem::showTree() const
578{
579    return showTreeWithIndent(0);
580}
581
582int HistoryItem::showTreeWithIndent(unsigned indentLevel) const
583{
584    Vector<char> prefix;
585    for (unsigned i = 0; i < indentLevel; ++i)
586        prefix.append("  ", 2);
587    prefix.append("\0", 1);
588
589    fprintf(stderr, "%s+-%s (%p)\n", prefix.data(), m_urlString.utf8().data(), this);
590
591    int totalSubItems = 0;
592    for (unsigned i = 0; i < m_children.size(); ++i)
593        totalSubItems += m_children[i]->showTreeWithIndent(indentLevel + 1);
594    return totalSubItems + 1;
595}
596
597#endif
598
599} // namespace WebCore
600
601#ifndef NDEBUG
602
603int showTree(const WebCore::HistoryItem* item)
604{
605    return item->showTree();
606}
607
608#endif
609