1/*
2 * Copyright (C) 2006, 2007, 2009 Apple Inc. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 */
20
21#include "config.h"
22#include "core/html/HTMLFrameOwnerElement.h"
23
24#include "bindings/v8/ExceptionMessages.h"
25#include "bindings/v8/ExceptionState.h"
26#include "core/accessibility/AXObjectCache.h"
27#include "core/dom/ExceptionCode.h"
28#include "core/events/Event.h"
29#include "core/frame/FrameView.h"
30#include "core/frame/LocalFrame.h"
31#include "core/loader/FrameLoader.h"
32#include "core/loader/FrameLoaderClient.h"
33#include "core/rendering/RenderLayer.h"
34#include "core/rendering/RenderPart.h"
35#include "core/rendering/compositing/RenderLayerCompositor.h"
36#include "platform/weborigin/SecurityOrigin.h"
37#include "platform/weborigin/SecurityPolicy.h"
38
39namespace WebCore {
40
41typedef HashMap<RefPtr<Widget>, FrameView*> WidgetToParentMap;
42static WidgetToParentMap& widgetNewParentMap()
43{
44    DEFINE_STATIC_LOCAL(WidgetToParentMap, map, ());
45    return map;
46}
47
48static unsigned s_updateSuspendCount = 0;
49
50HTMLFrameOwnerElement::UpdateSuspendScope::UpdateSuspendScope()
51{
52    ++s_updateSuspendCount;
53}
54
55void HTMLFrameOwnerElement::UpdateSuspendScope::performDeferredWidgetTreeOperations()
56{
57    WidgetToParentMap map;
58    widgetNewParentMap().swap(map);
59    WidgetToParentMap::iterator end = map.end();
60    for (WidgetToParentMap::iterator it = map.begin(); it != end; ++it) {
61        Widget* child = it->key.get();
62        ScrollView* currentParent = toScrollView(child->parent());
63        FrameView* newParent = it->value;
64        if (newParent != currentParent) {
65            if (currentParent)
66                currentParent->removeChild(child);
67            if (newParent)
68                newParent->addChild(child);
69        }
70    }
71}
72
73HTMLFrameOwnerElement::UpdateSuspendScope::~UpdateSuspendScope()
74{
75    ASSERT(s_updateSuspendCount > 0);
76    if (s_updateSuspendCount == 1)
77        performDeferredWidgetTreeOperations();
78    --s_updateSuspendCount;
79}
80
81static void moveWidgetToParentSoon(Widget* child, FrameView* parent)
82{
83    if (!s_updateSuspendCount) {
84        if (parent)
85            parent->addChild(child);
86        else if (toScrollView(child->parent()))
87            toScrollView(child->parent())->removeChild(child);
88        return;
89    }
90    widgetNewParentMap().set(child, parent);
91}
92
93HTMLFrameOwnerElement::HTMLFrameOwnerElement(const QualifiedName& tagName, Document& document)
94    : HTMLElement(tagName, document)
95    , m_contentFrame(0)
96    , m_widget(nullptr)
97    , m_sandboxFlags(SandboxNone)
98{
99}
100
101RenderPart* HTMLFrameOwnerElement::renderPart() const
102{
103    // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
104    // when using fallback content.
105    if (!renderer() || !renderer()->isRenderPart())
106        return 0;
107    return toRenderPart(renderer());
108}
109
110void HTMLFrameOwnerElement::setContentFrame(Frame& frame)
111{
112    // Make sure we will not end up with two frames referencing the same owner element.
113    ASSERT(!m_contentFrame || m_contentFrame->owner() != this);
114    // Disconnected frames should not be allowed to load.
115    ASSERT(inDocument());
116    m_contentFrame = &frame;
117
118    for (ContainerNode* node = this; node; node = node->parentOrShadowHostNode())
119        node->incrementConnectedSubframeCount();
120}
121
122void HTMLFrameOwnerElement::clearContentFrame()
123{
124    if (!m_contentFrame)
125        return;
126
127    m_contentFrame = 0;
128
129    for (ContainerNode* node = this; node; node = node->parentOrShadowHostNode())
130        node->decrementConnectedSubframeCount();
131}
132
133void HTMLFrameOwnerElement::disconnectContentFrame()
134{
135    // FIXME: Currently we don't do this in removedFrom because this causes an
136    // unload event in the subframe which could execute script that could then
137    // reach up into this document and then attempt to look back down. We should
138    // see if this behavior is really needed as Gecko does not allow this.
139    if (Frame* frame = contentFrame()) {
140        RefPtr<Frame> protect(frame);
141        if (frame->isLocalFrame())
142            toLocalFrame(frame)->loader().frameDetached();
143        frame->disconnectOwnerElement();
144    }
145}
146
147HTMLFrameOwnerElement::~HTMLFrameOwnerElement()
148{
149    if (m_contentFrame)
150        m_contentFrame->disconnectOwnerElement();
151}
152
153Document* HTMLFrameOwnerElement::contentDocument() const
154{
155    return (m_contentFrame && m_contentFrame->isLocalFrame()) ? toLocalFrame(m_contentFrame)->document() : 0;
156}
157
158LocalDOMWindow* HTMLFrameOwnerElement::contentWindow() const
159{
160    return m_contentFrame ? m_contentFrame->domWindow() : 0;
161}
162
163void HTMLFrameOwnerElement::setSandboxFlags(SandboxFlags flags)
164{
165    m_sandboxFlags = flags;
166}
167
168bool HTMLFrameOwnerElement::isKeyboardFocusable() const
169{
170    return m_contentFrame && HTMLElement::isKeyboardFocusable();
171}
172
173void HTMLFrameOwnerElement::dispatchLoad()
174{
175    dispatchEvent(Event::create(EventTypeNames::load));
176}
177
178Document* HTMLFrameOwnerElement::getSVGDocument(ExceptionState& exceptionState) const
179{
180    Document* doc = contentDocument();
181    if (doc && doc->isSVGDocument())
182        return doc;
183    return 0;
184}
185
186void HTMLFrameOwnerElement::setWidget(PassRefPtr<Widget> widget)
187{
188    if (widget == m_widget)
189        return;
190
191    if (m_widget) {
192        if (m_widget->parent())
193            moveWidgetToParentSoon(m_widget.get(), 0);
194        m_widget = nullptr;
195    }
196
197    m_widget = widget;
198
199    RenderWidget* renderWidget = toRenderWidget(renderer());
200    if (!renderWidget)
201        return;
202
203    if (m_widget) {
204        renderWidget->updateOnWidgetChange();
205
206        ASSERT(document().view() == renderWidget->frameView());
207        ASSERT(renderWidget->frameView());
208        moveWidgetToParentSoon(m_widget.get(), renderWidget->frameView());
209    }
210
211    if (AXObjectCache* cache = document().existingAXObjectCache())
212        cache->childrenChanged(renderWidget);
213}
214
215Widget* HTMLFrameOwnerElement::ownedWidget() const
216{
217    return m_widget.get();
218}
219
220bool HTMLFrameOwnerElement::loadOrRedirectSubframe(const KURL& url, const AtomicString& frameName, bool lockBackForwardList)
221{
222    RefPtr<LocalFrame> parentFrame = document().frame();
223    // FIXME(kenrb): The necessary semantics for RemoteFrames have not been worked out yet, but this will likely need some logic to handle them.
224    if (contentFrame() && contentFrame()->isLocalFrame()) {
225        toLocalFrame(contentFrame())->navigationScheduler().scheduleLocationChange(&document(), url.string(), Referrer(document().outgoingReferrer(), document().referrerPolicy()), lockBackForwardList);
226        return true;
227    }
228
229    if (!document().securityOrigin()->canDisplay(url)) {
230        FrameLoader::reportLocalLoadFailed(parentFrame.get(), url.string());
231        return false;
232    }
233
234    if (!SubframeLoadingDisabler::canLoadFrame(*this))
235        return false;
236
237    String referrer = SecurityPolicy::generateReferrerHeader(document().referrerPolicy(), url, document().outgoingReferrer());
238    RefPtr<LocalFrame> childFrame = parentFrame->loader().client()->createFrame(url, frameName, Referrer(referrer, document().referrerPolicy()), this);
239
240    if (!childFrame)  {
241        parentFrame->loader().checkCompleted();
242        return false;
243    }
244
245    // All new frames will have m_isComplete set to true at this point due to synchronously loading
246    // an empty document in FrameLoader::init(). But many frames will now be starting an
247    // asynchronous load of url, so we set m_isComplete to false and then check if the load is
248    // actually completed below. (Note that we set m_isComplete to false even for synchronous
249    // loads, so that checkCompleted() below won't bail early.)
250    // FIXME: Can we remove this entirely? m_isComplete normally gets set to false when a load is committed.
251    childFrame->loader().started();
252
253    FrameView* view = childFrame->view();
254    RenderObject* renderObject = renderer();
255    // We need to test the existence of renderObject and its widget-ness, as
256    // failing to do so causes problems.
257    if (renderObject && renderObject->isWidget() && view)
258        setWidget(view);
259
260    // Some loads are performed synchronously (e.g., about:blank and loads
261    // cancelled by returning a null ResourceRequest from requestFromDelegate).
262    // In these cases, the synchronous load would have finished
263    // before we could connect the signals, so make sure to send the
264    // completed() signal for the child by hand and mark the load as being
265    // complete.
266    // FIXME: In this case the LocalFrame will have finished loading before
267    // it's being added to the child list. It would be a good idea to
268    // create the child first, then invoke the loader separately.
269    if (childFrame->loader().state() == FrameStateComplete && !childFrame->loader().policyDocumentLoader())
270        childFrame->loader().checkCompleted();
271    return true;
272}
273
274
275} // namespace WebCore
276