Reviewed by Sam Weinig.
[WebKit-https.git] / WebCore / bindings / js / kjs_binding.cpp
1 /*
2  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
4  *  Copyright (C) 2007 Samuel Weinig <sam@webkit.org>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 // gcc 3.x can't handle including the HashMap pointer specialization in this file
22 #if defined __GNUC__ && !defined __GLIBCXX__ // less than gcc 3.4
23 #define HASH_MAP_PTR_SPEC_WORKAROUND 1
24 #endif
25
26 #include "config.h"
27 #include "kjs_binding.h"
28
29 #include "ExceptionCode.h"
30 #include "HTMLImageElement.h"
31 #include "HTMLNames.h"
32 #include "JSNode.h"
33 #include "XMLHttpRequest.h"
34
35 using namespace WebCore;
36 using namespace HTMLNames;
37
38 namespace KJS {
39
40 typedef HashMap<void*, DOMObject*> DOMObjectMap;
41 typedef HashMap<Node*, JSNode*> NodeMap;
42 typedef HashMap<Document*, NodeMap*> NodePerDocMap;
43
44 // For debugging, keep a set of wrappers currently registered, and check that
45 // all are unregistered before they are destroyed. This has helped us fix at
46 // least one bug.
47
48 static void addWrapper(DOMObject* wrapper);
49 static void removeWrapper(DOMObject* wrapper);
50 static void removeWrappers(const NodeMap& wrappers);
51
52 #ifdef NDEBUG
53
54 static inline void addWrapper(DOMObject*)
55 {
56 }
57
58 static inline void removeWrapper(DOMObject*)
59 {
60 }
61
62 static inline void removeWrappers(const NodeMap&)
63 {
64 }
65
66 #else
67
68 static HashSet<DOMObject*>& wrapperSet()
69 {
70     static HashSet<DOMObject*> staticWrapperSet;
71     return staticWrapperSet;
72 }
73
74 static void addWrapper(DOMObject* wrapper)
75 {
76     ASSERT(!wrapperSet().contains(wrapper));
77     wrapperSet().add(wrapper);
78 }
79
80 static void removeWrapper(DOMObject* wrapper)
81 {
82     if (!wrapper)
83         return;
84     ASSERT(wrapperSet().contains(wrapper));
85     wrapperSet().remove(wrapper);
86 }
87
88 static void removeWrappers(const NodeMap& wrappers)
89 {
90     for (NodeMap::const_iterator it = wrappers.begin(); it != wrappers.end(); ++it)
91         removeWrapper(it->second);
92 }
93
94 DOMObject::~DOMObject()
95 {
96     ASSERT(!wrapperSet().contains(this));
97 }
98
99 #endif
100
101 static DOMObjectMap& domObjects()
102
103     // Don't use malloc here. Calling malloc from a mark function can deadlock.
104     static DOMObjectMap staticDOMObjects;
105     return staticDOMObjects;
106 }
107
108 static NodePerDocMap& domNodesPerDocument()
109 {
110     // domNodesPerDocument() callers must synchronize using the JSLock because 
111     // domNodesPerDocument() is called from a mark function, which can run
112     // on a secondary thread.
113     ASSERT(JSLock::lockCount());
114
115     // Don't use malloc here. Calling malloc from a mark function can deadlock.
116     static NodePerDocMap staticDOMNodesPerDocument;
117     return staticDOMNodesPerDocument;
118 }
119
120 DOMObject* ScriptInterpreter::getDOMObject(void* objectHandle) 
121 {
122     return domObjects().get(objectHandle);
123 }
124
125 void ScriptInterpreter::putDOMObject(void* objectHandle, DOMObject* wrapper) 
126 {
127     addWrapper(wrapper);
128     domObjects().set(objectHandle, wrapper);
129 }
130
131 void ScriptInterpreter::forgetDOMObject(void* objectHandle)
132 {
133     removeWrapper(domObjects().take(objectHandle));
134 }
135
136 JSNode* ScriptInterpreter::getDOMNodeForDocument(Document* document, Node* node)
137 {
138     if (!document)
139         return static_cast<JSNode*>(domObjects().get(node));
140     NodeMap* documentDict = domNodesPerDocument().get(document);
141     if (documentDict)
142         return documentDict->get(node);
143     return NULL;
144 }
145
146 void ScriptInterpreter::forgetDOMNodeForDocument(Document* document, Node* node)
147 {
148     if (!document) {
149         removeWrapper(domObjects().take(node));
150         return;
151     }
152     NodeMap* documentDict = domNodesPerDocument().get(document);
153     if (documentDict)
154         removeWrapper(documentDict->take(node));
155 }
156
157 void ScriptInterpreter::putDOMNodeForDocument(Document* document, Node* node, JSNode* wrapper)
158 {
159     addWrapper(wrapper);
160     if (!document) {
161         domObjects().set(node, wrapper);
162         return;
163     }
164     NodeMap* documentDict = domNodesPerDocument().get(document);
165     if (!documentDict) {
166         documentDict = new NodeMap;
167         domNodesPerDocument().set(document, documentDict);
168     }
169     documentDict->set(node, wrapper);
170 }
171
172 void ScriptInterpreter::forgetAllDOMNodesForDocument(Document* document)
173 {
174     ASSERT(document);
175     NodeMap* map = domNodesPerDocument().take(document);
176     if (!map)
177         return;
178     removeWrappers(*map);
179     delete map;
180 }
181
182 void ScriptInterpreter::markDOMNodesForDocument(Document* doc)
183 {
184     NodePerDocMap::iterator dictIt = domNodesPerDocument().find(doc);
185     if (dictIt != domNodesPerDocument().end()) {
186         NodeMap* nodeDict = dictIt->second;
187         NodeMap::iterator nodeEnd = nodeDict->end();
188         for (NodeMap::iterator nodeIt = nodeDict->begin(); nodeIt != nodeEnd; ++nodeIt) {
189             JSNode* jsNode = nodeIt->second;
190             Node* node = jsNode->impl();
191             
192             // don't mark wrappers for nodes that are no longer in the
193             // document - they should not be saved if the node is not
194             // otherwise reachable from JS.
195             // However, image elements that aren't in the document are also
196             // marked, if they are not done loading yet.
197             if (!jsNode->marked() && (node->inDocument() || (node->hasTagName(imgTag) &&
198                                                              !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent())))
199                 jsNode->mark();
200         }
201     }
202 }
203
204 void ScriptInterpreter::updateDOMNodeDocument(Node* node, Document* oldDoc, Document* newDoc)
205 {
206     ASSERT(oldDoc != newDoc);
207     JSNode* wrapper = getDOMNodeForDocument(oldDoc, node);
208     if (wrapper) {
209         removeWrapper(wrapper);
210         putDOMNodeForDocument(newDoc, node, wrapper);
211         forgetDOMNodeForDocument(oldDoc, node);
212         addWrapper(wrapper);
213     }
214 }
215
216 JSValue* jsStringOrNull(const String& s)
217 {
218     if (s.isNull())
219         return jsNull();
220     return jsString(s);
221 }
222
223 JSValue* jsOwnedStringOrNull(const KJS::UString& s)
224 {
225     if (s.isNull())
226         return jsNull();
227     return jsOwnedString(s);
228 }
229
230 JSValue* jsStringOrUndefined(const String& s)
231 {
232     if (s.isNull())
233         return jsUndefined();
234     return jsString(s);
235 }
236
237 JSValue* jsStringOrFalse(const String& s)
238 {
239     if (s.isNull())
240         return jsBoolean(false);
241     return jsString(s);
242 }
243
244 String valueToStringWithNullCheck(ExecState* exec, JSValue* val)
245 {
246     if (val->isNull())
247         return String();
248     return val->toString(exec);
249 }
250
251 String valueToStringWithUndefinedOrNullCheck(ExecState* exec, JSValue* val)
252 {
253     if (val->isUndefinedOrNull())
254         return String();
255     return val->toString(exec);
256 }
257
258 void setDOMException(ExecState* exec, ExceptionCode ec)
259 {
260     if (!ec || exec->hadException())
261         return;
262
263     // To be removed: See XMLHttpRequest.h.
264     if (ec == XMLHttpRequestExceptionOffset + PERMISSION_DENIED) {
265         throwError(exec, GeneralError, "Permission denied");
266         return;
267     }
268
269     ExceptionCodeDescription description;
270     getExceptionCodeDescription(ec, description);
271
272     // 100 characters is a big enough buffer, because there are:
273     //   13 characters in the message
274     //   10 characters in the longest type name
275     //   27 characters in the longest exception name
276     //   20 or so digits in the longest integer's ASCII form (even if int is 64-bit)
277     //   1 byte for a null character
278     // That adds up to about 70 bytes.
279     char buffer[100];
280     if (description.name)
281         sprintf(buffer, "%s: %s Exception %d", description.name, description.typeName, description.code);
282     else
283         sprintf(buffer, "%s Exception %d", description.typeName, description.code);
284
285     JSObject* errorObject = throwError(exec, GeneralError, buffer);
286     errorObject->put(exec, "code", jsNumber(description.code));
287 }
288
289 }