| // Copyright 2016 PDFium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com |
| |
| #include "fxjs/xfa/cfxjse_class.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "fxjs/cfx_v8.h" |
| #include "fxjs/cjs_result.h" |
| #include "fxjs/fxv8.h" |
| #include "fxjs/js_resources.h" |
| #include "fxjs/xfa/cfxjse_context.h" |
| #include "fxjs/xfa/cfxjse_isolatetracker.h" |
| #include "fxjs/xfa/cfxjse_value.h" |
| #include "third_party/base/check.h" |
| #include "third_party/base/check_op.h" |
| |
| using pdfium::fxjse::kClassTag; |
| using pdfium::fxjse::kFuncTag; |
| |
| namespace { |
| |
| FXJSE_FUNCTION_DESCRIPTOR* AsFunctionDescriptor(void* ptr) { |
| auto* result = static_cast<FXJSE_FUNCTION_DESCRIPTOR*>(ptr); |
| return result && result->tag == kFuncTag ? result : nullptr; |
| } |
| |
| FXJSE_CLASS_DESCRIPTOR* AsClassDescriptor(void* ptr) { |
| auto* result = static_cast<FXJSE_CLASS_DESCRIPTOR*>(ptr); |
| return result && result->tag == kClassTag ? result : nullptr; |
| } |
| |
| void V8FunctionCallback_Wrapper( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| const FXJSE_FUNCTION_DESCRIPTOR* lpFunctionInfo = |
| AsFunctionDescriptor(info.Data().As<v8::External>()->Value()); |
| if (!lpFunctionInfo) |
| return; |
| |
| lpFunctionInfo->callbackProc(CFXJSE_HostObject::FromV8(info.Holder()), info); |
| } |
| |
| void V8ConstructorCallback_Wrapper( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| if (!info.IsConstructCall()) |
| return; |
| |
| const FXJSE_CLASS_DESCRIPTOR* lpClassDefinition = |
| AsClassDescriptor(info.Data().As<v8::External>()->Value()); |
| if (!lpClassDefinition) |
| return; |
| |
| DCHECK_EQ(info.Holder()->InternalFieldCount(), 2); |
| info.Holder()->SetAlignedPointerInInternalField(0, nullptr); |
| info.Holder()->SetAlignedPointerInInternalField(1, nullptr); |
| } |
| |
| void Context_GlobalObjToString( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| const FXJSE_CLASS_DESCRIPTOR* lpClass = |
| AsClassDescriptor(info.Data().As<v8::External>()->Value()); |
| if (!lpClass) |
| return; |
| |
| if (info.This() == info.Holder() && lpClass->name) { |
| ByteString szStringVal = ByteString::Format("[object %s]", lpClass->name); |
| info.GetReturnValue().Set( |
| fxv8::NewStringHelper(info.GetIsolate(), szStringVal.AsStringView())); |
| return; |
| } |
| v8::Local<v8::String> local_str = |
| info.Holder() |
| ->ObjectProtoToString(info.GetIsolate()->GetCurrentContext()) |
| .FromMaybe(v8::Local<v8::String>()); |
| info.GetReturnValue().Set(local_str); |
| } |
| |
| void DynPropGetterAdapter_MethodCallback( |
| const v8::FunctionCallbackInfo<v8::Value>& info) { |
| v8::Local<v8::Object> hCallBackInfo = info.Data().As<v8::Object>(); |
| if (hCallBackInfo->InternalFieldCount() != 2) |
| return; |
| |
| auto* pClassDescriptor = static_cast<const FXJSE_CLASS_DESCRIPTOR*>( |
| hCallBackInfo->GetAlignedPointerFromInternalField(0)); |
| if (pClassDescriptor != &GlobalClassDescriptor && |
| pClassDescriptor != &NormalClassDescriptor && |
| pClassDescriptor != &VariablesClassDescriptor && |
| pClassDescriptor != &kFormCalcFM2JSDescriptor) { |
| return; |
| } |
| |
| v8::Local<v8::String> hPropName = |
| hCallBackInfo->GetInternalField(1).As<v8::String>(); |
| if (hPropName.IsEmpty()) |
| return; |
| |
| v8::String::Utf8Value szPropName(info.GetIsolate(), hPropName); |
| CJS_Result result = |
| pClassDescriptor->dynMethodCall(info, WideString::FromUTF8(*szPropName)); |
| |
| if (result.HasError()) { |
| WideString err = JSFormatErrorString(pClassDescriptor->name, *szPropName, |
| result.Error()); |
| fxv8::ThrowExceptionHelper(info.GetIsolate(), err.AsStringView()); |
| return; |
| } |
| |
| if (result.HasReturn()) |
| info.GetReturnValue().Set(result.Return()); |
| } |
| |
| void DynPropGetterAdapter(v8::Isolate* pIsolate, |
| const FXJSE_CLASS_DESCRIPTOR* lpClass, |
| v8::Local<v8::Object> pObject, |
| ByteStringView szPropName, |
| CFXJSE_Value* pValue) { |
| DCHECK(lpClass); |
| |
| int32_t nPropType = |
| lpClass->dynPropTypeGetter == nullptr |
| ? FXJSE_ClassPropType_Property |
| : lpClass->dynPropTypeGetter(pIsolate, pObject, szPropName, false); |
| if (nPropType == FXJSE_ClassPropType_Property) { |
| if (lpClass->dynPropGetter) { |
| pValue->ForceSetValue( |
| pIsolate, lpClass->dynPropGetter(pIsolate, pObject, szPropName)); |
| } |
| } else if (nPropType == FXJSE_ClassPropType_Method) { |
| if (lpClass->dynMethodCall && pValue) { |
| v8::HandleScope hscope(pIsolate); |
| v8::Local<v8::ObjectTemplate> hCallBackInfoTemplate = |
| v8::ObjectTemplate::New(pIsolate); |
| hCallBackInfoTemplate->SetInternalFieldCount(2); |
| v8::Local<v8::Object> hCallBackInfo = |
| hCallBackInfoTemplate->NewInstance(pIsolate->GetCurrentContext()) |
| .ToLocalChecked(); |
| hCallBackInfo->SetAlignedPointerInInternalField( |
| 0, const_cast<FXJSE_CLASS_DESCRIPTOR*>(lpClass)); |
| hCallBackInfo->SetInternalField( |
| 1, fxv8::NewStringHelper(pIsolate, szPropName)); |
| pValue->ForceSetValue( |
| pIsolate, |
| v8::Function::New(pIsolate->GetCurrentContext(), |
| DynPropGetterAdapter_MethodCallback, hCallBackInfo, |
| 0, v8::ConstructorBehavior::kThrow) |
| .ToLocalChecked()); |
| } |
| } |
| } |
| |
| void DynPropSetterAdapter(v8::Isolate* pIsolate, |
| const FXJSE_CLASS_DESCRIPTOR* lpClass, |
| v8::Local<v8::Object> pObject, |
| ByteStringView szPropName, |
| CFXJSE_Value* pValue) { |
| DCHECK(lpClass); |
| int32_t nPropType = |
| lpClass->dynPropTypeGetter == nullptr |
| ? FXJSE_ClassPropType_Property |
| : lpClass->dynPropTypeGetter(pIsolate, pObject, szPropName, false); |
| if (nPropType != FXJSE_ClassPropType_Method) { |
| if (lpClass->dynPropSetter) { |
| lpClass->dynPropSetter(pIsolate, pObject, szPropName, |
| pValue->GetValue(pIsolate)); |
| } |
| } |
| } |
| |
| bool DynPropQueryAdapter(v8::Isolate* pIsolate, |
| const FXJSE_CLASS_DESCRIPTOR* lpClass, |
| v8::Local<v8::Object> pObject, |
| ByteStringView szPropName) { |
| DCHECK(lpClass); |
| int32_t nPropType = |
| lpClass->dynPropTypeGetter == nullptr |
| ? FXJSE_ClassPropType_Property |
| : lpClass->dynPropTypeGetter(pIsolate, pObject, szPropName, true); |
| return nPropType != FXJSE_ClassPropType_None; |
| } |
| |
| void NamedPropertyQueryCallback( |
| v8::Local<v8::Name> property, |
| const v8::PropertyCallbackInfo<v8::Integer>& info) { |
| const FXJSE_CLASS_DESCRIPTOR* pClass = |
| AsClassDescriptor(info.Data().As<v8::External>()->Value()); |
| if (!pClass) |
| return; |
| |
| v8::HandleScope scope(info.GetIsolate()); |
| v8::String::Utf8Value szPropName(info.GetIsolate(), property); |
| ByteStringView szFxPropName(*szPropName, szPropName.length()); |
| if (DynPropQueryAdapter(info.GetIsolate(), pClass, info.Holder(), |
| szFxPropName)) { |
| info.GetReturnValue().Set(v8::DontDelete); |
| return; |
| } |
| const int32_t iV8Absent = 64; |
| info.GetReturnValue().Set(iV8Absent); |
| } |
| |
| void NamedPropertyGetterCallback( |
| v8::Local<v8::Name> property, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| const FXJSE_CLASS_DESCRIPTOR* pClass = |
| AsClassDescriptor(info.Data().As<v8::External>()->Value()); |
| if (!pClass) |
| return; |
| |
| v8::String::Utf8Value szPropName(info.GetIsolate(), property); |
| ByteStringView szFxPropName(*szPropName, szPropName.length()); |
| auto pNewValue = std::make_unique<CFXJSE_Value>(); |
| DynPropGetterAdapter(info.GetIsolate(), pClass, info.Holder(), szFxPropName, |
| pNewValue.get()); |
| info.GetReturnValue().Set(pNewValue->DirectGetValue()); |
| } |
| |
| void NamedPropertySetterCallback( |
| v8::Local<v8::Name> property, |
| v8::Local<v8::Value> value, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| const FXJSE_CLASS_DESCRIPTOR* pClass = |
| AsClassDescriptor(info.Data().As<v8::External>()->Value()); |
| if (!pClass) |
| return; |
| |
| v8::String::Utf8Value szPropName(info.GetIsolate(), property); |
| ByteStringView szFxPropName(*szPropName, szPropName.length()); |
| auto pNewValue = std::make_unique<CFXJSE_Value>(info.GetIsolate(), value); |
| DynPropSetterAdapter(info.GetIsolate(), pClass, info.Holder(), szFxPropName, |
| pNewValue.get()); |
| info.GetReturnValue().Set(value); |
| } |
| |
| void NamedPropertyEnumeratorCallback( |
| const v8::PropertyCallbackInfo<v8::Array>& info) { |
| info.GetReturnValue().Set(v8::Array::New(info.GetIsolate())); |
| } |
| |
| void SetUpNamedPropHandler(v8::Isolate* pIsolate, |
| v8::Local<v8::ObjectTemplate>* pObjectTemplate, |
| const FXJSE_CLASS_DESCRIPTOR* lpClassDefinition) { |
| v8::NamedPropertyHandlerConfiguration configuration( |
| lpClassDefinition->dynPropGetter ? NamedPropertyGetterCallback : nullptr, |
| lpClassDefinition->dynPropSetter ? NamedPropertySetterCallback : nullptr, |
| lpClassDefinition->dynPropTypeGetter ? NamedPropertyQueryCallback |
| : nullptr, |
| nullptr, NamedPropertyEnumeratorCallback, |
| v8::External::New(pIsolate, |
| const_cast<FXJSE_CLASS_DESCRIPTOR*>(lpClassDefinition)), |
| v8::PropertyHandlerFlags::kNonMasking); |
| (*pObjectTemplate)->SetHandler(configuration); |
| } |
| |
| } // namespace |
| |
| // static |
| CFXJSE_Class* CFXJSE_Class::Create( |
| CFXJSE_Context* lpContext, |
| const FXJSE_CLASS_DESCRIPTOR* lpClassDefinition, |
| bool bIsJSGlobal) { |
| if (!lpContext || !lpClassDefinition) |
| return nullptr; |
| |
| CFXJSE_Class* pExistingClass = |
| lpContext->GetClassByName(lpClassDefinition->name); |
| if (pExistingClass) |
| return pExistingClass; |
| |
| v8::Isolate* pIsolate = lpContext->GetIsolate(); |
| auto pClass = std::make_unique<CFXJSE_Class>(lpContext); |
| pClass->m_szClassName = lpClassDefinition->name; |
| pClass->m_lpClassDefinition = lpClassDefinition; |
| CFXJSE_ScopeUtil_IsolateHandleRootContext scope(pIsolate); |
| v8::Local<v8::FunctionTemplate> hFunctionTemplate = v8::FunctionTemplate::New( |
| pIsolate, bIsJSGlobal ? 0 : V8ConstructorCallback_Wrapper, |
| v8::External::New( |
| pIsolate, const_cast<FXJSE_CLASS_DESCRIPTOR*>(lpClassDefinition))); |
| v8::Local<v8::String> classname = |
| fxv8::NewStringHelper(pIsolate, lpClassDefinition->name); |
| hFunctionTemplate->SetClassName(classname); |
| hFunctionTemplate->PrototypeTemplate()->Set( |
| v8::Symbol::GetToStringTag(pIsolate), classname, |
| static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum)); |
| hFunctionTemplate->InstanceTemplate()->SetInternalFieldCount(2); |
| v8::Local<v8::ObjectTemplate> hObjectTemplate = |
| hFunctionTemplate->InstanceTemplate(); |
| SetUpNamedPropHandler(pIsolate, &hObjectTemplate, lpClassDefinition); |
| |
| if (lpClassDefinition->methNum) { |
| for (int32_t i = 0; i < lpClassDefinition->methNum; i++) { |
| v8::Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New( |
| pIsolate, V8FunctionCallback_Wrapper, |
| v8::External::New(pIsolate, const_cast<FXJSE_FUNCTION_DESCRIPTOR*>( |
| lpClassDefinition->methods + i))); |
| fun->RemovePrototype(); |
| hObjectTemplate->Set( |
| fxv8::NewStringHelper(pIsolate, lpClassDefinition->methods[i].name), |
| fun, |
| static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete)); |
| } |
| } |
| |
| if (bIsJSGlobal) { |
| v8::Local<v8::FunctionTemplate> fn = v8::FunctionTemplate::New( |
| pIsolate, Context_GlobalObjToString, |
| v8::External::New( |
| pIsolate, const_cast<FXJSE_CLASS_DESCRIPTOR*>(lpClassDefinition))); |
| fn->RemovePrototype(); |
| hObjectTemplate->Set(fxv8::NewStringHelper(pIsolate, "toString"), fn); |
| } |
| pClass->m_hTemplate.Reset(lpContext->GetIsolate(), hFunctionTemplate); |
| CFXJSE_Class* pResult = pClass.get(); |
| lpContext->AddClass(std::move(pClass)); |
| return pResult; |
| } |
| |
| CFXJSE_Class::CFXJSE_Class(const CFXJSE_Context* lpContext) |
| : m_pContext(lpContext) {} |
| |
| CFXJSE_Class::~CFXJSE_Class() = default; |
| |
| v8::Local<v8::FunctionTemplate> CFXJSE_Class::GetTemplate( |
| v8::Isolate* pIsolate) { |
| return v8::Local<v8::FunctionTemplate>::New(pIsolate, m_hTemplate); |
| } |