| /* |
| * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) |
| * Copyright (C) 2004-2011, 2013, 2016 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Samuel Weinig <sam@webkit.org> |
| * Copyright (C) 2013 Michael Pruett <michael@68k.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 "CachedScript.h" |
| #include "DOMConstructorWithDocument.h" |
| #include "ExceptionCode.h" |
| #include "ExceptionCodeDescription.h" |
| #include "ExceptionHeaders.h" |
| #include "ExceptionInterfaces.h" |
| #include "Frame.h" |
| #include "HTMLParserIdioms.h" |
| #include "IDBDatabaseException.h" |
| #include "JSDOMConvert.h" |
| #include "JSDOMWindowCustom.h" |
| #include "JSExceptionBase.h" |
| #include "SecurityOrigin.h" |
| #include <bytecode/CodeBlock.h> |
| #include <inspector/ScriptCallStack.h> |
| #include <inspector/ScriptCallStackFactory.h> |
| #include <interpreter/Interpreter.h> |
| #include <runtime/DateInstance.h> |
| #include <runtime/Error.h> |
| #include <runtime/ErrorHandlingScope.h> |
| #include <runtime/ErrorInstance.h> |
| #include <runtime/Exception.h> |
| #include <runtime/ExceptionHelpers.h> |
| #include <runtime/JSFunction.h> |
| #include <stdarg.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/unicode/CharacterNames.h> |
| |
| using namespace JSC; |
| using namespace Inspector; |
| |
| namespace WebCore { |
| |
| STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(DOMConstructorObject); |
| STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(DOMConstructorWithDocument); |
| |
| void addImpureProperty(const AtomicString& propertyName) |
| { |
| JSDOMWindow::commonVM().addImpureProperty(propertyName); |
| } |
| |
| JSValue jsStringOrNull(ExecState* exec, const String& s) |
| { |
| if (s.isNull()) |
| return jsNull(); |
| return jsStringWithCache(exec, s); |
| } |
| |
| JSValue jsOwnedStringOrNull(ExecState* exec, const String& s) |
| { |
| if (s.isNull()) |
| return jsNull(); |
| return jsOwnedString(exec, s); |
| } |
| |
| JSValue jsStringOrUndefined(ExecState* exec, const String& s) |
| { |
| if (s.isNull()) |
| return jsUndefined(); |
| return jsStringWithCache(exec, s); |
| } |
| |
| JSValue jsString(ExecState* exec, const URL& url) |
| { |
| return jsStringWithCache(exec, url.string()); |
| } |
| |
| JSValue jsStringOrNull(ExecState* exec, const URL& url) |
| { |
| if (url.isNull()) |
| return jsNull(); |
| return jsStringWithCache(exec, url.string()); |
| } |
| |
| JSValue jsStringOrUndefined(ExecState* exec, const URL& url) |
| { |
| if (url.isNull()) |
| return jsUndefined(); |
| return jsStringWithCache(exec, url.string()); |
| } |
| |
| String valueToStringTreatingNullAsEmptyString(ExecState* exec, JSValue value) |
| { |
| if (value.isNull()) |
| return emptyString(); |
| return value.toString(exec)->value(exec); |
| } |
| |
| String valueToStringWithUndefinedOrNullCheck(ExecState* exec, JSValue value) |
| { |
| if (value.isUndefinedOrNull()) |
| return String(); |
| return value.toString(exec)->value(exec); |
| } |
| |
| String valueToUSVString(ExecState* exec, JSValue value) |
| { |
| String string = value.toWTFString(exec); |
| if (exec->hadException()) |
| return { }; |
| StringView view { string }; |
| |
| // Fast path for 8-bit strings, since they can't have any surrogates. |
| if (view.is8Bit()) |
| return string; |
| |
| // Fast path for the case where there are no unpaired surrogates. |
| bool foundUnpairedSurrogate = false; |
| for (auto codePoint : view.codePoints()) { |
| if (U_IS_SURROGATE(codePoint)) { |
| foundUnpairedSurrogate = true; |
| break; |
| } |
| } |
| if (!foundUnpairedSurrogate) |
| return string; |
| |
| // Slow path: http://heycam.github.io/webidl/#dfn-obtain-unicode |
| // Replaces unpaired surrogates with the replacement character. |
| StringBuilder result; |
| result.reserveCapacity(view.length()); |
| for (auto codePoint : view.codePoints()) { |
| if (U_IS_SURROGATE(codePoint)) |
| result.append(replacementCharacter); |
| else |
| result.append(codePoint); |
| } |
| return result.toString(); |
| } |
| |
| String valueToUSVStringTreatingNullAsEmptyString(ExecState* exec, JSValue value) |
| { |
| return value.isNull() ? emptyString() : valueToUSVString(exec, value); |
| } |
| |
| String valueToUSVStringWithUndefinedOrNullCheck(ExecState* exec, JSValue value) |
| { |
| return value.isUndefinedOrNull() ? String() : valueToUSVString(exec, value); |
| } |
| |
| JSValue jsDateOrNaN(ExecState* exec, double value) |
| { |
| if (std::isnan(value)) |
| return jsDoubleNumber(value); |
| return jsDateOrNull(exec, value); |
| } |
| |
| JSValue jsDateOrNull(ExecState* exec, double value) |
| { |
| if (!std::isfinite(value)) |
| return jsNull(); |
| return DateInstance::create(exec->vm(), exec->lexicalGlobalObject()->dateStructure(), value); |
| } |
| |
| double valueToDate(ExecState* exec, JSValue value) |
| { |
| if (value.isNumber()) |
| return value.asNumber(); |
| if (!value.inherits(DateInstance::info())) |
| return std::numeric_limits<double>::quiet_NaN(); |
| return static_cast<DateInstance*>(value.toObject(exec))->internalNumber(); |
| } |
| |
| void reportException(ExecState* exec, JSValue exceptionValue, CachedScript* cachedScript) |
| { |
| RELEASE_ASSERT(exec->vm().currentThreadIsHoldingAPILock()); |
| Exception* exception = jsDynamicCast<Exception*>(exceptionValue); |
| if (!exception) { |
| exception = exec->lastException(); |
| if (!exception) |
| exception = Exception::create(exec->vm(), exceptionValue, Exception::DoNotCaptureStack); |
| } |
| |
| reportException(exec, exception, cachedScript); |
| } |
| |
| void reportException(ExecState* exec, Exception* exception, CachedScript* cachedScript, ExceptionDetails* exceptionDetails) |
| { |
| RELEASE_ASSERT(exec->vm().currentThreadIsHoldingAPILock()); |
| if (isTerminatedExecutionException(exception)) |
| return; |
| |
| ErrorHandlingScope errorScope(exec->vm()); |
| |
| RefPtr<ScriptCallStack> callStack(createScriptCallStackFromException(exec, exception, ScriptCallStack::maxCallStackSizeToCapture)); |
| exec->clearException(); |
| exec->clearLastException(); |
| |
| JSDOMGlobalObject* globalObject = jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()); |
| if (JSDOMWindow* window = jsDynamicCast<JSDOMWindow*>(globalObject)) { |
| if (!window->wrapped().isCurrentlyDisplayedInFrame()) |
| return; |
| } |
| |
| int lineNumber = 0; |
| int columnNumber = 0; |
| String exceptionSourceURL; |
| if (const ScriptCallFrame* callFrame = callStack->firstNonNativeCallFrame()) { |
| lineNumber = callFrame->lineNumber(); |
| columnNumber = callFrame->columnNumber(); |
| exceptionSourceURL = callFrame->sourceURL(); |
| } |
| |
| String errorMessage; |
| JSValue exceptionValue = exception->value(); |
| if (ExceptionBase* exceptionBase = toExceptionBase(exceptionValue)) |
| errorMessage = exceptionBase->toString(); |
| else { |
| // FIXME: <http://webkit.org/b/115087> Web Inspector: WebCore::reportException should not evaluate JavaScript handling exceptions |
| // If this is a custom exception object, call toString on it to try and get a nice string representation for the exception. |
| if (ErrorInstance* error = jsDynamicCast<ErrorInstance*>(exceptionValue)) |
| errorMessage = error->sanitizedToString(exec); |
| else |
| errorMessage = exceptionValue.toString(exec)->value(exec); |
| |
| // We need to clear any new exception that may be thrown in the toString() call above. |
| // reportException() is not supposed to be making new exceptions. |
| exec->clearException(); |
| exec->clearLastException(); |
| } |
| |
| ScriptExecutionContext* scriptExecutionContext = globalObject->scriptExecutionContext(); |
| scriptExecutionContext->reportException(errorMessage, lineNumber, columnNumber, exceptionSourceURL, exception, callStack->size() ? callStack : nullptr, cachedScript); |
| |
| if (exceptionDetails) { |
| exceptionDetails->message = errorMessage; |
| exceptionDetails->lineNumber = lineNumber; |
| exceptionDetails->columnNumber = columnNumber; |
| exceptionDetails->sourceURL = exceptionSourceURL; |
| } |
| } |
| |
| void reportCurrentException(ExecState* exec) |
| { |
| Exception* exception = exec->exception(); |
| exec->clearException(); |
| reportException(exec, exception); |
| } |
| |
| #define TRY_TO_CREATE_EXCEPTION(interfaceName) \ |
| case interfaceName##Type: \ |
| errorObject = toJS(exec, globalObject, interfaceName::create(description)); \ |
| break; |
| |
| static JSValue createDOMException(ExecState* exec, ExceptionCode ec, const String* message) |
| { |
| if (!ec) |
| return jsUndefined(); |
| |
| // FIXME: Handle other WebIDL exception types. |
| if (ec == TypeError) { |
| if (!message || message->isEmpty()) |
| return createTypeError(exec); |
| return createTypeError(exec, *message); |
| } |
| |
| if (ec == RangeError) { |
| if (!message || message->isEmpty()) |
| return createRangeError(exec, ASCIILiteral("Bad value")); |
| return createRangeError(exec, *message); |
| } |
| |
| // 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(ec); |
| |
| CString messageCString; |
| if (message) |
| messageCString = message->utf8(); |
| if (message && !message->isEmpty()) { |
| // It is safe to do this because the char* contents of the CString are copied into a new WTF::String before the CString is destroyed. |
| description.description = messageCString.data(); |
| } |
| |
| JSValue errorObject; |
| switch (description.type) { |
| case DOMCoreExceptionType: |
| #if ENABLE(INDEXED_DATABASE) |
| case IDBDatabaseExceptionType: |
| #endif |
| errorObject = toJS(exec, globalObject, DOMCoreException::create(description)); |
| break; |
| case FileExceptionType: |
| errorObject = toJS(exec, globalObject, FileException::create(description)); |
| break; |
| case SQLExceptionType: |
| errorObject = toJS(exec, globalObject, SQLException::create(description)); |
| break; |
| case SVGExceptionType: |
| errorObject = toJS(exec, globalObject, SVGException::create(description)); |
| break; |
| case XPathExceptionType: |
| errorObject = toJS(exec, globalObject, XPathException::create(description)); |
| break; |
| } |
| |
| ASSERT(errorObject); |
| addErrorInfo(exec, asObject(errorObject), true); |
| return errorObject; |
| } |
| |
| JSValue createDOMException(ExecState* exec, ExceptionCode ec, const String& message) |
| { |
| return createDOMException(exec, ec, &message); |
| } |
| |
| JSValue createDOMException(ExecState* exec, ExceptionCode ec) |
| { |
| return createDOMException(exec, ec, nullptr); |
| } |
| |
| void setDOMException(ExecState* exec, ExceptionCode ec) |
| { |
| if (!ec || exec->hadException()) |
| return; |
| |
| exec->vm().throwException(exec, createDOMException(exec, ec)); |
| } |
| |
| void setDOMException(JSC::ExecState* exec, const ExceptionCodeWithMessage& ec) |
| { |
| if (!ec.code || exec->hadException()) |
| return; |
| |
| exec->vm().throwException(exec, createDOMException(exec, ec.code, ec.message)); |
| } |
| |
| #undef TRY_TO_CREATE_EXCEPTION |
| |
| bool hasIteratorMethod(JSC::ExecState& state, JSC::JSValue value) |
| { |
| if (!value.isObject()) |
| return false; |
| |
| auto& vm = state.vm(); |
| JSObject* object = JSC::asObject(value); |
| CallData callData; |
| CallType callType; |
| JSValue applyMethod = object->getMethod(&state, callData, callType, vm.propertyNames->iteratorSymbol, ASCIILiteral("Symbol.iterator property should be callable")); |
| if (vm.exception()) |
| return false; |
| |
| return !applyMethod.isUndefined(); |
| } |
| |
| bool shouldAllowAccessToNode(ExecState* exec, Node* node) |
| { |
| return BindingSecurity::shouldAllowAccessToNode(exec, node); |
| } |
| |
| bool shouldAllowAccessToFrame(ExecState* exec, Frame* target) |
| { |
| return BindingSecurity::shouldAllowAccessToFrame(exec, target); |
| } |
| |
| bool shouldAllowAccessToFrame(ExecState* exec, Frame* frame, String& message) |
| { |
| if (!frame) |
| return false; |
| if (BindingSecurity::shouldAllowAccessToFrame(exec, frame, DoNotReportSecurityError)) |
| return true; |
| message = frame->document()->domWindow()->crossDomainAccessErrorMessage(activeDOMWindow(exec)); |
| return false; |
| } |
| |
| bool shouldAllowAccessToDOMWindow(ExecState* exec, DOMWindow& target, String& message) |
| { |
| if (BindingSecurity::shouldAllowAccessToDOMWindow(exec, target, DoNotReportSecurityError)) |
| return true; |
| message = target.crossDomainAccessErrorMessage(activeDOMWindow(exec)); |
| return false; |
| } |
| |
| void printErrorMessageForFrame(Frame* frame, const String& message) |
| { |
| if (!frame) |
| return; |
| frame->document()->domWindow()->printErrorMessage(message); |
| } |
| |
| 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.vm(), &globalObject, structure)).iterator->value.get(); |
| } |
| |
| static const int32_t kMaxInt32 = 0x7fffffff; |
| static const int32_t kMinInt32 = -kMaxInt32 - 1; |
| static const uint32_t kMaxUInt32 = 0xffffffffU; |
| static const int64_t kJSMaxInteger = 0x20000000000000LL - 1; // 2^53 - 1, largest integer exactly representable in ECMAScript. |
| |
| static String rangeErrorString(double value, double min, double max) |
| { |
| return makeString("Value ", String::numberToStringECMAScript(value), " is outside the range [", String::numberToStringECMAScript(min), ", ", String::numberToStringECMAScript(max), "]"); |
| } |
| |
| static double enforceRange(ExecState& state, double x, double minimum, double maximum) |
| { |
| if (std::isnan(x) || std::isinf(x)) { |
| throwTypeError(&state, rangeErrorString(x, minimum, maximum)); |
| return 0; |
| } |
| x = trunc(x); |
| if (x < minimum || x > maximum) { |
| throwTypeError(&state, rangeErrorString(x, minimum, maximum)); |
| return 0; |
| } |
| return x; |
| } |
| |
| template <typename T> |
| struct IntTypeLimits { |
| }; |
| |
| template <> |
| struct IntTypeLimits<int8_t> { |
| static const int8_t minValue = -128; |
| static const int8_t maxValue = 127; |
| static const unsigned numberOfValues = 256; // 2^8 |
| }; |
| |
| template <> |
| struct IntTypeLimits<uint8_t> { |
| static const uint8_t maxValue = 255; |
| static const unsigned numberOfValues = 256; // 2^8 |
| }; |
| |
| template <> |
| struct IntTypeLimits<int16_t> { |
| static const short minValue = -32768; |
| static const short maxValue = 32767; |
| static const unsigned numberOfValues = 65536; // 2^16 |
| }; |
| |
| template <> |
| struct IntTypeLimits<uint16_t> { |
| static const unsigned short maxValue = 65535; |
| static const unsigned numberOfValues = 65536; // 2^16 |
| }; |
| |
| template <typename T> |
| static inline T toSmallerInt(ExecState& state, JSValue value, IntegerConversionConfiguration configuration) |
| { |
| static_assert(std::is_signed<T>::value && std::is_integral<T>::value, "Should only be used for signed integral types"); |
| |
| typedef IntTypeLimits<T> LimitsTrait; |
| // Fast path if the value is already a 32-bit signed integer in the right range. |
| if (value.isInt32()) { |
| int32_t d = value.asInt32(); |
| if (d >= LimitsTrait::minValue && d <= LimitsTrait::maxValue) |
| return static_cast<T>(d); |
| switch (configuration) { |
| case NormalConversion: |
| break; |
| case EnforceRange: |
| throwTypeError(&state); |
| return 0; |
| case Clamp: |
| return d < LimitsTrait::minValue ? LimitsTrait::minValue : LimitsTrait::maxValue; |
| } |
| d %= LimitsTrait::numberOfValues; |
| return static_cast<T>(d > LimitsTrait::maxValue ? d - LimitsTrait::numberOfValues : d); |
| } |
| |
| double x = value.toNumber(&state); |
| if (UNLIKELY(state.hadException())) |
| return 0; |
| |
| switch (configuration) { |
| case NormalConversion: |
| break; |
| case EnforceRange: |
| return enforceRange(state, x, LimitsTrait::minValue, LimitsTrait::maxValue); |
| case Clamp: |
| return std::isnan(x) ? 0 : clampTo<T>(x); |
| } |
| |
| if (std::isnan(x) || std::isinf(x) || !x) |
| return 0; |
| |
| x = x < 0 ? -floor(fabs(x)) : floor(fabs(x)); |
| x = fmod(x, LimitsTrait::numberOfValues); |
| |
| return static_cast<T>(x > LimitsTrait::maxValue ? x - LimitsTrait::numberOfValues : x); |
| } |
| |
| template <typename T> |
| static inline T toSmallerUInt(ExecState& state, JSValue value, IntegerConversionConfiguration configuration) |
| { |
| static_assert(std::is_unsigned<T>::value && std::is_integral<T>::value, "Should only be used for unsigned integral types"); |
| |
| typedef IntTypeLimits<T> LimitsTrait; |
| // Fast path if the value is already a 32-bit unsigned integer in the right range. |
| if (value.isUInt32()) { |
| uint32_t d = value.asUInt32(); |
| if (d <= LimitsTrait::maxValue) |
| return static_cast<T>(d); |
| switch (configuration) { |
| case NormalConversion: |
| return static_cast<T>(d); |
| case EnforceRange: |
| throwTypeError(&state); |
| return 0; |
| case Clamp: |
| return LimitsTrait::maxValue; |
| } |
| } |
| |
| double x = value.toNumber(&state); |
| if (UNLIKELY(state.hadException())) |
| return 0; |
| |
| switch (configuration) { |
| case NormalConversion: |
| break; |
| case EnforceRange: |
| return enforceRange(state, x, 0, LimitsTrait::maxValue); |
| case Clamp: |
| return std::isnan(x) ? 0 : clampTo<T>(x); |
| } |
| |
| if (std::isnan(x) || std::isinf(x) || !x) |
| return 0; |
| |
| x = x < 0 ? -floor(fabs(x)) : floor(fabs(x)); |
| return static_cast<T>(fmod(x, LimitsTrait::numberOfValues)); |
| } |
| |
| int8_t toInt8EnforceRange(JSC::ExecState& state, JSValue value) |
| { |
| return toSmallerInt<int8_t>(state, value, EnforceRange); |
| } |
| |
| uint8_t toUInt8EnforceRange(JSC::ExecState& state, JSValue value) |
| { |
| return toSmallerUInt<uint8_t>(state, value, EnforceRange); |
| } |
| |
| int8_t toInt8Clamp(JSC::ExecState& state, JSValue value) |
| { |
| return toSmallerInt<int8_t>(state, value, Clamp); |
| } |
| |
| uint8_t toUInt8Clamp(JSC::ExecState& state, JSValue value) |
| { |
| return toSmallerUInt<uint8_t>(state, value, Clamp); |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-byte |
| int8_t toInt8(ExecState& state, JSValue value) |
| { |
| return toSmallerInt<int8_t>(state, value, NormalConversion); |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-octet |
| uint8_t toUInt8(ExecState& state, JSValue value) |
| { |
| return toSmallerUInt<uint8_t>(state, value, NormalConversion); |
| } |
| |
| int16_t toInt16EnforceRange(ExecState& state, JSValue value) |
| { |
| return toSmallerInt<int16_t>(state, value, EnforceRange); |
| } |
| |
| uint16_t toUInt16EnforceRange(ExecState& state, JSValue value) |
| { |
| return toSmallerUInt<uint16_t>(state, value, EnforceRange); |
| } |
| |
| int16_t toInt16Clamp(ExecState& state, JSValue value) |
| { |
| return toSmallerInt<int16_t>(state, value, Clamp); |
| } |
| |
| uint16_t toUInt16Clamp(ExecState& state, JSValue value) |
| { |
| return toSmallerUInt<uint16_t>(state, value, Clamp); |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-short |
| int16_t toInt16(ExecState& state, JSValue value) |
| { |
| return toSmallerInt<int16_t>(state, value, NormalConversion); |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-unsigned-short |
| uint16_t toUInt16(ExecState& state, JSValue value) |
| { |
| return toSmallerUInt<uint16_t>(state, value, NormalConversion); |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-long |
| int32_t toInt32EnforceRange(ExecState& state, JSValue value) |
| { |
| if (value.isInt32()) |
| return value.asInt32(); |
| |
| double x = value.toNumber(&state); |
| if (UNLIKELY(state.hadException())) |
| return 0; |
| return enforceRange(state, x, kMinInt32, kMaxInt32); |
| } |
| |
| int32_t toInt32Clamp(ExecState& state, JSValue value) |
| { |
| if (value.isInt32()) |
| return value.asInt32(); |
| |
| double x = value.toNumber(&state); |
| return std::isnan(x) ? 0 : clampTo<int32_t>(x); |
| } |
| |
| uint32_t toUInt32Clamp(ExecState& state, JSValue value) |
| { |
| if (value.isUInt32()) |
| return value.asUInt32(); |
| |
| double x = value.toNumber(&state); |
| return std::isnan(x) ? 0 : clampTo<uint32_t>(x); |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-unsigned-long |
| uint32_t toUInt32EnforceRange(ExecState& state, JSValue value) |
| { |
| if (value.isUInt32()) |
| return value.asUInt32(); |
| |
| double x = value.toNumber(&state); |
| if (UNLIKELY(state.hadException())) |
| return 0; |
| return enforceRange(state, x, 0, kMaxUInt32); |
| } |
| |
| int64_t toInt64EnforceRange(ExecState& state, JSC::JSValue value) |
| { |
| double x = value.toNumber(&state); |
| if (UNLIKELY(state.hadException())) |
| return 0; |
| return enforceRange(state, x, -kJSMaxInteger, kJSMaxInteger); |
| } |
| |
| uint64_t toUInt64EnforceRange(ExecState& state, JSC::JSValue value) |
| { |
| double x = value.toNumber(&state); |
| if (UNLIKELY(state.hadException())) |
| return 0; |
| return enforceRange(state, x, 0, kJSMaxInteger); |
| } |
| |
| int64_t toInt64Clamp(ExecState& state, JSC::JSValue value) |
| { |
| double x = value.toNumber(&state); |
| return std::isnan(x) ? 0 : static_cast<int64_t>(std::min<double>(std::max<double>(x, -kJSMaxInteger), kJSMaxInteger)); |
| } |
| |
| uint64_t toUInt64Clamp(ExecState& state, JSC::JSValue value) |
| { |
| double x = value.toNumber(&state); |
| return std::isnan(x) ? 0 : static_cast<uint64_t>(std::min<double>(std::max<double>(x, 0), kJSMaxInteger)); |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-long-long |
| int64_t toInt64(ExecState& state, JSValue value) |
| { |
| double x = value.toNumber(&state); |
| |
| // Map NaNs and +/-Infinity to 0; convert finite values modulo 2^64. |
| unsigned long long n; |
| doubleToInteger(x, n); |
| return n; |
| } |
| |
| // http://www.w3.org/TR/WebIDL/#es-unsigned-long-long |
| uint64_t toUInt64(ExecState& state, JSValue value) |
| { |
| double x = value.toNumber(&state); |
| |
| // Map NaNs and +/-Infinity to 0; convert finite values modulo 2^64. |
| unsigned long long n; |
| doubleToInteger(x, n); |
| return n; |
| } |
| |
| class GetCallerGlobalObjectFunctor { |
| public: |
| GetCallerGlobalObjectFunctor() = default; |
| |
| StackVisitor::Status operator()(StackVisitor& visitor) const |
| { |
| if (!m_hasSkippedFirstFrame) { |
| m_hasSkippedFirstFrame = true; |
| return StackVisitor::Continue; |
| } |
| |
| if (auto* codeBlock = visitor->codeBlock()) |
| m_globalObject = codeBlock->globalObject(); |
| else { |
| ASSERT(visitor->callee()); |
| m_globalObject = visitor->callee()->globalObject(); |
| } |
| return StackVisitor::Done; |
| } |
| |
| JSGlobalObject* globalObject() const { return m_globalObject; } |
| |
| private: |
| mutable bool m_hasSkippedFirstFrame { false }; |
| mutable JSGlobalObject* m_globalObject { nullptr }; |
| }; |
| |
| DOMWindow& callerDOMWindow(ExecState* exec) |
| { |
| GetCallerGlobalObjectFunctor iter; |
| exec->iterate(iter); |
| return iter.globalObject() ? asJSDOMWindow(iter.globalObject())->wrapped() : firstDOMWindow(exec); |
| } |
| |
| DOMWindow& activeDOMWindow(ExecState* exec) |
| { |
| return asJSDOMWindow(exec->lexicalGlobalObject())->wrapped(); |
| } |
| |
| DOMWindow& firstDOMWindow(ExecState* exec) |
| { |
| return asJSDOMWindow(exec->vmEntryGlobalObject())->wrapped(); |
| } |
| |
| static inline bool canAccessDocument(JSC::ExecState* state, Document* targetDocument, SecurityReportingOption reportingOption = ReportSecurityError) |
| { |
| if (!targetDocument) |
| return false; |
| |
| DOMWindow& active = activeDOMWindow(state); |
| |
| if (active.document()->securityOrigin()->canAccess(targetDocument->securityOrigin())) |
| return true; |
| |
| if (reportingOption == ReportSecurityError) |
| printErrorMessageForFrame(targetDocument->frame(), targetDocument->domWindow()->crossDomainAccessErrorMessage(active)); |
| |
| return false; |
| } |
| |
| bool BindingSecurity::shouldAllowAccessToDOMWindow(JSC::ExecState* state, DOMWindow& target, SecurityReportingOption reportingOption) |
| { |
| return canAccessDocument(state, target.document(), reportingOption); |
| } |
| |
| bool BindingSecurity::shouldAllowAccessToFrame(JSC::ExecState* state, Frame* target, SecurityReportingOption reportingOption) |
| { |
| return target && canAccessDocument(state, target->document(), reportingOption); |
| } |
| |
| bool BindingSecurity::shouldAllowAccessToNode(JSC::ExecState* state, Node* target) |
| { |
| return target && canAccessDocument(state, &target->document()); |
| } |
| |
| static EncodedJSValue throwTypeError(JSC::ExecState& state, const String& errorMessage) |
| { |
| return throwVMTypeError(&state, errorMessage); |
| } |
| |
| static void appendArgumentMustBe(StringBuilder& builder, unsigned argumentIndex, const char* argumentName, const char* interfaceName, const char* functionName) |
| { |
| builder.appendLiteral("Argument "); |
| builder.appendNumber(argumentIndex + 1); |
| builder.appendLiteral(" ('"); |
| builder.append(argumentName); |
| builder.appendLiteral("') to "); |
| if (!functionName) { |
| builder.appendLiteral("the "); |
| builder.append(interfaceName); |
| builder.appendLiteral(" constructor"); |
| } else { |
| builder.append(interfaceName); |
| builder.append('.'); |
| builder.append(functionName); |
| } |
| builder.appendLiteral(" must be "); |
| } |
| |
| JSC::EncodedJSValue reportDeprecatedGetterError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) |
| { |
| auto& context = *jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject())->scriptExecutionContext(); |
| context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Deprecated attempt to access property '", attributeName, "' on a non-", interfaceName, " object.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| void reportDeprecatedSetterError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) |
| { |
| auto& context = *jsCast<JSDOMGlobalObject*>(state.lexicalGlobalObject())->scriptExecutionContext(); |
| context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Deprecated attempt to set property '", attributeName, "' on a non-", interfaceName, " object.")); |
| } |
| |
| void throwNotSupportedError(JSC::ExecState& state, const char* message) |
| { |
| ASSERT(!state.hadException()); |
| String messageString(message); |
| state.vm().throwException(&state, createDOMException(&state, NOT_SUPPORTED_ERR, &messageString)); |
| } |
| |
| void throwInvalidStateError(JSC::ExecState& state, const char* message) |
| { |
| ASSERT(!state.hadException()); |
| String messageString(message); |
| state.vm().throwException(&state, createDOMException(&state, INVALID_STATE_ERR, &messageString)); |
| } |
| |
| JSC::EncodedJSValue throwArgumentMustBeEnumError(JSC::ExecState& state, unsigned argumentIndex, const char* argumentName, const char* functionInterfaceName, const char* functionName, const char* expectedValues) |
| { |
| StringBuilder builder; |
| appendArgumentMustBe(builder, argumentIndex, argumentName, functionInterfaceName, functionName); |
| builder.appendLiteral("one of: "); |
| builder.append(expectedValues); |
| return throwVMTypeError(&state, builder.toString()); |
| } |
| |
| JSC::EncodedJSValue throwArgumentMustBeFunctionError(JSC::ExecState& state, unsigned argumentIndex, const char* argumentName, const char* interfaceName, const char* functionName) |
| { |
| StringBuilder builder; |
| appendArgumentMustBe(builder, argumentIndex, argumentName, interfaceName, functionName); |
| builder.appendLiteral("a function"); |
| return throwVMTypeError(&state, builder.toString()); |
| } |
| |
| JSC::EncodedJSValue throwArgumentTypeError(JSC::ExecState& state, unsigned argumentIndex, const char* argumentName, const char* functionInterfaceName, const char* functionName, const char* expectedType) |
| { |
| StringBuilder builder; |
| appendArgumentMustBe(builder, argumentIndex, argumentName, functionInterfaceName, functionName); |
| builder.appendLiteral("an instance of "); |
| builder.append(expectedType); |
| return throwVMTypeError(&state, builder.toString()); |
| } |
| |
| void throwArrayElementTypeError(JSC::ExecState& state) |
| { |
| throwTypeError(state, ASCIILiteral("Invalid Array element type")); |
| } |
| |
| void throwAttributeTypeError(JSC::ExecState& state, const char* interfaceName, const char* attributeName, const char* expectedType) |
| { |
| throwTypeError(state, makeString("The ", interfaceName, '.', attributeName, " attribute must be an instance of ", expectedType)); |
| } |
| |
| JSC::EncodedJSValue throwConstructorDocumentUnavailableError(JSC::ExecState& state, const char* interfaceName) |
| { |
| // FIXME: This is confusing exception wording. Can we reword to be clearer and more specific? |
| return throwVMError(&state, createReferenceError(&state, makeString(interfaceName, " constructor associated document is unavailable"))); |
| } |
| |
| void throwSequenceTypeError(JSC::ExecState& state) |
| { |
| throwTypeError(state, ASCIILiteral("Value is not a sequence")); |
| } |
| |
| void throwNonFiniteTypeError(ExecState& state) |
| { |
| throwTypeError(&state, ASCIILiteral("The provided value is non-finite")); |
| } |
| |
| String makeGetterTypeErrorMessage(const char* interfaceName, const char* attributeName) |
| { |
| return makeString("The ", interfaceName, '.', attributeName, " getter can only be used on instances of ", interfaceName); |
| } |
| |
| JSC::EncodedJSValue throwGetterTypeError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) |
| { |
| return throwVMTypeError(&state, makeGetterTypeErrorMessage(interfaceName, attributeName)); |
| } |
| |
| bool throwSetterTypeError(JSC::ExecState& state, const char* interfaceName, const char* attributeName) |
| { |
| throwTypeError(state, makeString("The ", interfaceName, '.', attributeName, " setter can only be used on instances of ", interfaceName)); |
| return false; |
| } |
| |
| String makeThisTypeErrorMessage(const char* interfaceName, const char* functionName) |
| { |
| return makeString("Can only call ", interfaceName, '.', functionName, " on instances of ", interfaceName); |
| } |
| |
| EncodedJSValue throwThisTypeError(JSC::ExecState& state, const char* interfaceName, const char* functionName) |
| { |
| return throwTypeError(state, makeThisTypeErrorMessage(interfaceName, functionName)); |
| } |
| |
| void callFunctionWithCurrentArguments(JSC::ExecState& state, JSC::JSObject& thisObject, JSC::JSFunction& function) |
| { |
| JSC::CallData callData; |
| JSC::CallType callType = JSC::getCallData(&function, callData); |
| ASSERT(callType != CallType::None); |
| |
| JSC::MarkedArgumentBuffer arguments; |
| for (unsigned i = 0; i < state.argumentCount(); ++i) |
| arguments.append(state.uncheckedArgument(i)); |
| JSC::call(&state, &function, callType, callData, &thisObject, arguments); |
| } |
| |
| void DOMConstructorJSBuiltinObject::visitChildren(JSC::JSCell* cell, JSC::SlotVisitor& visitor) |
| { |
| auto* thisObject = jsCast<DOMConstructorJSBuiltinObject*>(cell); |
| ASSERT_GC_OBJECT_INHERITS(thisObject, info()); |
| Base::visitChildren(thisObject, visitor); |
| visitor.append(&thisObject->m_initializeFunction); |
| } |
| |
| static EncodedJSValue JSC_HOST_CALL callThrowTypeError(ExecState* exec) |
| { |
| throwTypeError(exec, ASCIILiteral("Constructor requires 'new' operator")); |
| return JSValue::encode(jsNull()); |
| } |
| |
| CallType DOMConstructorObject::getCallData(JSCell*, CallData& callData) |
| { |
| callData.native.function = callThrowTypeError; |
| return CallType::Host; |
| } |
| |
| } // namespace WebCore |