| /* |
| * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Samuel Weinig <sam@webkit.org> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include "JSDOMBinding.h" |
| |
| #include "debugger/DebuggerCallFrame.h" |
| |
| #include "ActiveDOMObject.h" |
| #include "DOMCoreException.h" |
| #include "DOMObjectHashTableMap.h" |
| #include "Document.h" |
| #include "EventException.h" |
| #include "ExceptionBase.h" |
| #include "ExceptionCode.h" |
| #include "Frame.h" |
| #include "HTMLAudioElement.h" |
| #include "HTMLCanvasElement.h" |
| #include "HTMLFrameElementBase.h" |
| #include "HTMLImageElement.h" |
| #include "HTMLLinkElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLScriptElement.h" |
| #include "HTMLStyleElement.h" |
| #include "JSDOMCoreException.h" |
| #include "JSDOMWindowCustom.h" |
| #include "JSEventException.h" |
| #include "JSExceptionBase.h" |
| #include "JSMainThreadExecState.h" |
| #include "JSRangeException.h" |
| #include "JSXMLHttpRequestException.h" |
| #include "KURL.h" |
| #include "MessagePort.h" |
| #include "ProcessingInstruction.h" |
| #include "RangeException.h" |
| #include "ScriptCachedFrameData.h" |
| #include "ScriptCallStack.h" |
| #include "ScriptController.h" |
| #include "Settings.h" |
| #include "WebCoreJSClientData.h" |
| #include "XMLHttpRequestException.h" |
| #include <runtime/DateInstance.h> |
| #include <runtime/Error.h> |
| #include <runtime/JSFunction.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/StdLibExtras.h> |
| |
| #if ENABLE(SVG) |
| #include "JSSVGException.h" |
| #include "SVGException.h" |
| #endif |
| |
| #if ENABLE(XPATH) |
| #include "JSXPathException.h" |
| #include "XPathException.h" |
| #endif |
| |
| #if ENABLE(DATABASE) |
| #include "JSSQLException.h" |
| #include "SQLException.h" |
| #endif |
| |
| #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM) |
| #include "FileException.h" |
| #include "JSFileException.h" |
| #endif |
| |
| #if ENABLE(INDEXED_DATABASE) |
| #include "IDBDatabaseException.h" |
| #include "JSIDBDatabaseException.h" |
| #endif |
| |
| using namespace JSC; |
| |
| namespace WebCore { |
| |
| using namespace HTMLNames; |
| |
| class JSGlobalDataWorldIterator { |
| public: |
| JSGlobalDataWorldIterator(JSGlobalData* globalData) |
| : m_pos(static_cast<WebCoreJSClientData*>(globalData->clientData)->m_worldSet.begin()) |
| , m_end(static_cast<WebCoreJSClientData*>(globalData->clientData)->m_worldSet.end()) |
| { |
| } |
| |
| operator bool() |
| { |
| return m_pos != m_end; |
| } |
| |
| DOMWrapperWorld* operator*() |
| { |
| ASSERT(m_pos != m_end); |
| return *m_pos; |
| } |
| |
| DOMWrapperWorld* operator->() |
| { |
| ASSERT(m_pos != m_end); |
| return *m_pos; |
| } |
| |
| JSGlobalDataWorldIterator& operator++() |
| { |
| ++m_pos; |
| return *this; |
| } |
| |
| private: |
| HashSet<DOMWrapperWorld*>::iterator m_pos; |
| HashSet<DOMWrapperWorld*>::iterator m_end; |
| }; |
| |
| const JSC::HashTable* getHashTableForGlobalData(JSGlobalData& globalData, const JSC::HashTable* staticTable) |
| { |
| return DOMObjectHashTableMap::mapFor(globalData).get(staticTable); |
| } |
| |
| void visitActiveObjectsForContext(SlotVisitor& visitor, JSGlobalData& globalData, ScriptExecutionContext* scriptExecutionContext) |
| { |
| // If an element has pending activity that may result in event listeners being called |
| // (e.g. an XMLHttpRequest), we need to keep JS wrappers alive. |
| |
| const HashMap<ActiveDOMObject*, void*>& activeObjects = scriptExecutionContext->activeDOMObjects(); |
| HashMap<ActiveDOMObject*, void*>::const_iterator activeObjectsEnd = activeObjects.end(); |
| for (HashMap<ActiveDOMObject*, void*>::const_iterator iter = activeObjects.begin(); iter != activeObjectsEnd; ++iter) { |
| if (iter->first->hasPendingActivity()) { |
| // Generally, an active object with pending activity must have a wrapper to mark its listeners. |
| // However, some ActiveDOMObjects don't have JS wrappers. |
| markDOMObjectWrapper(visitor, globalData, iter->second); |
| } |
| } |
| |
| const HashSet<MessagePort*>& messagePorts = scriptExecutionContext->messagePorts(); |
| HashSet<MessagePort*>::const_iterator portsEnd = messagePorts.end(); |
| for (HashSet<MessagePort*>::const_iterator iter = messagePorts.begin(); iter != portsEnd; ++iter) { |
| // If the message port is remotely entangled, then always mark it as in-use because we can't determine reachability across threads. |
| if (!(*iter)->locallyEntangledPort() || (*iter)->hasPendingActivity()) |
| markDOMObjectWrapper(visitor, globalData, *iter); |
| } |
| } |
| |
| void markDOMObjectWrapper(SlotVisitor& visitor, JSGlobalData& globalData, void* object) |
| { |
| // FIXME: This could be changed to only mark wrappers that are "observable" |
| // as markDOMNodesForDocument does, allowing us to collect more wrappers, |
| // but doing this correctly would be challenging. |
| if (!object) |
| return; |
| |
| for (JSGlobalDataWorldIterator worldIter(&globalData); worldIter; ++worldIter) { |
| if (JSDOMWrapper* wrapper = worldIter->m_wrappers.get(object).get()) |
| visitor.deprecatedAppend(reinterpret_cast<JSCell**>(&wrapper)); |
| } |
| } |
| |
| static void stringWrapperDestroyed(JSString*, void* context) |
| { |
| StringImpl* cacheKey = static_cast<StringImpl*>(context); |
| cacheKey->deref(); |
| } |
| |
| JSValue jsStringSlowCase(ExecState* exec, JSStringCache& stringCache, StringImpl* stringImpl) |
| { |
| JSString* wrapper = jsStringWithFinalizer(exec, UString(stringImpl), stringWrapperDestroyed, stringImpl); |
| stringCache.set(exec->globalData(), stringImpl, wrapper); |
| // ref explicitly instead of using a RefPtr-keyed hashtable because the wrapper can |
| // outlive the cache, so the stringImpl has to match the wrapper's lifetime. |
| stringImpl->ref(); |
| return wrapper; |
| } |
| |
| JSValue jsStringOrNull(ExecState* exec, const String& s) |
| { |
| if (s.isNull()) |
| return jsNull(); |
| return jsString(exec, s); |
| } |
| |
| JSValue jsOwnedStringOrNull(ExecState* exec, const String& s) |
| { |
| if (s.isNull()) |
| return jsNull(); |
| return jsOwnedString(exec, stringToUString(s)); |
| } |
| |
| JSValue jsStringOrUndefined(ExecState* exec, const String& s) |
| { |
| if (s.isNull()) |
| return jsUndefined(); |
| return jsString(exec, s); |
| } |
| |
| JSValue jsStringOrFalse(ExecState* exec, const String& s) |
| { |
| if (s.isNull()) |
| return jsBoolean(false); |
| return jsString(exec, s); |
| } |
| |
| JSValue jsString(ExecState* exec, const KURL& url) |
| { |
| return jsString(exec, url.string()); |
| } |
| |
| JSValue jsStringOrNull(ExecState* exec, const KURL& url) |
| { |
| if (url.isNull()) |
| return jsNull(); |
| return jsString(exec, url.string()); |
| } |
| |
| JSValue jsStringOrUndefined(ExecState* exec, const KURL& url) |
| { |
| if (url.isNull()) |
| return jsUndefined(); |
| return jsString(exec, url.string()); |
| } |
| |
| JSValue jsStringOrFalse(ExecState* exec, const KURL& url) |
| { |
| if (url.isNull()) |
| return jsBoolean(false); |
| return jsString(exec, url.string()); |
| } |
| |
| AtomicStringImpl* findAtomicString(const Identifier& identifier) |
| { |
| if (identifier.isNull()) |
| return 0; |
| StringImpl* impl = identifier.impl(); |
| ASSERT(impl->existingHash()); |
| return AtomicString::find(impl->characters(), impl->length(), impl->existingHash()); |
| } |
| |
| String valueToStringWithNullCheck(ExecState* exec, JSValue value) |
| { |
| if (value.isNull()) |
| return String(); |
| return ustringToString(value.toString(exec)); |
| } |
| |
| String valueToStringWithUndefinedOrNullCheck(ExecState* exec, JSValue value) |
| { |
| if (value.isUndefinedOrNull()) |
| return String(); |
| return ustringToString(value.toString(exec)); |
| } |
| |
| JSValue jsDateOrNull(ExecState* exec, double value) |
| { |
| if (!isfinite(value)) |
| return jsNull(); |
| return new (exec) DateInstance(exec, exec->lexicalGlobalObject()->dateStructure(), value); |
| } |
| |
| double valueToDate(ExecState* exec, JSValue value) |
| { |
| if (value.isNumber()) |
| return value.uncheckedGetNumber(); |
| if (!value.inherits(&DateInstance::s_info)) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return static_cast<DateInstance*>(value.toObject(exec))->internalNumber(); |
| } |
| |
| void reportException(ExecState* exec, JSValue exception) |
| { |
| if (exception.isObject() && asObject(exception)->exceptionType() == Terminated) |
| return; |
| |
| UString errorMessage = exception.toString(exec); |
| JSObject* exceptionObject = exception.toObject(exec); |
| int lineNumber = exceptionObject->get(exec, Identifier(exec, "line")).toInt32(exec); |
| UString exceptionSourceURL = exceptionObject->get(exec, Identifier(exec, "sourceURL")).toString(exec); |
| exec->clearException(); |
| |
| if (ExceptionBase* exceptionBase = toExceptionBase(exception)) |
| errorMessage = stringToUString(exceptionBase->message() + ": " + exceptionBase->description()); |
| |
| ScriptExecutionContext* scriptExecutionContext = static_cast<JSDOMGlobalObject*>(exec->lexicalGlobalObject())->scriptExecutionContext(); |
| ASSERT(scriptExecutionContext); |
| |
| // Crash data indicates null-dereference crashes at this point in the Safari 4 Public Beta. |
| // It's harmless to return here without reporting the exception to the log and the debugger in this case. |
| if (!scriptExecutionContext) |
| return; |
| |
| scriptExecutionContext->reportException(ustringToString(errorMessage), lineNumber, ustringToString(exceptionSourceURL), 0); |
| } |
| |
| void reportCurrentException(ExecState* exec) |
| { |
| JSValue exception = exec->exception(); |
| exec->clearException(); |
| reportException(exec, exception); |
| } |
| |
| void setDOMException(ExecState* exec, ExceptionCode ec) |
| { |
| if (!ec || exec->hadException()) |
| return; |
| |
| // FIXME: All callers to setDOMException need to pass in the right global object |
| // for now, we're going to assume the lexicalGlobalObject. Which is wrong in cases like this: |
| // frames[0].document.createElement(null, null); // throws an exception which should have the subframes prototypes. |
| JSDOMGlobalObject* globalObject = deprecatedGlobalObjectForPrototype(exec); |
| |
| ExceptionCodeDescription description; |
| getExceptionCodeDescription(ec, description); |
| |
| JSValue errorObject; |
| switch (description.type) { |
| case DOMExceptionType: |
| errorObject = toJS(exec, globalObject, DOMCoreException::create(description)); |
| break; |
| case RangeExceptionType: |
| errorObject = toJS(exec, globalObject, RangeException::create(description)); |
| break; |
| case EventExceptionType: |
| errorObject = toJS(exec, globalObject, EventException::create(description)); |
| break; |
| case XMLHttpRequestExceptionType: |
| errorObject = toJS(exec, globalObject, XMLHttpRequestException::create(description)); |
| break; |
| #if ENABLE(SVG) |
| case SVGExceptionType: |
| errorObject = toJS(exec, globalObject, SVGException::create(description).get()); |
| break; |
| #endif |
| #if ENABLE(XPATH) |
| case XPathExceptionType: |
| errorObject = toJS(exec, globalObject, XPathException::create(description)); |
| break; |
| #endif |
| #if ENABLE(DATABASE) |
| case SQLExceptionType: |
| errorObject = toJS(exec, globalObject, SQLException::create(description)); |
| break; |
| #endif |
| #if ENABLE(BLOB) || ENABLE(FILE_SYSTEM) |
| case FileExceptionType: |
| errorObject = toJS(exec, globalObject, FileException::create(description)); |
| break; |
| #endif |
| #if ENABLE(INDEXED_DATABASE) |
| case IDBDatabaseExceptionType: |
| errorObject = toJS(exec, globalObject, IDBDatabaseException::create(description)); |
| break; |
| #endif |
| } |
| |
| ASSERT(errorObject); |
| throwError(exec, errorObject); |
| } |
| |
| DOMWindow* activeDOMWindow(ExecState* exec) |
| { |
| return asJSDOMWindow(exec->lexicalGlobalObject())->impl(); |
| } |
| |
| DOMWindow* firstDOMWindow(ExecState* exec) |
| { |
| return asJSDOMWindow(exec->dynamicGlobalObject())->impl(); |
| } |
| |
| bool checkNodeSecurity(ExecState* exec, Node* node) |
| { |
| return node && allowsAccessFromFrame(exec, node->document()->frame()); |
| } |
| |
| bool allowsAccessFromFrame(ExecState* exec, Frame* frame) |
| { |
| if (!frame) |
| return false; |
| JSDOMWindow* window = toJSDOMWindow(frame, currentWorld(exec)); |
| return window && window->allowsAccessFrom(exec); |
| } |
| |
| bool allowsAccessFromFrame(ExecState* exec, Frame* frame, String& message) |
| { |
| if (!frame) |
| return false; |
| JSDOMWindow* window = toJSDOMWindow(frame, currentWorld(exec)); |
| return window && window->allowsAccessFrom(exec, message); |
| } |
| |
| void printErrorMessageForFrame(Frame* frame, const String& message) |
| { |
| if (!frame) |
| return; |
| frame->domWindow()->printErrorMessage(message); |
| } |
| |
| // FIXME: We should remove or at least deprecate this function. Callers can use firstDOMWindow directly. |
| Frame* toDynamicFrame(ExecState* exec) |
| { |
| return firstDOMWindow(exec)->frame(); |
| } |
| |
| // FIXME: We should remove this function. Callers can use ScriptController directly. |
| bool processingUserGesture() |
| { |
| return ScriptController::processingUserGesture(); |
| } |
| |
| JSValue objectToStringFunctionGetter(ExecState* exec, JSValue, const Identifier& propertyName) |
| { |
| return new (exec) JSFunction(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->functionStructure(), 0, propertyName, objectProtoFuncToString); |
| } |
| |
| Structure* getCachedDOMStructure(JSDOMGlobalObject* globalObject, const ClassInfo* classInfo) |
| { |
| JSDOMStructureMap& structures = globalObject->structures(); |
| return structures.get(classInfo).get(); |
| } |
| |
| Structure* cacheDOMStructure(JSDOMGlobalObject* globalObject, Structure* structure, const ClassInfo* classInfo) |
| { |
| JSDOMStructureMap& structures = globalObject->structures(); |
| ASSERT(!structures.contains(classInfo)); |
| return structures.set(classInfo, WriteBarrier<Structure>(globalObject->globalData(), globalObject, structure)).first->second.get(); |
| } |
| |
| JSC::JSObject* toJSSequence(ExecState* exec, JSValue value, unsigned& length) |
| { |
| JSObject* object = value.getObject(); |
| if (!object) { |
| throwTypeError(exec); |
| return 0; |
| } |
| JSValue lengthValue = object->get(exec, exec->propertyNames().length); |
| if (exec->hadException()) |
| return 0; |
| |
| if (lengthValue.isUndefinedOrNull()) { |
| throwTypeError(exec); |
| return 0; |
| } |
| |
| length = lengthValue.toUInt32(exec); |
| if (exec->hadException()) |
| return 0; |
| |
| return object; |
| } |
| |
| } // namespace WebCore |