| /* |
| * Copyright (C) 2006, 2007, 2008, 2009 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "bindings/core/v8/V8Binding.h" |
| |
| #include "bindings/core/v8/ScriptController.h" |
| #include "bindings/core/v8/V8AbstractEventListener.h" |
| #include "bindings/core/v8/V8ArrayBufferView.h" |
| #include "bindings/core/v8/V8BindingMacros.h" |
| #include "bindings/core/v8/V8Element.h" |
| #include "bindings/core/v8/V8EventTarget.h" |
| #include "bindings/core/v8/V8HTMLLinkElement.h" |
| #include "bindings/core/v8/V8NodeFilter.h" |
| #include "bindings/core/v8/V8NodeFilterCondition.h" |
| #include "bindings/core/v8/V8ObjectConstructor.h" |
| #include "bindings/core/v8/V8Window.h" |
| #include "bindings/core/v8/V8WorkerGlobalScope.h" |
| #include "bindings/core/v8/V8WorkletGlobalScope.h" |
| #include "bindings/core/v8/V8XPathNSResolver.h" |
| #include "bindings/core/v8/WindowProxy.h" |
| #include "bindings/core/v8/WorkerOrWorkletScriptController.h" |
| #include "bindings/core/v8/custom/V8CustomXPathNSResolver.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/FlexibleArrayBufferView.h" |
| #include "core/dom/NodeFilter.h" |
| #include "core/dom/QualifiedName.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/inspector/InspectorTraceEvents.h" |
| #include "core/loader/FrameLoader.h" |
| #include "core/loader/FrameLoaderClient.h" |
| #include "core/origin_trials/OriginTrialContext.h" |
| #include "core/workers/WorkerGlobalScope.h" |
| #include "core/workers/WorkletGlobalScope.h" |
| #include "core/xml/XPathNSResolver.h" |
| #include "platform/TracedValue.h" |
| #include "wtf/MathExtras.h" |
| #include "wtf/StdLibExtras.h" |
| #include "wtf/Threading.h" |
| #include "wtf/text/AtomicString.h" |
| #include "wtf/text/CString.h" |
| #include "wtf/text/CharacterNames.h" |
| #include "wtf/text/StringBuffer.h" |
| #include "wtf/text/StringHash.h" |
| #include "wtf/text/Unicode.h" |
| #include "wtf/text/WTFString.h" |
| |
| namespace blink { |
| |
| void setArityTypeError(ExceptionState& exceptionState, const char* valid, unsigned provided) |
| { |
| exceptionState.throwTypeError(ExceptionMessages::invalidArity(valid, provided)); |
| } |
| |
| v8::Local<v8::Value> createMinimumArityTypeErrorForMethod(v8::Isolate* isolate, const char* method, const char* type, unsigned expected, unsigned provided) |
| { |
| return V8ThrowException::createTypeError(isolate, ExceptionMessages::failedToExecute(method, type, ExceptionMessages::notEnoughArguments(expected, provided))); |
| } |
| |
| v8::Local<v8::Value> createMinimumArityTypeErrorForConstructor(v8::Isolate* isolate, const char* type, unsigned expected, unsigned provided) |
| { |
| return V8ThrowException::createTypeError(isolate, ExceptionMessages::failedToConstruct(type, ExceptionMessages::notEnoughArguments(expected, provided))); |
| } |
| |
| void setMinimumArityTypeError(ExceptionState& exceptionState, unsigned expected, unsigned provided) |
| { |
| exceptionState.throwTypeError(ExceptionMessages::notEnoughArguments(expected, provided)); |
| } |
| |
| NodeFilter* toNodeFilter(v8::Local<v8::Value> callback, v8::Local<v8::Object> creationContext, ScriptState* scriptState) |
| { |
| if (callback->IsNull()) |
| return nullptr; |
| NodeFilter* filter = NodeFilter::create(); |
| |
| v8::Local<v8::Value> filterWrapper = toV8(filter, creationContext, scriptState->isolate()); |
| if (filterWrapper.IsEmpty()) |
| return nullptr; |
| |
| NodeFilterCondition* condition = V8NodeFilterCondition::create(callback, filterWrapper.As<v8::Object>(), scriptState); |
| filter->setCondition(condition); |
| |
| return filter; |
| } |
| |
| bool toBooleanSlow(v8::Isolate* isolate, v8::Local<v8::Value> value, ExceptionState& exceptionState) |
| { |
| ASSERT(!value->IsBoolean()); |
| v8::TryCatch block(isolate); |
| bool result = false; |
| if (!v8Call(value->BooleanValue(isolate->GetCurrentContext()), result, block)) |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return result; |
| } |
| |
| const int32_t kMaxInt32 = 0x7fffffff; |
| const int32_t kMinInt32 = -kMaxInt32 - 1; |
| const uint32_t kMaxUInt32 = 0xffffffff; |
| const int64_t kJSMaxInteger = 0x20000000000000LL - 1; // 2^53 - 1, maximum uniquely representable integer in ECMAScript. |
| |
| static double enforceRange(double x, double minimum, double maximum, const char* typeName, ExceptionState& exceptionState) |
| { |
| if (std::isnan(x) || std::isinf(x)) { |
| exceptionState.throwTypeError("Value is" + String(std::isinf(x) ? " infinite and" : "") + " not of type '" + String(typeName) + "'."); |
| return 0; |
| } |
| x = trunc(x); |
| if (x < minimum || x > maximum) { |
| exceptionState.throwTypeError("Value is outside the '" + String(typeName) + "' value range."); |
| 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(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, const char* typeName, ExceptionState& exceptionState) |
| { |
| typedef IntTypeLimits<T> LimitsTrait; |
| |
| // Fast case. The value is already a 32-bit integer in the right range. |
| if (value->IsInt32()) { |
| int32_t result = value.As<v8::Int32>()->Value(); |
| if (result >= LimitsTrait::minValue && result <= LimitsTrait::maxValue) |
| return static_cast<T>(result); |
| if (configuration == EnforceRange) { |
| exceptionState.throwTypeError("Value is outside the '" + String(typeName) + "' value range."); |
| return 0; |
| } |
| if (configuration == Clamp) |
| return clampTo<T>(result); |
| result %= LimitsTrait::numberOfValues; |
| return static_cast<T>(result > LimitsTrait::maxValue ? result - LimitsTrait::numberOfValues : result); |
| } |
| |
| v8::Local<v8::Number> numberObject; |
| if (value->IsNumber()) { |
| numberObject = value.As<v8::Number>(); |
| } else { |
| // Can the value be converted to a number? |
| v8::TryCatch block(isolate); |
| if (!v8Call(value->ToNumber(isolate->GetCurrentContext()), numberObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| } |
| ASSERT(!numberObject.IsEmpty()); |
| |
| if (configuration == EnforceRange) |
| return enforceRange(numberObject->Value(), LimitsTrait::minValue, LimitsTrait::maxValue, typeName, exceptionState); |
| |
| double numberValue = numberObject->Value(); |
| if (std::isnan(numberValue) || !numberValue) |
| return 0; |
| |
| if (configuration == Clamp) |
| return clampTo<T>(numberValue); |
| |
| if (std::isinf(numberValue)) |
| return 0; |
| |
| numberValue = numberValue < 0 ? -floor(fabs(numberValue)) : floor(fabs(numberValue)); |
| numberValue = fmod(numberValue, LimitsTrait::numberOfValues); |
| |
| return static_cast<T>(numberValue > LimitsTrait::maxValue ? numberValue - LimitsTrait::numberOfValues : numberValue); |
| } |
| |
| template <typename T> |
| static inline T toSmallerUInt(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, const char* typeName, ExceptionState& exceptionState) |
| { |
| typedef IntTypeLimits<T> LimitsTrait; |
| |
| // Fast case. The value is a 32-bit signed integer - possibly positive? |
| if (value->IsInt32()) { |
| int32_t result = value.As<v8::Int32>()->Value(); |
| if (result >= 0 && result <= LimitsTrait::maxValue) |
| return static_cast<T>(result); |
| if (configuration == EnforceRange) { |
| exceptionState.throwTypeError("Value is outside the '" + String(typeName) + "' value range."); |
| return 0; |
| } |
| if (configuration == Clamp) |
| return clampTo<T>(result); |
| return static_cast<T>(result); |
| } |
| |
| v8::Local<v8::Number> numberObject; |
| if (value->IsNumber()) { |
| numberObject = value.As<v8::Number>(); |
| } else { |
| // Can the value be converted to a number? |
| v8::TryCatch block(isolate); |
| if (!v8Call(value->ToNumber(isolate->GetCurrentContext()), numberObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| } |
| ASSERT(!numberObject.IsEmpty()); |
| |
| if (configuration == EnforceRange) |
| return enforceRange(numberObject->Value(), 0, LimitsTrait::maxValue, typeName, exceptionState); |
| |
| double numberValue = numberObject->Value(); |
| |
| if (std::isnan(numberValue) || !numberValue) |
| return 0; |
| |
| if (configuration == Clamp) |
| return clampTo<T>(numberValue); |
| |
| if (std::isinf(numberValue)) |
| return 0; |
| |
| numberValue = numberValue < 0 ? -floor(fabs(numberValue)) : floor(fabs(numberValue)); |
| return static_cast<T>(fmod(numberValue, LimitsTrait::numberOfValues)); |
| } |
| |
| int8_t toInt8(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| return toSmallerInt<int8_t>(isolate, value, configuration, "byte", exceptionState); |
| } |
| |
| uint8_t toUInt8(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| return toSmallerUInt<uint8_t>(isolate, value, configuration, "octet", exceptionState); |
| } |
| |
| int16_t toInt16(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| return toSmallerInt<int16_t>(isolate, value, configuration, "short", exceptionState); |
| } |
| |
| uint16_t toUInt16(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| return toSmallerUInt<uint16_t>(isolate, value, configuration, "unsigned short", exceptionState); |
| } |
| |
| int32_t toInt32Slow(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| ASSERT(!value->IsInt32()); |
| // Can the value be converted to a number? |
| v8::TryCatch block(isolate); |
| v8::Local<v8::Number> numberObject; |
| if (!v8Call(value->ToNumber(isolate->GetCurrentContext()), numberObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| |
| ASSERT(!numberObject.IsEmpty()); |
| |
| double numberValue = numberObject->Value(); |
| if (configuration == EnforceRange) |
| return enforceRange(numberValue, kMinInt32, kMaxInt32, "long", exceptionState); |
| |
| if (std::isnan(numberValue)) |
| return 0; |
| |
| if (configuration == Clamp) |
| return clampTo<int32_t>(numberValue); |
| |
| if (std::isinf(numberValue)) |
| return 0; |
| |
| int32_t result; |
| if (!v8Call(numberObject->Int32Value(isolate->GetCurrentContext()), result, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| return result; |
| } |
| |
| uint32_t toUInt32Slow(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| ASSERT(!value->IsUint32()); |
| if (value->IsInt32()) { |
| ASSERT(configuration != NormalConversion); |
| int32_t result = value.As<v8::Int32>()->Value(); |
| if (result >= 0) |
| return result; |
| if (configuration == EnforceRange) { |
| exceptionState.throwTypeError("Value is outside the 'unsigned long' value range."); |
| return 0; |
| } |
| ASSERT(configuration == Clamp); |
| return clampTo<uint32_t>(result); |
| } |
| |
| // Can the value be converted to a number? |
| v8::TryCatch block(isolate); |
| v8::Local<v8::Number> numberObject; |
| if (!v8Call(value->ToNumber(isolate->GetCurrentContext()), numberObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| ASSERT(!numberObject.IsEmpty()); |
| |
| if (configuration == EnforceRange) |
| return enforceRange(numberObject->Value(), 0, kMaxUInt32, "unsigned long", exceptionState); |
| |
| double numberValue = numberObject->Value(); |
| |
| if (std::isnan(numberValue)) |
| return 0; |
| |
| if (configuration == Clamp) |
| return clampTo<uint32_t>(numberValue); |
| |
| if (std::isinf(numberValue)) |
| return 0; |
| |
| uint32_t result; |
| if (!v8Call(numberObject->Uint32Value(isolate->GetCurrentContext()), result, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| return result; |
| } |
| |
| int64_t toInt64Slow(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| ASSERT(!value->IsInt32()); |
| |
| v8::Local<v8::Number> numberObject; |
| // Can the value be converted to a number? |
| v8::TryCatch block(isolate); |
| if (!v8Call(value->ToNumber(isolate->GetCurrentContext()), numberObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| ASSERT(!numberObject.IsEmpty()); |
| |
| double numberValue = numberObject->Value(); |
| |
| if (configuration == EnforceRange) |
| return enforceRange(numberValue, -kJSMaxInteger, kJSMaxInteger, "long long", exceptionState); |
| |
| if (std::isnan(numberValue) || std::isinf(numberValue)) |
| return 0; |
| |
| // NaNs and +/-Infinity should be 0, otherwise modulo 2^64. |
| unsigned long long integer; |
| doubleToInteger(numberValue, integer); |
| return integer; |
| } |
| |
| uint64_t toUInt64Slow(v8::Isolate* isolate, v8::Local<v8::Value> value, IntegerConversionConfiguration configuration, ExceptionState& exceptionState) |
| { |
| ASSERT(!value->IsUint32()); |
| if (value->IsInt32()) { |
| ASSERT(configuration != NormalConversion); |
| int32_t result = value.As<v8::Int32>()->Value(); |
| if (result >= 0) |
| return result; |
| if (configuration == EnforceRange) { |
| exceptionState.throwTypeError("Value is outside the 'unsigned long long' value range."); |
| return 0; |
| } |
| ASSERT(configuration == Clamp); |
| return clampTo<uint64_t>(result); |
| } |
| |
| v8::Local<v8::Number> numberObject; |
| // Can the value be converted to a number? |
| v8::TryCatch block(isolate); |
| if (!v8Call(value->ToNumber(isolate->GetCurrentContext()), numberObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| ASSERT(!numberObject.IsEmpty()); |
| |
| double numberValue = numberObject->Value(); |
| |
| if (configuration == EnforceRange) |
| return enforceRange(numberValue, 0, kJSMaxInteger, "unsigned long long", exceptionState); |
| |
| if (std::isnan(numberValue)) |
| return 0; |
| |
| if (configuration == Clamp) |
| return clampTo<uint64_t>(numberValue); |
| |
| if (std::isinf(numberValue)) |
| return 0; |
| |
| // NaNs and +/-Infinity should be 0, otherwise modulo 2^64. |
| unsigned long long integer; |
| doubleToInteger(numberValue, integer); |
| return integer; |
| } |
| |
| float toRestrictedFloat(v8::Isolate* isolate, v8::Local<v8::Value> value, ExceptionState& exceptionState) |
| { |
| float numberValue = toFloat(isolate, value, exceptionState); |
| if (exceptionState.hadException()) |
| return 0; |
| if (!std::isfinite(numberValue)) { |
| exceptionState.throwTypeError("The provided float value is non-finite."); |
| return 0; |
| } |
| return numberValue; |
| } |
| |
| double toDoubleSlow(v8::Isolate* isolate, v8::Local<v8::Value> value, ExceptionState& exceptionState) |
| { |
| ASSERT(!value->IsNumber()); |
| v8::TryCatch block(isolate); |
| double doubleValue; |
| if (!v8Call(value->NumberValue(isolate->GetCurrentContext()), doubleValue, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return 0; |
| } |
| return doubleValue; |
| } |
| |
| double toRestrictedDouble(v8::Isolate* isolate, v8::Local<v8::Value> value, ExceptionState& exceptionState) |
| { |
| double numberValue = toDouble(isolate, value, exceptionState); |
| if (exceptionState.hadException()) |
| return 0; |
| if (!std::isfinite(numberValue)) { |
| exceptionState.throwTypeError("The provided double value is non-finite."); |
| return 0; |
| } |
| return numberValue; |
| } |
| |
| String toByteString(v8::Isolate* isolate, v8::Local<v8::Value> value, ExceptionState& exceptionState) |
| { |
| // Handle null default value. |
| if (value.IsEmpty()) |
| return String(); |
| |
| // From the Web IDL spec: http://heycam.github.io/webidl/#es-ByteString |
| if (value.IsEmpty()) |
| return String(); |
| |
| // 1. Let x be ToString(v) |
| v8::Local<v8::String> stringObject; |
| if (value->IsString()) { |
| stringObject = value.As<v8::String>(); |
| } else { |
| v8::TryCatch block(isolate); |
| if (!v8Call(value->ToString(isolate->GetCurrentContext()), stringObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return String(); |
| } |
| } |
| |
| String x = toCoreString(stringObject); |
| |
| // 2. If the value of any element of x is greater than 255, then throw a TypeError. |
| if (!x.containsOnlyLatin1()) { |
| exceptionState.throwTypeError("Value is not a valid ByteString."); |
| return String(); |
| } |
| |
| // 3. Return an IDL ByteString value whose length is the length of x, and where the |
| // value of each element is the value of the corresponding element of x. |
| // Blink: A ByteString is simply a String with a range constrained per the above, so |
| // this is the identity operation. |
| return x; |
| } |
| |
| static bool hasUnmatchedSurrogates(const String& string) |
| { |
| // By definition, 8-bit strings are confined to the Latin-1 code page and |
| // have no surrogates, matched or otherwise. |
| if (string.is8Bit()) |
| return false; |
| |
| const UChar* characters = string.characters16(); |
| const unsigned length = string.length(); |
| |
| for (unsigned i = 0; i < length; ++i) { |
| UChar c = characters[i]; |
| if (U16_IS_SINGLE(c)) |
| continue; |
| if (U16_IS_TRAIL(c)) |
| return true; |
| ASSERT(U16_IS_LEAD(c)); |
| if (i == length - 1) |
| return true; |
| UChar d = characters[i + 1]; |
| if (!U16_IS_TRAIL(d)) |
| return true; |
| ++i; |
| } |
| return false; |
| } |
| |
| // Replace unmatched surrogates with REPLACEMENT CHARACTER U+FFFD. |
| static String replaceUnmatchedSurrogates(const String& string) |
| { |
| // This roughly implements http://heycam.github.io/webidl/#dfn-obtain-unicode |
| // but since Blink strings are 16-bits internally, the output is simply |
| // re-encoded to UTF-16. |
| |
| // The concept of surrogate pairs is explained at: |
| // http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf#G2630 |
| |
| // Blink-specific optimization to avoid making an unnecessary copy. |
| if (!hasUnmatchedSurrogates(string)) |
| return string; |
| ASSERT(!string.is8Bit()); |
| |
| // 1. Let S be the DOMString value. |
| const UChar* s = string.characters16(); |
| |
| // 2. Let n be the length of S. |
| const unsigned n = string.length(); |
| |
| // 3. Initialize i to 0. |
| unsigned i = 0; |
| |
| // 4. Initialize U to be an empty sequence of Unicode characters. |
| StringBuilder u; |
| u.reserveCapacity(n); |
| |
| // 5. While i < n: |
| while (i < n) { |
| // 1. Let c be the code unit in S at index i. |
| UChar c = s[i]; |
| // 2. Depending on the value of c: |
| if (U16_IS_SINGLE(c)) { |
| // c < 0xD800 or c > 0xDFFF |
| // Append to U the Unicode character with code point c. |
| u.append(c); |
| } else if (U16_IS_TRAIL(c)) { |
| // 0xDC00 <= c <= 0xDFFF |
| // Append to U a U+FFFD REPLACEMENT CHARACTER. |
| u.append(replacementCharacter); |
| } else { |
| // 0xD800 <= c <= 0xDBFF |
| ASSERT(U16_IS_LEAD(c)); |
| if (i == n - 1) { |
| // 1. If i = n-1, then append to U a U+FFFD REPLACEMENT CHARACTER. |
| u.append(replacementCharacter); |
| } else { |
| // 2. Otherwise, i < n-1: |
| ASSERT(i < n - 1); |
| // ....1. Let d be the code unit in S at index i+1. |
| UChar d = s[i + 1]; |
| if (U16_IS_TRAIL(d)) { |
| // 2. If 0xDC00 <= d <= 0xDFFF, then: |
| // ..1. Let a be c & 0x3FF. |
| // ..2. Let b be d & 0x3FF. |
| // ..3. Append to U the Unicode character with code point 2^16+2^10*a+b. |
| u.append(U16_GET_SUPPLEMENTARY(c, d)); |
| // Blink: This is equivalent to u.append(c); u.append(d); |
| ++i; |
| } else { |
| // 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a U+FFFD REPLACEMENT CHARACTER. |
| u.append(replacementCharacter); |
| } |
| } |
| } |
| // 3. Set i to i+1. |
| ++i; |
| } |
| |
| // 6. Return U. |
| ASSERT(u.length() == string.length()); |
| return u.toString(); |
| } |
| |
| String toUSVString(v8::Isolate* isolate, v8::Local<v8::Value> value, ExceptionState& exceptionState) |
| { |
| // http://heycam.github.io/webidl/#es-USVString |
| if (value.IsEmpty()) |
| return String(); |
| |
| v8::Local<v8::String> stringObject; |
| if (value->IsString()) { |
| stringObject = value.As<v8::String>(); |
| } else { |
| v8::TryCatch block(isolate); |
| if (!v8Call(value->ToString(isolate->GetCurrentContext()), stringObject, block)) { |
| exceptionState.rethrowV8Exception(block.Exception()); |
| return String(); |
| } |
| } |
| |
| // USVString is identical to DOMString except that "convert a |
| // DOMString to a sequence of Unicode characters" is used subsequently |
| // when converting to an IDL value |
| String x = toCoreString(stringObject); |
| return replaceUnmatchedSurrogates(x); |
| } |
| |
| XPathNSResolver* toXPathNSResolver(ScriptState* scriptState, v8::Local<v8::Value> value) |
| { |
| XPathNSResolver* resolver = nullptr; |
| if (V8XPathNSResolver::hasInstance(value, scriptState->isolate())) |
| resolver = V8XPathNSResolver::toImpl(v8::Local<v8::Object>::Cast(value)); |
| else if (value->IsObject()) |
| resolver = V8CustomXPathNSResolver::create(scriptState, value.As<v8::Object>()); |
| return resolver; |
| } |
| |
| DOMWindow* toDOMWindow(v8::Isolate* isolate, v8::Local<v8::Value> value) |
| { |
| if (value.IsEmpty() || !value->IsObject()) |
| return 0; |
| |
| v8::Local<v8::Object> windowWrapper = V8Window::findInstanceInPrototypeChain(v8::Local<v8::Object>::Cast(value), isolate); |
| if (!windowWrapper.IsEmpty()) |
| return V8Window::toImpl(windowWrapper); |
| return 0; |
| } |
| |
| DOMWindow* toDOMWindow(v8::Local<v8::Context> context) |
| { |
| if (context.IsEmpty()) |
| return 0; |
| return toDOMWindow(context->GetIsolate(), context->Global()); |
| } |
| |
| LocalDOMWindow* enteredDOMWindow(v8::Isolate* isolate) |
| { |
| LocalDOMWindow* window = toLocalDOMWindow(toDOMWindow(isolate->GetEnteredContext())); |
| if (!window) { |
| // We don't always have an entered DOM window, for example during microtask callbacks from V8 |
| // (where the entered context may be the DOM-in-JS context). In that case, we fall back |
| // to the current context. |
| // |
| // TODO(haraken): It's nasty to return a current window from enteredDOMWindow. |
| // All call sites should be updated so that it works even if it doesn't have |
| // an entered window. |
| window = currentDOMWindow(isolate); |
| ASSERT(window); |
| } |
| return window; |
| } |
| |
| LocalDOMWindow* currentDOMWindow(v8::Isolate* isolate) |
| { |
| return toLocalDOMWindow(toDOMWindow(isolate->GetCurrentContext())); |
| } |
| |
| ExecutionContext* toExecutionContext(v8::Local<v8::Context> context) |
| { |
| if (context.IsEmpty()) |
| return 0; |
| v8::Local<v8::Object> global = context->Global(); |
| v8::Local<v8::Object> windowWrapper = V8Window::findInstanceInPrototypeChain(global, context->GetIsolate()); |
| if (!windowWrapper.IsEmpty()) |
| return V8Window::toImpl(windowWrapper)->getExecutionContext(); |
| v8::Local<v8::Object> workerWrapper = V8WorkerGlobalScope::findInstanceInPrototypeChain(global, context->GetIsolate()); |
| if (!workerWrapper.IsEmpty()) |
| return V8WorkerGlobalScope::toImpl(workerWrapper)->getExecutionContext(); |
| v8::Local<v8::Object> workletWrapper = V8WorkletGlobalScope::findInstanceInPrototypeChain(global, context->GetIsolate()); |
| if (!workletWrapper.IsEmpty()) |
| return V8WorkletGlobalScope::toImpl(workletWrapper); |
| // FIXME: Is this line of code reachable? |
| return nullptr; |
| } |
| |
| ExecutionContext* currentExecutionContext(v8::Isolate* isolate) |
| { |
| return toExecutionContext(isolate->GetCurrentContext()); |
| } |
| |
| ExecutionContext* enteredExecutionContext(v8::Isolate* isolate) |
| { |
| ExecutionContext* context = toExecutionContext(isolate->GetEnteredContext()); |
| if (!context) { |
| // We don't always have an entered execution context, for example during microtask callbacks from V8 |
| // (where the entered context may be the DOM-in-JS context). In that case, we fall back |
| // to the current context. |
| context = currentExecutionContext(isolate); |
| ASSERT(context); |
| } |
| return context; |
| } |
| |
| Frame* toFrameIfNotDetached(v8::Local<v8::Context> context) |
| { |
| DOMWindow* window = toDOMWindow(context); |
| if (window && window->isCurrentlyDisplayedInFrame()) |
| return window->frame(); |
| // We return 0 here because |context| is detached from the Frame. If we |
| // did return |frame| we could get in trouble because the frame could be |
| // navigated to another security origin. |
| return nullptr; |
| } |
| |
| EventTarget* toEventTarget(v8::Isolate* isolate, v8::Local<v8::Value> value) |
| { |
| // We need to handle a DOMWindow specially, because a DOMWindow wrapper |
| // exists on a prototype chain of v8Value. |
| if (DOMWindow* window = toDOMWindow(isolate, value)) |
| return static_cast<EventTarget*>(window); |
| if (V8EventTarget::hasInstance(value, isolate)) { |
| v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(value); |
| return toWrapperTypeInfo(object)->toEventTarget(object); |
| } |
| return 0; |
| } |
| |
| void toFlexibleArrayBufferView(v8::Isolate* isolate, v8::Local<v8::Value> value, FlexibleArrayBufferView& result, void* storage) |
| { |
| ASSERT(value->IsArrayBufferView()); |
| v8::Local<v8::ArrayBufferView> buffer = value.As<v8::ArrayBufferView>(); |
| if (!storage) { |
| result.setFull(V8ArrayBufferView::toImpl(buffer)); |
| return; |
| } |
| size_t length = buffer->ByteLength(); |
| buffer->CopyContents(storage, length); |
| result.setSmall(storage, length); |
| } |
| |
| v8::Local<v8::Context> toV8Context(ExecutionContext* context, DOMWrapperWorld& world) |
| { |
| ASSERT(context); |
| if (context->isDocument()) { |
| if (LocalFrame* frame = toDocument(context)->frame()) |
| return toV8Context(frame, world); |
| } else if (context->isWorkerGlobalScope()) { |
| if (WorkerOrWorkletScriptController* script = toWorkerOrWorkletGlobalScope(context)->scriptController()) { |
| if (script->getScriptState()->contextIsValid()) |
| return script->getScriptState()->context(); |
| } |
| } |
| return v8::Local<v8::Context>(); |
| } |
| |
| v8::Local<v8::Context> toV8Context(Frame* frame, DOMWrapperWorld& world) |
| { |
| if (!frame) |
| return v8::Local<v8::Context>(); |
| v8::Local<v8::Context> context = toV8ContextEvenIfDetached(frame, world); |
| if (context.IsEmpty()) |
| return v8::Local<v8::Context>(); |
| ScriptState* scriptState = ScriptState::from(context); |
| if (scriptState->contextIsValid()) { |
| ASSERT(toFrameIfNotDetached(context) == frame); |
| return scriptState->context(); |
| } |
| return v8::Local<v8::Context>(); |
| } |
| |
| v8::Local<v8::Context> toV8ContextEvenIfDetached(Frame* frame, DOMWrapperWorld& world) |
| { |
| ASSERT(frame); |
| return frame->windowProxy(world)->contextIfInitialized(); |
| } |
| |
| void installOriginTrialsCore(ScriptState* scriptState) |
| { |
| // TODO(iclelland): Generate all of this logic at compile-time, based on the |
| // configuration of origin trial enabled attibutes and interfaces in IDL |
| // files. (crbug.com/615060) |
| |
| ExecutionContext* executionContext = scriptState->getExecutionContext(); |
| OriginTrialContext* originTrialContext = OriginTrialContext::from(executionContext, OriginTrialContext::DontCreateIfNotExists); |
| if (!originTrialContext) |
| return; |
| |
| if (!originTrialContext->featureBindingsInstalled("LinkServiceWorker") && (RuntimeEnabledFeatures::linkServiceWorkerEnabled() || originTrialContext->isFeatureEnabled("ForeignFetch"))) { |
| if (executionContext->isDocument()) { |
| V8HTMLLinkElement::installLinkServiceWorker(scriptState); |
| } |
| } |
| } |
| |
| namespace { |
| InstallOriginTrialsFunction s_installOriginTrialsFunction = &installOriginTrialsCore; |
| } |
| |
| void installOriginTrials(ScriptState* scriptState) |
| { |
| v8::Local<v8::Context> context = scriptState->context(); |
| ExecutionContext* executionContext = toExecutionContext(context); |
| OriginTrialContext* originTrialContext = OriginTrialContext::from(executionContext, OriginTrialContext::DontCreateIfNotExists); |
| if (!originTrialContext) |
| return; |
| |
| ScriptState::Scope scope(scriptState); |
| |
| (*s_installOriginTrialsFunction)(scriptState); |
| |
| // Mark each enabled feature as having been installed. |
| if (!originTrialContext->featureBindingsInstalled("DurableStorage") && (RuntimeEnabledFeatures::durableStorageEnabled() || originTrialContext->isFeatureEnabled("DurableStorage"))) { |
| originTrialContext->setFeatureBindingsInstalled("DurableStorage"); |
| } |
| |
| if (!originTrialContext->featureBindingsInstalled("WebBluetooth") && (RuntimeEnabledFeatures::webBluetoothEnabled() || originTrialContext->isFeatureEnabled("WebBluetooth"))) { |
| originTrialContext->setFeatureBindingsInstalled("WebBluetooth"); |
| } |
| |
| if (!originTrialContext->featureBindingsInstalled("WebUSB") && (RuntimeEnabledFeatures::webUSBEnabled() || originTrialContext->isFeatureEnabled("WebUSB"))) { |
| originTrialContext->setFeatureBindingsInstalled("WebUSB"); |
| } |
| |
| if (!originTrialContext->featureBindingsInstalled("LinkServiceWorker") && (RuntimeEnabledFeatures::linkServiceWorkerEnabled() || originTrialContext->isFeatureEnabled("ForeignFetch"))) { |
| originTrialContext->setFeatureBindingsInstalled("LinkServiceWorker"); |
| } |
| |
| if (!originTrialContext->featureBindingsInstalled("ForeignFetch") && (RuntimeEnabledFeatures::foreignFetchEnabled() || originTrialContext->isFeatureEnabled("ForeignFetch"))) { |
| originTrialContext->setFeatureBindingsInstalled("ForeignFetch"); |
| } |
| } |
| |
| InstallOriginTrialsFunction setInstallOriginTrialsFunction(InstallOriginTrialsFunction newInstallOriginTrialsFunction) |
| { |
| InstallOriginTrialsFunction originalFunction = s_installOriginTrialsFunction; |
| s_installOriginTrialsFunction = newInstallOriginTrialsFunction; |
| return originalFunction; |
| } |
| |
| void crashIfIsolateIsDead(v8::Isolate* isolate) |
| { |
| if (isolate->IsDead()) { |
| // FIXME: We temporarily deal with V8 internal error situations |
| // such as out-of-memory by crashing the renderer. |
| CRASH(); |
| } |
| } |
| |
| bool isValidEnum(const String& value, const char** validValues, size_t length, const String& enumName, ExceptionState& exceptionState) |
| { |
| for (size_t i = 0; i < length; ++i) { |
| if (value == validValues[i]) |
| return true; |
| } |
| exceptionState.throwTypeError("The provided value '" + value + "' is not a valid enum value of type " + enumName + "."); |
| return false; |
| } |
| |
| bool isValidEnum(const Vector<String>& values, const char** validValues, size_t length, const String& enumName, ExceptionState& exceptionState) |
| { |
| for (auto value : values) { |
| if (!isValidEnum(value, validValues, length, enumName, exceptionState)) |
| return false; |
| } |
| return true; |
| } |
| |
| v8::Local<v8::Function> getBoundFunction(v8::Local<v8::Function> function) |
| { |
| v8::Local<v8::Value> boundFunction = function->GetBoundFunction(); |
| return boundFunction->IsFunction() ? v8::Local<v8::Function>::Cast(boundFunction) : function; |
| } |
| |
| bool addHiddenValueToArray(v8::Isolate* isolate, v8::Local<v8::Object> object, v8::Local<v8::Value> value, int arrayIndex) |
| { |
| ASSERT(!value.IsEmpty()); |
| v8::Local<v8::Value> arrayValue = object->GetInternalField(arrayIndex); |
| if (arrayValue->IsNull() || arrayValue->IsUndefined()) { |
| arrayValue = v8::Array::New(isolate); |
| object->SetInternalField(arrayIndex, arrayValue); |
| } |
| |
| v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(arrayValue); |
| return v8CallBoolean(array->CreateDataProperty(isolate->GetCurrentContext(), array->Length(), value)); |
| } |
| |
| void removeHiddenValueFromArray(v8::Isolate* isolate, v8::Local<v8::Object> object, v8::Local<v8::Value> value, int arrayIndex) |
| { |
| v8::Local<v8::Value> arrayValue = object->GetInternalField(arrayIndex); |
| if (!arrayValue->IsArray()) |
| return; |
| v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(arrayValue); |
| for (int i = array->Length() - 1; i >= 0; --i) { |
| v8::Local<v8::Value> item; |
| if (!array->Get(isolate->GetCurrentContext(), i).ToLocal(&item)) |
| return; |
| if (item->StrictEquals(value)) { |
| array->Delete(isolate->GetCurrentContext(), i); |
| return; |
| } |
| } |
| } |
| |
| void moveEventListenerToNewWrapper(v8::Isolate* isolate, v8::Local<v8::Object> object, EventListener* oldValue, v8::Local<v8::Value> newValue, int arrayIndex) |
| { |
| if (oldValue) { |
| V8AbstractEventListener* oldListener = V8AbstractEventListener::cast(oldValue); |
| if (oldListener) { |
| v8::Local<v8::Object> oldListenerObject = oldListener->getExistingListenerObject(); |
| if (!oldListenerObject.IsEmpty()) |
| removeHiddenValueFromArray(isolate, object, oldListenerObject, arrayIndex); |
| } |
| } |
| // Non-callable input is treated as null and ignored |
| if (newValue->IsFunction()) |
| addHiddenValueToArray(isolate, object, newValue, arrayIndex); |
| } |
| |
| v8::Isolate* toIsolate(ExecutionContext* context) |
| { |
| if (context && context->isDocument()) |
| return V8PerIsolateData::mainThreadIsolate(); |
| return v8::Isolate::GetCurrent(); |
| } |
| |
| v8::Isolate* toIsolate(LocalFrame* frame) |
| { |
| ASSERT(frame); |
| return frame->script().isolate(); |
| } |
| |
| v8::Local<v8::Value> freezeV8Object(v8::Local<v8::Value> value, v8::Isolate* isolate) |
| { |
| value.As<v8::Object>()->SetIntegrityLevel(isolate->GetCurrentContext(), v8::IntegrityLevel::kFrozen).ToChecked(); |
| return value; |
| } |
| |
| } // namespace blink |