Changeset 207068 in webkit


Ignore:
Timestamp:
Oct 11, 2016 1:48:26 AM (8 years ago)
Author:
Carlos Garcia Campos
Message:

Merge r205786 - 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:
releases/WebKitGTK/webkit-2.14
Files:
2 added
9 edited

Legend:

Unmodified
Added
Removed
  • releases/WebKitGTK/webkit-2.14/LayoutTests/ChangeLog

    r207056 r207068  
     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-08  Simon Fraser  <simon.fraser@apple.com>
    216
  • releases/WebKitGTK/webkit-2.14/Source/WebCore/ChangeLog

    r207063 r207068  
     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-09  Antti Koivisto  <antti@apple.com>
    269
  • releases/WebKitGTK/webkit-2.14/Source/WebCore/dom/Document.cpp

    r205644 r207068  
    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);
     
    22492248{
    22502249    ASSERT(!renderView());
    2251     ASSERT(!m_inPageCache);
     2250    ASSERT(m_pageCacheState != InPageCache);
    22522251    ASSERT(!m_axObjectCache || this != &topDocument());
    22532252
     
    23132312{
    23142313    ASSERT(hasLivingRenderTree());
    2315     ASSERT(!m_inPageCache);
     2314    ASSERT(m_pageCacheState != InPageCache);
    23162315
    23172316    TemporaryChange<bool> change(m_renderTreeBeingDestroyed, true);
     
    37993798        return true;
    38003799
    3801     if (m_inPageCache)
     3800    if (inPageCache())
    38023801        return false;
    38033802
     
    47274726}
    47284727
    4729 void Document::setInPageCache(bool flag)
    4730 {
    4731     if (m_inPageCache == flag)
    4732         return;
    4733 
    4734     m_inPageCache = flag;
     4728void Document::setPageCacheState(PageCacheState state)
     4729{
     4730    if (m_pageCacheState == state)
     4731        return;
     4732
     4733    m_pageCacheState = state;
    47354734
    47364735    FrameView* v = view();
    47374736    Page* page = this->page();
    47384737
    4739     if (flag) {
     4738    switch (state) {
     4739    case InPageCache:
    47404740        if (v) {
    47414741            // FIXME: There is some scrolling related work that needs to happen whenever a page goes into the
     
    47574757
    47584758        clearSharedObjectPool();
    4759     } else {
     4759        break;
     4760    case NotInPageCache:
    47604761        if (childNeedsStyleRecalc())
    47614762            scheduleStyleRecalc();
     4763        break;
     4764    case AboutToEnterPageCache:
     4765        break;
    47624766    }
    47634767}
     
    50785082    // FIXME: This special-casing avoids incorrectly determined top documents during the process
    50795083    // of AXObjectCache teardown or notification posting for cached or being-destroyed documents.
    5080     if (!m_inPageCache && !m_renderTreeBeingDestroyed) {
     5084    if (!inPageCache() && !m_renderTreeBeingDestroyed) {
    50815085        if (!m_frame)
    50825086            return const_cast<Document&>(*this);
  • releases/WebKitGTK/webkit-2.14/Source/WebCore/dom/Document.h

    r205613 r207068  
    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;
  • releases/WebKitGTK/webkit-2.14/Source/WebCore/history/CachedFrame.cpp

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

    r201655 r207068  
    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.
  • releases/WebKitGTK/webkit-2.14/Source/WebCore/loader/FrameLoader.cpp

    r205644 r207068  
    15981598void FrameLoader::stopAllLoaders(ClearProvisionalItemPolicy clearProvisionalItemPolicy)
    15991599{
    1600     ASSERT(!m_frame.document() || !m_frame.document()->inPageCache());
     1600    ASSERT(!m_frame.document() || m_frame.document()->pageCacheState() != Document::InPageCache);
    16011601    if (m_pageDismissalEventBeingDispatched != PageDismissalType::None)
    16021602        return;
     
    20962096    clear(document, true, true, cachedFrame.isMainFrame());
    20972097
    2098     document->setInPageCache(false);
     2098    document->setPageCacheState(Document::NotInPageCache);
    20992099
    21002100    m_needsClear = true;
  • releases/WebKitGTK/webkit-2.14/Source/WebCore/loader/HistoryController.cpp

    r200770 r207068  
    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    }
  • releases/WebKitGTK/webkit-2.14/Source/WebCore/page/Frame.cpp

    r205717 r207068  
    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.