Changeset 205786 in webkit


Ignore:
Timestamp:
Sep 10, 2016 9:06:05 AM (8 years ago)
Author:
Chris Dumez
Message:

It is possible for Document::m_frame pointer to become stale
https://bugs.webkit.org/show_bug.cgi?id=161812
<rdar://problem/27745023>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Document::m_frame is supposed to get cleared by Document::prepareForDestruction().
The Frame destructor calls Frame::setView(nullptr) which is supposed to call the
prepareForDestruction() on the Frame's associated document. However,
Frame::setView(nullptr) was calling prepareForDestruction() only if
Document::inPageCache() returned true. This is because, we allow Documents to
stay alive in the PageCache even though they don't have a frame.

The issue is that Document::m_inPageCache flag was set to true right before
firing the pagehide event, so technically before really entering PageCache.
Therefore, we can run into problems if a Frame gets destroyed by a pagehide
EventHandler because ~Frame() will not call Document::prepareForDestruction()
due to Document::m_inPageCache being true. After the frame is destroyed,
Document::m_frame becomes stale and any action on the document will likely
lead to crashes (such as the one in the layout test and the radar which
happens when trying to unregister event listeners from the document).

The solution adopted in this patch is to replace the m_inPageCache boolean
with a m_pageCacheState enumeration that has 3 states:

  • NotInPageCache
  • AboutToEnterPageCache
  • InPageCache

Frame::setView() / Frame::setDocument() were then updated to call
Document::prepareForDestruction() on the associated document whenever
the document's pageCacheState is not InPageCache. This means that we
will now call Document::prepareForDestruction() when the document is
being detached from its frame while firing the pagehide event.

Note that I tried to keep this patch minimal. Therefore, I kept
the Document::inPageCache() getter for now. I plan to switch all its
calls sites to the new Document::pageCacheState() getter in a follow-up
patch so that we can finally drop the confusing Document::inPageCache().

Test: fast/history/pagehide-remove-iframe-crash.html

  • dom/Document.cpp:

(WebCore::Document::Document):
(WebCore::Document::~Document):
(WebCore::Document::createRenderTree):
(WebCore::Document::destroyRenderTree):
(WebCore::Document::setFocusedElement):
(WebCore::Document::setPageCacheState):
(WebCore::Document::topDocument):

  • dom/Document.h:

(WebCore::Document::pageCacheState):
(WebCore::Document::inPageCache):

  • history/CachedFrame.cpp:

(WebCore::CachedFrame::destroy):

  • history/PageCache.cpp:

(WebCore::setPageCacheState):
(WebCore::PageCache::addIfCacheable):

  • loader/FrameLoader.cpp:

(WebCore::FrameLoader::stopAllLoaders):
(WebCore::FrameLoader::open):

  • loader/HistoryController.cpp:

(WebCore::HistoryController::invalidateCurrentItemCachedPage):

  • page/Frame.cpp:

(WebCore::Frame::setView):

LayoutTests:

Add layout test that crashes on both Mac and iOS due to using a stale
Document::m_frame pointer.

  • fast/history/pagehide-remove-iframe-crash-expected.txt: Added.
  • fast/history/pagehide-remove-iframe-crash.html: Added.
Location:
trunk
Files:
2 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r205785 r205786  
     12016-09-10  Chris Dumez  <cdumez@apple.com>
     2
     3        It is possible for Document::m_frame pointer to become stale
     4        https://bugs.webkit.org/show_bug.cgi?id=161812
     5        <rdar://problem/27745023>
     6
     7        Reviewed by Ryosuke Niwa.
     8
     9        Add layout test that crashes on both Mac and iOS due to using a stale
     10        Document::m_frame pointer.
     11
     12        * fast/history/pagehide-remove-iframe-crash-expected.txt: Added.
     13        * fast/history/pagehide-remove-iframe-crash.html: Added.
     14
    1152016-09-10  Gyuyoung Kim  <gyuyoung.kim@webkit.org>
    216
  • trunk/Source/WebCore/ChangeLog

    r205784 r205786  
     12016-09-10  Chris Dumez  <cdumez@apple.com>
     2
     3        It is possible for Document::m_frame pointer to become stale
     4        https://bugs.webkit.org/show_bug.cgi?id=161812
     5        <rdar://problem/27745023>
     6
     7        Reviewed by Ryosuke Niwa.
     8
     9        Document::m_frame is supposed to get cleared by Document::prepareForDestruction().
     10        The Frame destructor calls Frame::setView(nullptr) which is supposed to call the
     11        prepareForDestruction() on the Frame's associated document. However,
     12        Frame::setView(nullptr) was calling prepareForDestruction() only if
     13        Document::inPageCache() returned true. This is because, we allow Documents to
     14        stay alive in the PageCache even though they don't have a frame.
     15
     16        The issue is that Document::m_inPageCache flag was set to true right before
     17        firing the pagehide event, so technically before really entering PageCache.
     18        Therefore, we can run into problems if a Frame gets destroyed by a pagehide
     19        EventHandler because ~Frame() will not call Document::prepareForDestruction()
     20        due to Document::m_inPageCache being true. After the frame is destroyed,
     21        Document::m_frame becomes stale and any action on the document will likely
     22        lead to crashes (such as the one in the layout test and the radar which
     23        happens when trying to unregister event listeners from the document).
     24
     25        The solution adopted in this patch is to replace the m_inPageCache boolean
     26        with a m_pageCacheState enumeration that has 3 states:
     27        - NotInPageCache
     28        - AboutToEnterPageCache
     29        - InPageCache
     30
     31        Frame::setView() / Frame::setDocument() were then updated to call
     32        Document::prepareForDestruction() on the associated document whenever
     33        the document's pageCacheState is not InPageCache. This means that we
     34        will now call Document::prepareForDestruction() when the document is
     35        being detached from its frame while firing the pagehide event.
     36
     37        Note that I tried to keep this patch minimal. Therefore, I kept
     38        the Document::inPageCache() getter for now. I plan to switch all its
     39        calls sites to the new Document::pageCacheState() getter in a follow-up
     40        patch so that we can finally drop the confusing Document::inPageCache().
     41
     42        Test: fast/history/pagehide-remove-iframe-crash.html
     43
     44        * dom/Document.cpp:
     45        (WebCore::Document::Document):
     46        (WebCore::Document::~Document):
     47        (WebCore::Document::createRenderTree):
     48        (WebCore::Document::destroyRenderTree):
     49        (WebCore::Document::setFocusedElement):
     50        (WebCore::Document::setPageCacheState):
     51        (WebCore::Document::topDocument):
     52        * dom/Document.h:
     53        (WebCore::Document::pageCacheState):
     54        (WebCore::Document::inPageCache):
     55        * history/CachedFrame.cpp:
     56        (WebCore::CachedFrame::destroy):
     57        * history/PageCache.cpp:
     58        (WebCore::setPageCacheState):
     59        (WebCore::PageCache::addIfCacheable):
     60        * loader/FrameLoader.cpp:
     61        (WebCore::FrameLoader::stopAllLoaders):
     62        (WebCore::FrameLoader::open):
     63        * loader/HistoryController.cpp:
     64        (WebCore::HistoryController::invalidateCurrentItemCachedPage):
     65        * page/Frame.cpp:
     66        (WebCore::Frame::setView):
     67
    1682016-09-10  Wenson Hsieh  <wenson_hsieh@apple.com>
    269
  • trunk/Source/WebCore/dom/Document.cpp

    r205468 r205786  
    494494#endif
    495495    , m_createRenderers(true)
    496     , m_inPageCache(false)
    497496    , m_accessKeyMapValid(false)
    498497    , m_documentClasses(documentClasses)
     
    603602
    604603    ASSERT(!renderView());
    605     ASSERT(!m_inPageCache);
     604    ASSERT(m_pageCacheState != InPageCache);
    606605    ASSERT(m_ranges.isEmpty());
    607606    ASSERT(!m_parentTreeScope);
     
    22382237{
    22392238    ASSERT(!renderView());
    2240     ASSERT(!m_inPageCache);
     2239    ASSERT(m_pageCacheState != InPageCache);
    22412240    ASSERT(!m_axObjectCache || this != &topDocument());
    22422241
     
    23022301{
    23032302    ASSERT(hasLivingRenderTree());
    2304     ASSERT(!m_inPageCache);
     2303    ASSERT(m_pageCacheState != InPageCache);
    23052304
    23062305    TemporaryChange<bool> change(m_renderTreeBeingDestroyed, true);
     
    37883787        return true;
    37893788
    3790     if (m_inPageCache)
     3789    if (inPageCache())
    37913790        return false;
    37923791
     
    47164715}
    47174716
    4718 void Document::setInPageCache(bool flag)
    4719 {
    4720     if (m_inPageCache == flag)
    4721         return;
    4722 
    4723     m_inPageCache = flag;
     4717void Document::setPageCacheState(PageCacheState state)
     4718{
     4719    if (m_pageCacheState == state)
     4720        return;
     4721
     4722    m_pageCacheState = state;
    47244723
    47254724    FrameView* v = view();
    47264725    Page* page = this->page();
    47274726
    4728     if (flag) {
     4727    switch (state) {
     4728    case InPageCache:
    47294729        if (v) {
    47304730            // FIXME: There is some scrolling related work that needs to happen whenever a page goes into the
     
    47464746
    47474747        clearSharedObjectPool();
    4748     } else {
     4748        break;
     4749    case NotInPageCache:
    47494750        if (childNeedsStyleRecalc())
    47504751            scheduleStyleRecalc();
     4752        break;
     4753    case AboutToEnterPageCache:
     4754        break;
    47514755    }
    47524756}
     
    50675071    // FIXME: This special-casing avoids incorrectly determined top documents during the process
    50685072    // of AXObjectCache teardown or notification posting for cached or being-destroyed documents.
    5069     if (!m_inPageCache && !m_renderTreeBeingDestroyed) {
     5073    if (!inPageCache() && !m_renderTreeBeingDestroyed) {
    50705074        if (!m_frame)
    50715075            return const_cast<Document&>(*this);
  • trunk/Source/WebCore/dom/Document.h

    r205468 r205786  
    10021002    void finishedParsing();
    10031003
    1004     bool inPageCache() const { return m_inPageCache; }
    1005     void setInPageCache(bool flag);
     1004    enum PageCacheState { NotInPageCache, AboutToEnterPageCache, InPageCache };
     1005
     1006    PageCacheState pageCacheState() const { return m_pageCacheState; }
     1007    void setPageCacheState(PageCacheState);
     1008
     1009    // FIXME: Update callers to use pageCacheState() instead.
     1010    bool inPageCache() const { return m_pageCacheState != NotInPageCache; }
    10061011
    10071012    // Elements can register themselves for the "suspend()" and
     
    15941599
    15951600    bool m_createRenderers;
    1596     bool m_inPageCache;
     1601    PageCacheState m_pageCacheState { NotInPageCache };
    15971602
    15981603    HashSet<Element*> m_documentSuspensionCallbackElements;
  • trunk/Source/WebCore/history/CachedFrame.cpp

    r201310 r205786  
    265265    m_document->removeAllEventListeners();
    266266
    267     m_document->setInPageCache(false);
     267    m_document->setPageCacheState(Document::NotInPageCache);
    268268    m_document->prepareForDestruction();
    269269
  • trunk/Source/WebCore/history/PageCache.cpp

    r201655 r205786  
    374374}
    375375
    376 static void setInPageCache(Page& page, bool isInPageCache)
     376static void setPageCacheState(Page& page, Document::PageCacheState pageCacheState)
    377377{
    378378    for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
    379379        if (auto* document = frame->document())
    380             document->setInPageCache(isInPageCache);
     380            document->setPageCacheState(pageCacheState);
    381381    }
    382382}
     
    408408        return;
    409409
    410     // Make sure all the documents know they are being added to the PageCache.
    411     setInPageCache(*page, true);
     410    setPageCacheState(*page, Document::AboutToEnterPageCache);
    412411
    413412    // Focus the main frame, defocusing a focused subframe (if we have one). We do this here,
     
    422421    // could have altered the page in a way that could prevent caching.
    423422    if (!canCache(*page)) {
    424         setInPageCache(*page, false);
    425         return;
    426     }
     423        setPageCacheState(*page, Document::NotInPageCache);
     424        return;
     425    }
     426
     427    setPageCacheState(*page, Document::InPageCache);
    427428
    428429    // Make sure we no longer fire any JS events past this point.
  • trunk/Source/WebCore/loader/FrameLoader.cpp

    r205411 r205786  
    15971597void FrameLoader::stopAllLoaders(ClearProvisionalItemPolicy clearProvisionalItemPolicy)
    15981598{
    1599     ASSERT(!m_frame.document() || !m_frame.document()->inPageCache());
     1599    ASSERT(!m_frame.document() || m_frame.document()->pageCacheState() != Document::InPageCache);
    16001600    if (m_pageDismissalEventBeingDispatched != PageDismissalType::None)
    16011601        return;
     
    20952095    clear(document, true, true, cachedFrame.isMainFrame());
    20962096
    2097     document->setInPageCache(false);
     2097    document->setPageCacheState(Document::NotInPageCache);
    20982098
    20992099    m_needsClear = true;
  • trunk/Source/WebCore/loader/HistoryController.cpp

    r200770 r205786  
    271271    ASSERT(cachedPage->document() == m_frame.document());
    272272    if (cachedPage->document() == m_frame.document()) {
    273         cachedPage->document()->setInPageCache(false);
     273        cachedPage->document()->setPageCacheState(Document::NotInPageCache);
    274274        cachedPage->clear();
    275275    }
  • trunk/Source/WebCore/page/Frame.cpp

    r205462 r205786  
    247247    // notified. If we wait until the view is destroyed, then things won't be hooked up enough for
    248248    // these calls to work.
    249     if (!view && m_doc && !m_doc->inPageCache())
     249    if (!view && m_doc && m_doc->pageCacheState() != Document::InPageCache)
    250250        m_doc->prepareForDestruction();
    251251   
     
    269269    ASSERT(!newDocument || newDocument->frame() == this);
    270270
    271     if (m_doc && !m_doc->inPageCache())
     271    if (m_doc && m_doc->pageCacheState() != Document::InPageCache)
    272272        m_doc->prepareForDestruction();
    273273
Note: See TracChangeset for help on using the changeset viewer.