1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "config.h"
6#include "core/dom/ChildFrameDisconnector.h"
7
8#include "core/dom/shadow/ElementShadow.h"
9#include "core/dom/shadow/ShadowRoot.h"
10#include "core/html/HTMLFrameOwnerElement.h"
11#include "wtf/Assertions.h"
12
13namespace WebCore {
14
15#ifndef NDEBUG
16static unsigned checkConnectedSubframeCountIsConsistent(Node&);
17#endif
18
19void ChildFrameDisconnector::disconnect(DisconnectPolicy policy)
20{
21#ifndef NDEBUG
22    checkConnectedSubframeCountIsConsistent(m_root);
23#endif
24
25    if (!m_root.connectedSubframeCount())
26        return;
27
28    if (policy == RootAndDescendants) {
29        collectFrameOwners(m_root);
30    } else {
31        for (Node* child = m_root.firstChild(); child; child = child->nextSibling())
32            collectFrameOwners(*child);
33    }
34
35    disconnectCollectedFrameOwners();
36}
37
38void ChildFrameDisconnector::collectFrameOwners(Node& root)
39{
40    if (!root.connectedSubframeCount())
41        return;
42
43    if (root.isHTMLElement() && root.isFrameOwnerElement())
44        m_frameOwners.append(&toHTMLFrameOwnerElement(root));
45
46    for (Node* child = root.firstChild(); child; child = child->nextSibling())
47        collectFrameOwners(*child);
48
49    ElementShadow* shadow = root.isElementNode() ? toElement(root).shadow() : 0;
50    if (shadow)
51        collectFrameOwners(*shadow);
52}
53
54void ChildFrameDisconnector::disconnectCollectedFrameOwners()
55{
56    // Must disable frame loading in the subtree so an unload handler cannot
57    // insert more frames and create loaded frames in detached subtrees.
58    SubframeLoadingDisabler disabler(m_root);
59
60    for (unsigned i = 0; i < m_frameOwners.size(); ++i) {
61        HTMLFrameOwnerElement* owner = m_frameOwners[i].get();
62        // Don't need to traverse up the tree for the first owner since no
63        // script could have moved it.
64        if (!i || m_root.containsIncludingShadowDOM(owner))
65            owner->disconnectContentFrame();
66    }
67}
68
69void ChildFrameDisconnector::collectFrameOwners(ElementShadow& shadow)
70{
71    for (ShadowRoot* root = shadow.youngestShadowRoot(); root; root = root->olderShadowRoot())
72        collectFrameOwners(*root);
73}
74
75#ifndef NDEBUG
76static unsigned checkConnectedSubframeCountIsConsistent(Node& node)
77{
78    unsigned count = 0;
79
80    if (node.isElementNode()) {
81        if (node.isFrameOwnerElement() && toHTMLFrameOwnerElement(node).contentFrame())
82            count++;
83
84        if (ElementShadow* shadow = toElement(node).shadow()) {
85            for (ShadowRoot* root = shadow->youngestShadowRoot(); root; root = root->olderShadowRoot())
86                count += checkConnectedSubframeCountIsConsistent(*root);
87        }
88    }
89
90    for (Node* child = node.firstChild(); child; child = child->nextSibling())
91        count += checkConnectedSubframeCountIsConsistent(*child);
92
93    // If we undercount there's possibly a security bug since we'd leave frames
94    // in subtrees outside the document.
95    ASSERT(node.connectedSubframeCount() >= count);
96
97    // If we overcount it's safe, but not optimal because it means we'll traverse
98    // through the document in ChildFrameDisconnector looking for frames that have
99    // already been disconnected.
100    ASSERT(node.connectedSubframeCount() == count);
101
102    return count;
103}
104#endif
105
106}
107