1/*
2 *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3 *  Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 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#include "config.h"
22#include "JSDOMBinding.h"
23
24#include "debugger/DebuggerCallFrame.h"
25
26#include "ActiveDOMObject.h"
27#include "DOMCoreException.h"
28#include "DOMObjectHashTableMap.h"
29#include "Document.h"
30#include "EventException.h"
31#include "ExceptionBase.h"
32#include "ExceptionCode.h"
33#include "Frame.h"
34#include "HTMLAudioElement.h"
35#include "HTMLCanvasElement.h"
36#include "HTMLFrameElementBase.h"
37#include "HTMLImageElement.h"
38#include "HTMLLinkElement.h"
39#include "HTMLNames.h"
40#include "HTMLScriptElement.h"
41#include "HTMLStyleElement.h"
42#include "JSDOMCoreException.h"
43#include "JSDOMWindowCustom.h"
44#include "JSEventException.h"
45#include "JSExceptionBase.h"
46#include "JSMainThreadExecState.h"
47#include "JSRangeException.h"
48#include "JSXMLHttpRequestException.h"
49#include "KURL.h"
50#include "MessagePort.h"
51#include "ProcessingInstruction.h"
52#include "RangeException.h"
53#include "ScriptCachedFrameData.h"
54#include "ScriptCallStack.h"
55#include "ScriptController.h"
56#include "Settings.h"
57#include "WebCoreJSClientData.h"
58#include "XMLHttpRequestException.h"
59#include <runtime/DateInstance.h>
60#include <runtime/Error.h>
61#include <runtime/JSFunction.h>
62#include <wtf/MathExtras.h>
63#include <wtf/StdLibExtras.h>
64
65#if ENABLE(SVG)
66#include "JSSVGException.h"
67#include "SVGException.h"
68#endif
69
70#if ENABLE(XPATH)
71#include "JSXPathException.h"
72#include "XPathException.h"
73#endif
74
75#if ENABLE(DATABASE)
76#include "JSSQLException.h"
77#include "SQLException.h"
78#endif
79
80#if ENABLE(BLOB) || ENABLE(FILE_SYSTEM)
81#include "FileException.h"
82#include "JSFileException.h"
83#endif
84
85#if ENABLE(INDEXED_DATABASE)
86#include "IDBDatabaseException.h"
87#include "JSIDBDatabaseException.h"
88#endif
89
90using namespace JSC;
91
92namespace WebCore {
93
94using namespace HTMLNames;
95
96class JSGlobalDataWorldIterator {
97public:
98    JSGlobalDataWorldIterator(JSGlobalData* globalData)
99        : m_pos(static_cast<WebCoreJSClientData*>(globalData->clientData)->m_worldSet.begin())
100        , m_end(static_cast<WebCoreJSClientData*>(globalData->clientData)->m_worldSet.end())
101    {
102    }
103
104    operator bool()
105    {
106        return m_pos != m_end;
107    }
108
109    DOMWrapperWorld* operator*()
110    {
111        ASSERT(m_pos != m_end);
112        return *m_pos;
113    }
114
115    DOMWrapperWorld* operator->()
116    {
117        ASSERT(m_pos != m_end);
118        return *m_pos;
119    }
120
121    JSGlobalDataWorldIterator& operator++()
122    {
123        ++m_pos;
124        return *this;
125    }
126
127private:
128    HashSet<DOMWrapperWorld*>::iterator m_pos;
129    HashSet<DOMWrapperWorld*>::iterator m_end;
130};
131
132const JSC::HashTable* getHashTableForGlobalData(JSGlobalData& globalData, const JSC::HashTable* staticTable)
133{
134    return DOMObjectHashTableMap::mapFor(globalData).get(staticTable);
135}
136
137void markActiveObjectsForContext(MarkStack& markStack, JSGlobalData& globalData, ScriptExecutionContext* scriptExecutionContext)
138{
139    // If an element has pending activity that may result in event listeners being called
140    // (e.g. an XMLHttpRequest), we need to keep JS wrappers alive.
141
142    const HashMap<ActiveDOMObject*, void*>& activeObjects = scriptExecutionContext->activeDOMObjects();
143    HashMap<ActiveDOMObject*, void*>::const_iterator activeObjectsEnd = activeObjects.end();
144    for (HashMap<ActiveDOMObject*, void*>::const_iterator iter = activeObjects.begin(); iter != activeObjectsEnd; ++iter) {
145        if (iter->first->hasPendingActivity()) {
146            // Generally, an active object with pending activity must have a wrapper to mark its listeners.
147            // However, some ActiveDOMObjects don't have JS wrappers.
148            markDOMObjectWrapper(markStack, globalData, iter->second);
149        }
150    }
151
152    const HashSet<MessagePort*>& messagePorts = scriptExecutionContext->messagePorts();
153    HashSet<MessagePort*>::const_iterator portsEnd = messagePorts.end();
154    for (HashSet<MessagePort*>::const_iterator iter = messagePorts.begin(); iter != portsEnd; ++iter) {
155        // If the message port is remotely entangled, then always mark it as in-use because we can't determine reachability across threads.
156        if (!(*iter)->locallyEntangledPort() || (*iter)->hasPendingActivity())
157            markDOMObjectWrapper(markStack, globalData, *iter);
158    }
159}
160
161void markDOMObjectWrapper(MarkStack& markStack, JSGlobalData& globalData, void* object)
162{
163    // FIXME: This could be changed to only mark wrappers that are "observable"
164    // as markDOMNodesForDocument does, allowing us to collect more wrappers,
165    // but doing this correctly would be challenging.
166    if (!object)
167        return;
168
169    for (JSGlobalDataWorldIterator worldIter(&globalData); worldIter; ++worldIter) {
170        if (JSDOMWrapper* wrapper = worldIter->m_wrappers.get(object).get())
171            markStack.deprecatedAppend(reinterpret_cast<JSCell**>(&wrapper));
172    }
173}
174
175static void stringWrapperDestroyed(JSString*, void* context)
176{
177    StringImpl* cacheKey = static_cast<StringImpl*>(context);
178    cacheKey->deref();
179}
180
181JSValue jsStringSlowCase(ExecState* exec, JSStringCache& stringCache, StringImpl* stringImpl)
182{
183    JSString* wrapper = jsStringWithFinalizer(exec, UString(stringImpl), stringWrapperDestroyed, stringImpl);
184    stringCache.set(exec->globalData(), stringImpl, wrapper);
185    // ref explicitly instead of using a RefPtr-keyed hashtable because the wrapper can
186    // outlive the cache, so the stringImpl has to match the wrapper's lifetime.
187    stringImpl->ref();
188    return wrapper;
189}
190
191JSValue jsStringOrNull(ExecState* exec, const String& s)
192{
193    if (s.isNull())
194        return jsNull();
195    return jsString(exec, s);
196}
197
198JSValue jsOwnedStringOrNull(ExecState* exec, const String& s)
199{
200    if (s.isNull())
201        return jsNull();
202    return jsOwnedString(exec, stringToUString(s));
203}
204
205JSValue jsStringOrUndefined(ExecState* exec, const String& s)
206{
207    if (s.isNull())
208        return jsUndefined();
209    return jsString(exec, s);
210}
211
212JSValue jsStringOrFalse(ExecState* exec, const String& s)
213{
214    if (s.isNull())
215        return jsBoolean(false);
216    return jsString(exec, s);
217}
218
219JSValue jsString(ExecState* exec, const KURL& url)
220{
221    return jsString(exec, url.string());
222}
223
224JSValue jsStringOrNull(ExecState* exec, const KURL& url)
225{
226    if (url.isNull())
227        return jsNull();
228    return jsString(exec, url.string());
229}
230
231JSValue jsStringOrUndefined(ExecState* exec, const KURL& url)
232{
233    if (url.isNull())
234        return jsUndefined();
235    return jsString(exec, url.string());
236}
237
238JSValue jsStringOrFalse(ExecState* exec, const KURL& url)
239{
240    if (url.isNull())
241        return jsBoolean(false);
242    return jsString(exec, url.string());
243}
244
245AtomicStringImpl* findAtomicString(const Identifier& identifier)
246{
247    if (identifier.isNull())
248        return 0;
249    StringImpl* impl = identifier.impl();
250    ASSERT(impl->existingHash());
251    return AtomicString::find(impl->characters(), impl->length(), impl->existingHash());
252}
253
254String valueToStringWithNullCheck(ExecState* exec, JSValue value)
255{
256    if (value.isNull())
257        return String();
258    return ustringToString(value.toString(exec));
259}
260
261String valueToStringWithUndefinedOrNullCheck(ExecState* exec, JSValue value)
262{
263    if (value.isUndefinedOrNull())
264        return String();
265    return ustringToString(value.toString(exec));
266}
267
268JSValue jsDateOrNull(ExecState* exec, double value)
269{
270    if (!isfinite(value))
271        return jsNull();
272    return new (exec) DateInstance(exec, exec->lexicalGlobalObject()->dateStructure(), value);
273}
274
275double valueToDate(ExecState* exec, JSValue value)
276{
277    if (value.isNumber())
278        return value.uncheckedGetNumber();
279    if (!value.inherits(&DateInstance::s_info))
280        return std::numeric_limits<double>::quiet_NaN();
281    return static_cast<DateInstance*>(value.toObject(exec))->internalNumber();
282}
283
284void reportException(ExecState* exec, JSValue exception)
285{
286    if (exception.isObject() && asObject(exception)->exceptionType() == Terminated)
287        return;
288
289    UString errorMessage = exception.toString(exec);
290    JSObject* exceptionObject = exception.toObject(exec);
291    int lineNumber = exceptionObject->get(exec, Identifier(exec, "line")).toInt32(exec);
292    UString exceptionSourceURL = exceptionObject->get(exec, Identifier(exec, "sourceURL")).toString(exec);
293    exec->clearException();
294
295    if (ExceptionBase* exceptionBase = toExceptionBase(exception))
296        errorMessage = stringToUString(exceptionBase->message() + ": "  + exceptionBase->description());
297
298    ScriptExecutionContext* scriptExecutionContext = static_cast<JSDOMGlobalObject*>(exec->lexicalGlobalObject())->scriptExecutionContext();
299    ASSERT(scriptExecutionContext);
300
301    // Crash data indicates null-dereference crashes at this point in the Safari 4 Public Beta.
302    // It's harmless to return here without reporting the exception to the log and the debugger in this case.
303    if (!scriptExecutionContext)
304        return;
305
306    scriptExecutionContext->reportException(ustringToString(errorMessage), lineNumber, ustringToString(exceptionSourceURL), 0);
307}
308
309void reportCurrentException(ExecState* exec)
310{
311    JSValue exception = exec->exception();
312    exec->clearException();
313    reportException(exec, exception);
314}
315
316void setDOMException(ExecState* exec, ExceptionCode ec)
317{
318    if (!ec || exec->hadException())
319        return;
320
321    // FIXME: All callers to setDOMException need to pass in the right global object
322    // for now, we're going to assume the lexicalGlobalObject.  Which is wrong in cases like this:
323    // frames[0].document.createElement(null, null); // throws an exception which should have the subframes prototypes.
324    JSDOMGlobalObject* globalObject = deprecatedGlobalObjectForPrototype(exec);
325
326    ExceptionCodeDescription description;
327    getExceptionCodeDescription(ec, description);
328
329    JSValue errorObject;
330    switch (description.type) {
331        case DOMExceptionType:
332            errorObject = toJS(exec, globalObject, DOMCoreException::create(description));
333            break;
334        case RangeExceptionType:
335            errorObject = toJS(exec, globalObject, RangeException::create(description));
336            break;
337        case EventExceptionType:
338            errorObject = toJS(exec, globalObject, EventException::create(description));
339            break;
340        case XMLHttpRequestExceptionType:
341            errorObject = toJS(exec, globalObject, XMLHttpRequestException::create(description));
342            break;
343#if ENABLE(SVG)
344        case SVGExceptionType:
345            errorObject = toJS(exec, globalObject, SVGException::create(description).get());
346            break;
347#endif
348#if ENABLE(XPATH)
349        case XPathExceptionType:
350            errorObject = toJS(exec, globalObject, XPathException::create(description));
351            break;
352#endif
353#if ENABLE(DATABASE)
354        case SQLExceptionType:
355            errorObject = toJS(exec, globalObject, SQLException::create(description));
356            break;
357#endif
358#if ENABLE(BLOB) || ENABLE(FILE_SYSTEM)
359        case FileExceptionType:
360            errorObject = toJS(exec, globalObject, FileException::create(description));
361            break;
362#endif
363#if ENABLE(INDEXED_DATABASE)
364        case IDBDatabaseExceptionType:
365            errorObject = toJS(exec, globalObject, IDBDatabaseException::create(description));
366            break;
367#endif
368    }
369
370    ASSERT(errorObject);
371    throwError(exec, errorObject);
372}
373
374DOMWindow* activeDOMWindow(ExecState* exec)
375{
376    return asJSDOMWindow(exec->lexicalGlobalObject())->impl();
377}
378
379DOMWindow* firstDOMWindow(ExecState* exec)
380{
381    return asJSDOMWindow(exec->dynamicGlobalObject())->impl();
382}
383
384bool checkNodeSecurity(ExecState* exec, Node* node)
385{
386    return node && allowsAccessFromFrame(exec, node->document()->frame());
387}
388
389bool allowsAccessFromFrame(ExecState* exec, Frame* frame)
390{
391    if (!frame)
392        return false;
393    JSDOMWindow* window = toJSDOMWindow(frame, currentWorld(exec));
394    return window && window->allowsAccessFrom(exec);
395}
396
397bool allowsAccessFromFrame(ExecState* exec, Frame* frame, String& message)
398{
399    if (!frame)
400        return false;
401    JSDOMWindow* window = toJSDOMWindow(frame, currentWorld(exec));
402    return window && window->allowsAccessFrom(exec, message);
403}
404
405void printErrorMessageForFrame(Frame* frame, const String& message)
406{
407    if (!frame)
408        return;
409    frame->domWindow()->printErrorMessage(message);
410}
411
412// FIXME: We should remove or at least deprecate this function. Callers can use firstDOMWindow directly.
413Frame* toDynamicFrame(ExecState* exec)
414{
415    return firstDOMWindow(exec)->frame();
416}
417
418// FIXME: We should remove this function. Callers can use ScriptController directly.
419bool processingUserGesture()
420{
421    return ScriptController::processingUserGesture();
422}
423
424JSValue objectToStringFunctionGetter(ExecState* exec, JSValue, const Identifier& propertyName)
425{
426    return new (exec) JSFunction(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->functionStructure(), 0, propertyName, objectProtoFuncToString);
427}
428
429Structure* getCachedDOMStructure(JSDOMGlobalObject* globalObject, const ClassInfo* classInfo)
430{
431    JSDOMStructureMap& structures = globalObject->structures();
432    return structures.get(classInfo).get();
433}
434
435Structure* cacheDOMStructure(JSDOMGlobalObject* globalObject, Structure* structure, const ClassInfo* classInfo)
436{
437    JSDOMStructureMap& structures = globalObject->structures();
438    ASSERT(!structures.contains(classInfo));
439    return structures.set(classInfo, WriteBarrier<Structure>(globalObject->globalData(), globalObject, structure)).first->second.get();
440}
441
442JSC::JSObject* toJSSequence(ExecState* exec, JSValue value, unsigned& length)
443{
444    JSObject* object = value.getObject();
445    if (!object) {
446        throwTypeError(exec);
447        return 0;
448    }
449    JSValue lengthValue = object->get(exec, exec->propertyNames().length);
450    if (exec->hadException())
451        return 0;
452
453    if (lengthValue.isUndefinedOrNull()) {
454        throwTypeError(exec);
455        return 0;
456    }
457
458    length = lengthValue.toUInt32(exec);
459    if (exec->hadException())
460        return 0;
461
462    return object;
463}
464
465} // namespace WebCore
466