1/*
2 * Copyright (C) 2007, 2009, 2010 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 COMPUTER, 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 COMPUTER, 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 "JSNode.h"
28
29#include "Attr.h"
30#include "CDATASection.h"
31#include "Comment.h"
32#include "Document.h"
33#include "DocumentFragment.h"
34#include "DocumentType.h"
35#include "Entity.h"
36#include "EntityReference.h"
37#include "ExceptionCode.h"
38#include "HTMLAudioElement.h"
39#include "HTMLCanvasElement.h"
40#include "HTMLElement.h"
41#include "HTMLFrameElementBase.h"
42#include "HTMLImageElement.h"
43#include "HTMLLinkElement.h"
44#include "HTMLNames.h"
45#include "HTMLScriptElement.h"
46#include "HTMLStyleElement.h"
47#include "JSAttr.h"
48#include "JSCDATASection.h"
49#include "JSComment.h"
50#include "JSDOMBinding.h"
51#include "JSDocument.h"
52#include "JSDocumentFragment.h"
53#include "JSDocumentType.h"
54#include "JSEntity.h"
55#include "JSEntityReference.h"
56#include "JSEventListener.h"
57#include "JSHTMLElement.h"
58#include "JSHTMLElementWrapperFactory.h"
59#include "JSNotation.h"
60#include "JSProcessingInstruction.h"
61#include "JSText.h"
62#include "Node.h"
63#include "Notation.h"
64#include "ProcessingInstruction.h"
65#include "RegisteredEventListener.h"
66#include "StyleSheet.h"
67#include "StyledElement.h"
68#include "Text.h"
69#include <wtf/PassRefPtr.h>
70#include <wtf/RefPtr.h>
71
72#if ENABLE(SVG)
73#include "JSSVGElementWrapperFactory.h"
74#include "SVGElement.h"
75#endif
76
77using namespace JSC;
78
79namespace WebCore {
80
81using namespace HTMLNames;
82
83static bool isObservable(JSNode* jsNode, Node* node, DOMWrapperWorld* world)
84{
85    // Certain conditions implicitly make existence of a JS DOM node wrapper observable
86    // through the DOM, even if no explicit reference to it remains.
87
88    // The DOM doesn't know how to keep a tree of nodes alive without the root
89    // being explicitly referenced. So, we artificially treat the root of
90    // every tree as observable.
91    // FIXME: Resolve this lifetime issue in the DOM, and remove this inefficiency.
92    if (!node->parentNode())
93        return true;
94
95    // If a node is in the document, and its wrapper has custom properties,
96    // the wrapper is observable because future access to the node through the
97    // DOM must reflect those properties.
98    if (jsNode->hasCustomProperties())
99        return true;
100
101    // If a node is in the document, and has event listeners, its wrapper is
102    // observable because its wrapper is responsible for marking those event listeners.
103    if (node->hasEventListeners())
104        return true;
105
106    // If a node owns another object with a wrapper with custom properties,
107    // the wrapper must be treated as observable, because future access to
108    // those objects through the DOM must reflect those properties.
109    // FIXME: It would be better if this logic could be in the node next to
110    // the custom markChildren functions rather than here.
111    // Note that for some compound objects like stylesheets and CSSStyleDeclarations,
112    // we don't descend to check children for custom properties, and just conservatively
113    // keep the node wrappers protecting them alive.
114    if (node->isElementNode()) {
115        if (node->isStyledElement()) {
116            if (CSSMutableStyleDeclaration* style = static_cast<StyledElement*>(node)->inlineStyleDecl()) {
117                if (world->m_wrappers.get(style))
118                    return true;
119            }
120        }
121        if (static_cast<Element*>(node)->hasTagName(canvasTag)) {
122            if (CanvasRenderingContext* context = static_cast<HTMLCanvasElement*>(node)->renderingContext()) {
123                if (JSDOMWrapper* wrapper = world->m_wrappers.get(context).get()) {
124                    if (wrapper->hasCustomProperties())
125                        return true;
126                }
127            }
128        } else if (static_cast<Element*>(node)->hasTagName(linkTag)) {
129            if (StyleSheet* sheet = static_cast<HTMLLinkElement*>(node)->sheet()) {
130                if (world->m_wrappers.get(sheet))
131                    return true;
132            }
133        } else if (static_cast<Element*>(node)->hasTagName(styleTag)) {
134            if (StyleSheet* sheet = static_cast<HTMLStyleElement*>(node)->sheet()) {
135                if (world->m_wrappers.get(sheet))
136                    return true;
137            }
138        }
139    } else if (node->nodeType() == Node::PROCESSING_INSTRUCTION_NODE) {
140        if (StyleSheet* sheet = static_cast<ProcessingInstruction*>(node)->sheet()) {
141            if (world->m_wrappers.get(sheet))
142                return true;
143        }
144    }
145
146    return false;
147}
148
149static inline bool isReachableFromDOM(JSNode* jsNode, Node* node, DOMWrapperWorld* world, MarkStack& markStack)
150{
151    if (!node->inDocument()) {
152        // If a wrapper is the last reference to an image or script element
153        // that is loading but not in the document, the wrapper is observable
154        // because it is the only thing keeping the image element alive, and if
155        // the image element is destroyed, its load event will not fire.
156        // FIXME: The DOM should manage this issue without the help of JavaScript wrappers.
157        if (node->hasTagName(imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent())
158            return true;
159        if (node->hasTagName(scriptTag) && !static_cast<HTMLScriptElement*>(node)->haveFiredLoadEvent())
160            return true;
161    #if ENABLE(VIDEO)
162        if (node->hasTagName(audioTag) && !static_cast<HTMLAudioElement*>(node)->paused())
163            return true;
164    #endif
165
166        // If a node is firing event listeners, its wrapper is observable because
167        // its wrapper is responsible for marking those event listeners.
168        if (node->isFiringEventListeners())
169            return true;
170    }
171
172    return isObservable(jsNode, node, world) && markStack.containsOpaqueRoot(root(node));
173}
174
175bool JSNodeOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void* context, MarkStack& markStack)
176{
177    JSNode* jsNode = static_cast<JSNode*>(handle.get().asCell());
178    DOMWrapperWorld* world = static_cast<DOMWrapperWorld*>(context);
179    return isReachableFromDOM(jsNode, jsNode->impl(), world, markStack);
180}
181
182void JSNodeOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
183{
184    JSNode* jsNode = static_cast<JSNode*>(handle.get().asCell());
185    DOMWrapperWorld* world = static_cast<DOMWrapperWorld*>(context);
186    uncacheWrapper(world, jsNode->impl(), jsNode);
187}
188
189JSValue JSNode::insertBefore(ExecState* exec)
190{
191    Node* imp = static_cast<Node*>(impl());
192    ExceptionCode ec = 0;
193    bool ok = imp->insertBefore(toNode(exec->argument(0)), toNode(exec->argument(1)), ec, true);
194    setDOMException(exec, ec);
195    if (ok)
196        return exec->argument(0);
197    return jsNull();
198}
199
200JSValue JSNode::replaceChild(ExecState* exec)
201{
202    Node* imp = static_cast<Node*>(impl());
203    ExceptionCode ec = 0;
204    bool ok = imp->replaceChild(toNode(exec->argument(0)), toNode(exec->argument(1)), ec, true);
205    setDOMException(exec, ec);
206    if (ok)
207        return exec->argument(1);
208    return jsNull();
209}
210
211JSValue JSNode::removeChild(ExecState* exec)
212{
213    Node* imp = static_cast<Node*>(impl());
214    ExceptionCode ec = 0;
215    bool ok = imp->removeChild(toNode(exec->argument(0)), ec);
216    setDOMException(exec, ec);
217    if (ok)
218        return exec->argument(0);
219    return jsNull();
220}
221
222JSValue JSNode::appendChild(ExecState* exec)
223{
224    Node* imp = static_cast<Node*>(impl());
225    ExceptionCode ec = 0;
226    bool ok = imp->appendChild(toNode(exec->argument(0)), ec, true);
227    setDOMException(exec, ec);
228    if (ok)
229        return exec->argument(0);
230    return jsNull();
231}
232
233ScopeChainNode* JSNode::pushEventHandlerScope(ExecState*, ScopeChainNode* node) const
234{
235    return node;
236}
237
238void JSNode::markChildren(MarkStack& markStack)
239{
240    Base::markChildren(markStack);
241
242    Node* node = m_impl.get();
243    node->markJSEventListeners(markStack);
244
245    markStack.addOpaqueRoot(root(node));
246}
247
248static ALWAYS_INLINE JSValue createWrapperInline(ExecState* exec, JSDOMGlobalObject* globalObject, Node* node)
249{
250    ASSERT(node);
251    ASSERT(!getCachedWrapper(currentWorld(exec), node));
252
253    JSNode* wrapper;
254    switch (node->nodeType()) {
255        case Node::ELEMENT_NODE:
256            if (node->isHTMLElement())
257                wrapper = createJSHTMLWrapper(exec, globalObject, toHTMLElement(node));
258#if ENABLE(SVG)
259            else if (node->isSVGElement())
260                wrapper = createJSSVGWrapper(exec, globalObject, static_cast<SVGElement*>(node));
261#endif
262            else
263                wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, Element, node);
264            break;
265        case Node::ATTRIBUTE_NODE:
266            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, Attr, node);
267            break;
268        case Node::TEXT_NODE:
269            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, Text, node);
270            break;
271        case Node::CDATA_SECTION_NODE:
272            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, CDATASection, node);
273            break;
274        case Node::ENTITY_NODE:
275            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, Entity, node);
276            break;
277        case Node::PROCESSING_INSTRUCTION_NODE:
278            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, ProcessingInstruction, node);
279            break;
280        case Node::COMMENT_NODE:
281            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, Comment, node);
282            break;
283        case Node::DOCUMENT_NODE:
284            // we don't want to cache the document itself in the per-document dictionary
285            return toJS(exec, globalObject, static_cast<Document*>(node));
286        case Node::DOCUMENT_TYPE_NODE:
287            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, DocumentType, node);
288            break;
289        case Node::NOTATION_NODE:
290            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, Notation, node);
291            break;
292        case Node::DOCUMENT_FRAGMENT_NODE:
293            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, DocumentFragment, node);
294            break;
295        case Node::ENTITY_REFERENCE_NODE:
296            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, EntityReference, node);
297            break;
298        default:
299            wrapper = CREATE_DOM_NODE_WRAPPER(exec, globalObject, Node, node);
300    }
301
302    return wrapper;
303}
304
305JSValue createWrapper(ExecState* exec, JSDOMGlobalObject* globalObject, Node* node)
306{
307    return createWrapperInline(exec, globalObject, node);
308}
309
310JSValue toJSNewlyCreated(ExecState* exec, JSDOMGlobalObject* globalObject, Node* node)
311{
312    if (!node)
313        return jsNull();
314
315    return createWrapperInline(exec, globalObject, node);
316}
317
318} // namespace WebCore
319