blob: 9733d811fa888946201195d3e0ba9c9dec4c9cdf [file] [log] [blame]
// Copyright 2014 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 "../../include/javascript/IJavaScript.h"
#include "../../include/javascript/JS_Context.h"
#include "../../include/javascript/JS_Define.h"
#include "../../include/javascript/JS_EventHandler.h"
#include "../../include/javascript/JS_GlobalData.h"
#include "../../include/javascript/JS_Object.h"
#include "../../include/javascript/JS_Value.h"
#include "../../include/javascript/JavaScript.h"
#include "../../include/javascript/global.h"
#include "../../include/fpdfxfa/fpdfxfa_app.h"
#include "../../include/javascript/resource.h"
/* ---------------------------- global ---------------------------- */
// Helper class for compile-time calculation of hash values in order to
// avoid having global object initializers.
template <unsigned ACC, wchar_t... Ns>
struct CHash;
// Only needed to hash single-character strings.
template <wchar_t N>
struct CHash<N> {
static const unsigned value = N;
};
template <unsigned ACC, wchar_t N>
struct CHash<ACC, N> {
static const unsigned value = (ACC * 1313LLU + N) & 0xFFFFFFFF;
};
template <unsigned ACC, wchar_t N, wchar_t... Ns>
struct CHash<ACC, N, Ns...> {
static const unsigned value = CHash<CHash<ACC, N>::value, Ns...>::value;
};
extern const unsigned int JSCONST_nStringHash =
CHash<'s','t','r','i','n','g'>::value;
extern const unsigned int JSCONST_nNumberHash =
CHash<'n','u','m','b','e','r'>::value;
extern const unsigned int JSCONST_nBoolHash =
CHash<'b','o','o','l','e','a','n'>::value;
extern const unsigned int JSCONST_nDateHash =
CHash<'d','a','t','e'>::value;
extern const unsigned int JSCONST_nObjectHash =
CHash<'o','b','j','e','c','t'>::value;
extern const unsigned int JSCONST_nFXobjHash =
CHash<'f','x','o','b','j'>::value;
extern const unsigned int JSCONST_nNullHash =
CHash<'n','u','l','l'>::value;
extern const unsigned int JSCONST_nUndefHash =
CHash<'u','n','d','e','f','i','n','e','d'>::value;
#ifdef _DEBUG
class HashVerify
{
public:
HashVerify();
} g_hashVerify;
HashVerify::HashVerify()
{
ASSERT(JSCONST_nStringHash ==
JS_CalcHash(VALUE_NAME_STRING,wcslen(VALUE_NAME_STRING)));
ASSERT(JSCONST_nNumberHash ==
JS_CalcHash(VALUE_NAME_NUMBER,wcslen(VALUE_NAME_NUMBER)));
ASSERT(JSCONST_nBoolHash ==
JS_CalcHash(VALUE_NAME_BOOLEAN,wcslen(VALUE_NAME_BOOLEAN)));
ASSERT(JSCONST_nDateHash ==
JS_CalcHash(VALUE_NAME_DATE,wcslen(VALUE_NAME_DATE)));
ASSERT(JSCONST_nObjectHash ==
JS_CalcHash(VALUE_NAME_OBJECT,wcslen(VALUE_NAME_OBJECT)));
ASSERT(JSCONST_nFXobjHash ==
JS_CalcHash(VALUE_NAME_FXOBJ,wcslen(VALUE_NAME_FXOBJ)));
ASSERT(JSCONST_nNullHash ==
JS_CalcHash(VALUE_NAME_NULL,wcslen(VALUE_NAME_NULL)));
ASSERT(JSCONST_nUndefHash ==
JS_CalcHash(VALUE_NAME_UNDEFINED,wcslen(VALUE_NAME_UNDEFINED)));
}
#endif
BEGIN_JS_STATIC_CONST(CJS_Global)
END_JS_STATIC_CONST()
BEGIN_JS_STATIC_PROP(CJS_Global)
END_JS_STATIC_PROP()
BEGIN_JS_STATIC_METHOD(CJS_Global)
JS_STATIC_METHOD_ENTRY(setPersistent)
END_JS_STATIC_METHOD()
IMPLEMENT_SPECIAL_JS_CLASS(CJS_Global, global_alternate, global);
FX_BOOL CJS_Global::InitInstance(IFXJS_Context* cc)
{
CJS_Context* pContext = (CJS_Context*)cc;
ASSERT(pContext != NULL);
global_alternate* pGlobal = (global_alternate*)GetEmbedObject();
ASSERT(pGlobal != NULL);
pGlobal->Initial(pContext->GetReaderApp());
return TRUE;
};
global_alternate::global_alternate(CJS_Object* pJSObject)
: CJS_EmbedObj(pJSObject),
m_pApp(NULL)
{
}
global_alternate::~global_alternate(void)
{
DestroyGlobalPersisitentVariables();
CPDFXFA_App::GetInstance()->GetRuntimeFactory()->ReleaseGlobalData();
}
void global_alternate::Initial(CPDFDoc_Environment* pApp)
{
m_pApp = pApp;
m_pGlobalData = CPDFXFA_App::GetInstance()->GetRuntimeFactory()->NewGlobalData(pApp);
UpdateGlobalPersistentVariables();
}
FX_BOOL global_alternate::QueryProperty(const FX_WCHAR* propname)
{
return CFX_WideString(propname) != L"setPersistent";
}
FX_BOOL global_alternate::DelProperty(IFXJS_Context* cc, const FX_WCHAR* propname, CFX_WideString& sError)
{
js_global_data* pData = NULL;
CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname);
if (m_mapGlobal.Lookup(sPropName, (void*&)pData))
{
pData->bDeleted = TRUE;
return TRUE;
}
return FALSE;
}
FX_BOOL global_alternate::DoProperty(IFXJS_Context* cc, const FX_WCHAR* propname, CJS_PropValue& vp, CFX_WideString& sError)
{
if (vp.IsSetting())
{
CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname);
switch (vp.GetType())
{
case VT_number:
{
double dData;
vp >> dData;
return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NUMBER, dData, false, "", v8::Local<v8::Object>(), FALSE);
}
case VT_boolean:
{
bool bData;
vp >> bData;
return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_BOOLEAN, 0, bData, "", v8::Local<v8::Object>(), FALSE);
}
case VT_string:
{
CFX_ByteString sData;
vp >> sData;
return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_STRING, 0, false, sData, v8::Local<v8::Object>(), FALSE);
}
case VT_object:
{
JSObject pData;
vp >> pData;
return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_OBJECT, 0, false, "", pData, FALSE);
}
case VT_null:
{
return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NULL, 0, false, "", v8::Local<v8::Object>(), FALSE);
}
case VT_undefined:
{
DelProperty(cc, propname, sError);
return TRUE;
}
default:
break;
}
}
else
{
void* pVoid = nullptr;
if (!m_mapGlobal.Lookup(CFX_ByteString::FromUnicode(propname), pVoid))
{
vp.SetNull();
return TRUE;
}
if (!pVoid)
{
vp.SetNull();
return TRUE;
}
js_global_data* pData = (js_global_data*)pVoid;
if (pData->bDeleted)
return TRUE;
switch (pData->nType)
{
case JS_GLOBALDATA_TYPE_NUMBER:
vp << pData->dData;
return TRUE;
case JS_GLOBALDATA_TYPE_BOOLEAN:
vp << pData->bData;
return TRUE;
case JS_GLOBALDATA_TYPE_STRING:
vp << pData->sData;
return TRUE;
case JS_GLOBALDATA_TYPE_OBJECT:
{
v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(vp.GetIsolate(),pData->pData);
vp << obj;
return TRUE;
}
case JS_GLOBALDATA_TYPE_NULL:
vp.SetNull();
return TRUE;
default:
break;
}
}
return FALSE;
}
FX_BOOL global_alternate::setPersistent(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
CJS_Context* pContext = static_cast<CJS_Context*>(cc);
if (params.size() != 2)
{
sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
return FALSE;
}
CFX_ByteString sName = params[0].ToCFXByteString();
js_global_data* pData = NULL;
if (m_mapGlobal.Lookup(sName, (void*&)pData))
{
if (pData && !pData->bDeleted)
{
pData->bPersistent = params[1].ToBool();
return TRUE;
}
}
sError = JSGetStringFromID(pContext, IDS_STRING_JSNOGLOBAL);
return FALSE;
}
void global_alternate::UpdateGlobalPersistentVariables()
{
ASSERT(m_pGlobalData != NULL);
for (int i=0,sz=m_pGlobalData->GetSize(); i<sz; i++)
{
CJS_GlobalData_Element* pData = m_pGlobalData->GetAt(i);
ASSERT(pData != NULL);
switch (pData->data.nType)
{
case JS_GLOBALDATA_TYPE_NUMBER:
SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NUMBER, pData->data.dData, false, "", v8::Local<v8::Object>(), pData->bPersistent == 1);
JS_PutObjectNumber(NULL,(JSFXObject)(*m_pJSObject),
pData->data.sKey.UTF8Decode().c_str(), pData->data.dData);
break;
case JS_GLOBALDATA_TYPE_BOOLEAN:
SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_BOOLEAN, 0, (bool)(pData->data.bData == 1), "", v8::Local<v8::Object>(), pData->bPersistent == 1);
JS_PutObjectBoolean(NULL,(JSFXObject)(*m_pJSObject),
pData->data.sKey.UTF8Decode().c_str(), (bool)(pData->data.bData == 1));
break;
case JS_GLOBALDATA_TYPE_STRING:
SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_STRING, 0, false, pData->data.sData, v8::Local<v8::Object>(), pData->bPersistent == 1);
JS_PutObjectString(NULL, (JSFXObject)(*m_pJSObject),
pData->data.sKey.UTF8Decode().c_str(),
pData->data.sData.UTF8Decode().c_str());
break;
case JS_GLOBALDATA_TYPE_OBJECT:
{
IJS_Runtime* pRuntime = JS_GetRuntime((JSFXObject)(*m_pJSObject));
v8::Local<v8::Object> pObj = JS_NewFxDynamicObj(pRuntime, NULL, -1);
PutObjectProperty(pObj, &pData->data);
SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_OBJECT, 0, false, "",
(JSObject)pObj, pData->bPersistent == 1);
JS_PutObjectObject(NULL,(JSFXObject)(*m_pJSObject),
pData->data.sKey.UTF8Decode().c_str(), (JSObject)pObj);
}
break;
case JS_GLOBALDATA_TYPE_NULL:
SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NULL, 0, false, "", v8::Local<v8::Object>(), pData->bPersistent == 1);
JS_PutObjectNull(NULL,(JSFXObject)(*m_pJSObject),
pData->data.sKey.UTF8Decode().c_str());
break;
}
}
}
void global_alternate::CommitGlobalPersisitentVariables()
{
ASSERT(m_pGlobalData != NULL);
FX_POSITION pos = m_mapGlobal.GetStartPosition();
while (pos)
{
CFX_ByteString name;
js_global_data* pData = NULL;
m_mapGlobal.GetNextAssoc(pos, name, (void*&)pData);
if (pData)
{
if (pData->bDeleted)
{
m_pGlobalData->DeleteGlobalVariable(name);
}
else
{
switch (pData->nType)
{
case JS_GLOBALDATA_TYPE_NUMBER:
m_pGlobalData->SetGlobalVariableNumber(name, pData->dData);
m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
break;
case JS_GLOBALDATA_TYPE_BOOLEAN:
m_pGlobalData->SetGlobalVariableBoolean(name, pData->bData);
m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
break;
case JS_GLOBALDATA_TYPE_STRING:
m_pGlobalData->SetGlobalVariableString(name, pData->sData);
m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
break;
case JS_GLOBALDATA_TYPE_OBJECT:
//if (pData->pData)
{
CJS_GlobalVariableArray array;
v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(GetJSObject()->GetIsolate(),pData->pData);
ObjectToArray(obj, array);
m_pGlobalData->SetGlobalVariableObject(name, array);
m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
}
break;
case JS_GLOBALDATA_TYPE_NULL:
m_pGlobalData->SetGlobalVariableNull(name);
m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
break;
}
}
}
}
}
void global_alternate::ObjectToArray(v8::Local<v8::Object> pObj, CJS_GlobalVariableArray& array)
{
v8::Local<v8::Context> context = pObj->CreationContext();
v8::Isolate* isolate = context->GetIsolate();
v8::Local<v8::Array> pKeyList = JS_GetObjectElementNames(isolate, pObj);
int nObjElements = pKeyList->Length();
for (int i=0; i<nObjElements; i++)
{
CFX_WideString ws = JS_ToString(isolate, JS_GetArrayElement(isolate, pKeyList, i));
CFX_ByteString sKey = ws.UTF8Encode();
v8::Local<v8::Value> v = JS_GetObjectElement(isolate, pObj, ws.c_str());
FXJSVALUETYPE vt = GET_VALUE_TYPE(v);
switch (vt)
{
case VT_number:
{
CJS_KeyValue* pObjElement = new CJS_KeyValue;
pObjElement->nType = JS_GLOBALDATA_TYPE_NUMBER;
pObjElement->sKey = sKey;
pObjElement->dData = JS_ToNumber(isolate, v);
array.Add(pObjElement);
}
break;
case VT_boolean:
{
CJS_KeyValue* pObjElement = new CJS_KeyValue;
pObjElement->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
pObjElement->sKey = sKey;
pObjElement->dData = JS_ToBoolean(isolate, v);
array.Add(pObjElement);
}
break;
case VT_string:
{
CFX_ByteString sValue = CJS_Value(isolate, v, VT_string).ToCFXByteString();
CJS_KeyValue* pObjElement = new CJS_KeyValue;
pObjElement->nType = JS_GLOBALDATA_TYPE_STRING;
pObjElement->sKey = sKey;
pObjElement->sData = sValue;
array.Add(pObjElement);
}
break;
case VT_object:
{
CJS_KeyValue* pObjElement = new CJS_KeyValue;
pObjElement->nType = JS_GLOBALDATA_TYPE_OBJECT;
pObjElement->sKey = sKey;
ObjectToArray(JS_ToObject(isolate, v), pObjElement->objData);
array.Add(pObjElement);
}
break;
case VT_null:
{
CJS_KeyValue* pObjElement = new CJS_KeyValue;
pObjElement->nType = JS_GLOBALDATA_TYPE_NULL;
pObjElement->sKey = sKey;
array.Add(pObjElement);
}
break;
default:
break;
}
}
}
void global_alternate::PutObjectProperty(v8::Local<v8::Object> pObj, CJS_KeyValue* pData)
{
ASSERT(pData != NULL);
for (int i=0,sz=pData->objData.Count(); i<sz; i++)
{
CJS_KeyValue* pObjData = pData->objData.GetAt(i);
ASSERT(pObjData != NULL);
switch (pObjData->nType)
{
case JS_GLOBALDATA_TYPE_NUMBER:
JS_PutObjectNumber(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), pObjData->dData);
break;
case JS_GLOBALDATA_TYPE_BOOLEAN:
JS_PutObjectBoolean(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), (bool)(pObjData->bData == 1));
break;
case JS_GLOBALDATA_TYPE_STRING:
JS_PutObjectString(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), pObjData->sData.UTF8Decode().c_str());
break;
case JS_GLOBALDATA_TYPE_OBJECT:
{
IJS_Runtime* pRuntime = JS_GetRuntime((JSFXObject)(*m_pJSObject));
v8::Local<v8::Object> pNewObj = JS_NewFxDynamicObj(pRuntime, NULL, -1);
PutObjectProperty(pNewObj, pObjData);
JS_PutObjectObject(NULL, (JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), (JSObject)pNewObj);
}
break;
case JS_GLOBALDATA_TYPE_NULL:
JS_PutObjectNull(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str());
break;
}
}
}
void global_alternate::DestroyGlobalPersisitentVariables()
{
FX_POSITION pos = m_mapGlobal.GetStartPosition();
while (pos)
{
CFX_ByteString name;
js_global_data* pData = NULL;
m_mapGlobal.GetNextAssoc(pos, name, (void*&)pData);
delete pData;
}
m_mapGlobal.RemoveAll();
}
FX_BOOL global_alternate::SetGlobalVariables(const FX_CHAR* propname, int nType,
double dData, bool bData, const CFX_ByteString& sData, JSObject pData, bool bDefaultPersistent)
{
if (propname == NULL) return FALSE;
js_global_data* pTemp = NULL;
m_mapGlobal.Lookup(propname, (void*&)pTemp);
if (pTemp)
{
if (pTemp->bDeleted || pTemp->nType != nType)
{
pTemp->dData = 0;
pTemp->bData = 0;
pTemp->sData = "";
pTemp->nType = nType;
}
pTemp->bDeleted = FALSE;
switch (nType)
{
case JS_GLOBALDATA_TYPE_NUMBER:
{
pTemp->dData = dData;
}
break;
case JS_GLOBALDATA_TYPE_BOOLEAN:
{
pTemp->bData = bData;
}
break;
case JS_GLOBALDATA_TYPE_STRING:
{
pTemp->sData = sData;
}
break;
case JS_GLOBALDATA_TYPE_OBJECT:
{
pTemp->pData.Reset(JS_GetRuntime(pData), pData);
}
break;
case JS_GLOBALDATA_TYPE_NULL:
break;
default:
return FALSE;
}
return TRUE;
}
js_global_data* pNewData = NULL;
switch (nType)
{
case JS_GLOBALDATA_TYPE_NUMBER:
{
pNewData = new js_global_data;
pNewData->nType = JS_GLOBALDATA_TYPE_NUMBER;
pNewData->dData = dData;
pNewData->bPersistent = bDefaultPersistent;
}
break;
case JS_GLOBALDATA_TYPE_BOOLEAN:
{
pNewData = new js_global_data;
pNewData->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
pNewData->bData = bData;
pNewData->bPersistent = bDefaultPersistent;
}
break;
case JS_GLOBALDATA_TYPE_STRING:
{
pNewData = new js_global_data;
pNewData->nType = JS_GLOBALDATA_TYPE_STRING;
pNewData->sData = sData;
pNewData->bPersistent = bDefaultPersistent;
}
break;
case JS_GLOBALDATA_TYPE_OBJECT:
{
pNewData = new js_global_data;
pNewData->nType = JS_GLOBALDATA_TYPE_OBJECT;
pNewData->pData.Reset(JS_GetRuntime(pData), pData);
pNewData->bPersistent = bDefaultPersistent;
}
break;
case JS_GLOBALDATA_TYPE_NULL:
{
pNewData = new js_global_data;
pNewData->nType = JS_GLOBALDATA_TYPE_NULL;
pNewData->bPersistent = bDefaultPersistent;
}
break;
default:
return FALSE;
}
m_mapGlobal.SetAt(propname, (void*)pNewData);
return TRUE;
}
FXJSVALUETYPE GET_VALUE_TYPE(v8::Local<v8::Value> p)
{
const unsigned int nHash = JS_CalcHash(JS_GetTypeof(p));
if (nHash == JSCONST_nUndefHash)
return VT_undefined;
if (nHash == JSCONST_nNullHash)
return VT_null;
if (nHash == JSCONST_nStringHash)
return VT_string;
if (nHash == JSCONST_nNumberHash)
return VT_number;
if (nHash == JSCONST_nBoolHash)
return VT_boolean;
if (nHash == JSCONST_nDateHash)
return VT_date;
if (nHash == JSCONST_nObjectHash)
return VT_object;
if (nHash == JSCONST_nFXobjHash)
return VT_fxobject;
return VT_unknown;
}