1// Copyright 2014 PDFium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "global.h"
8
9#include "JS_Context.h"
10#include "JS_Define.h"
11#include "JS_EventHandler.h"
12#include "JS_GlobalData.h"
13#include "JS_Object.h"
14#include "JS_Value.h"
15#include "core/include/fxcrt/fx_ext.h"
16#include "fpdfsdk/include/javascript/IJavaScript.h"
17#include "resource.h"
18
19/* ---------------------------- global ---------------------------- */
20
21// Helper class for compile-time calculation of hash values in order to
22// avoid having global object initializers.
23template <unsigned ACC, wchar_t... Ns>
24struct CHash;
25
26// Only needed to hash single-character strings.
27template <wchar_t N>
28struct CHash<N> {
29  static const unsigned value = N;
30};
31
32template <unsigned ACC, wchar_t N>
33struct CHash<ACC, N> {
34  static const unsigned value = (ACC * 1313LLU + N) & 0xFFFFFFFF;
35};
36
37template <unsigned ACC, wchar_t N, wchar_t... Ns>
38struct CHash<ACC, N, Ns...> {
39  static const unsigned value = CHash<CHash<ACC, N>::value, Ns...>::value;
40};
41
42const unsigned int JSCONST_nStringHash =
43    CHash<'s', 't', 'r', 'i', 'n', 'g'>::value;
44const unsigned int JSCONST_nNumberHash =
45    CHash<'n', 'u', 'm', 'b', 'e', 'r'>::value;
46const unsigned int JSCONST_nBoolHash =
47    CHash<'b', 'o', 'o', 'l', 'e', 'a', 'n'>::value;
48const unsigned int JSCONST_nDateHash = CHash<'d', 'a', 't', 'e'>::value;
49const unsigned int JSCONST_nObjectHash =
50    CHash<'o', 'b', 'j', 'e', 'c', 't'>::value;
51const unsigned int JSCONST_nFXobjHash = CHash<'f', 'x', 'o', 'b', 'j'>::value;
52const unsigned int JSCONST_nNullHash = CHash<'n', 'u', 'l', 'l'>::value;
53const unsigned int JSCONST_nUndefHash =
54    CHash<'u', 'n', 'd', 'e', 'f', 'i', 'n', 'e', 'd'>::value;
55
56static unsigned JS_CalcHash(const wchar_t* main) {
57  return (unsigned)FX_HashCode_String_GetW(main, FXSYS_wcslen(main));
58}
59
60#ifdef _DEBUG
61class HashVerify {
62 public:
63  HashVerify();
64} g_hashVerify;
65
66HashVerify::HashVerify() {
67  ASSERT(JSCONST_nStringHash == JS_CalcHash(kFXJSValueNameString));
68  ASSERT(JSCONST_nNumberHash == JS_CalcHash(kFXJSValueNameNumber));
69  ASSERT(JSCONST_nBoolHash == JS_CalcHash(kFXJSValueNameBoolean));
70  ASSERT(JSCONST_nDateHash == JS_CalcHash(kFXJSValueNameDate));
71  ASSERT(JSCONST_nObjectHash == JS_CalcHash(kFXJSValueNameObject));
72  ASSERT(JSCONST_nFXobjHash == JS_CalcHash(kFXJSValueNameFxobj));
73  ASSERT(JSCONST_nNullHash == JS_CalcHash(kFXJSValueNameNull));
74  ASSERT(JSCONST_nUndefHash == JS_CalcHash(kFXJSValueNameUndefined));
75}
76#endif
77
78BEGIN_JS_STATIC_CONST(CJS_Global)
79END_JS_STATIC_CONST()
80
81BEGIN_JS_STATIC_PROP(CJS_Global)
82END_JS_STATIC_PROP()
83
84BEGIN_JS_STATIC_METHOD(CJS_Global)
85JS_STATIC_METHOD_ENTRY(setPersistent)
86END_JS_STATIC_METHOD()
87
88IMPLEMENT_SPECIAL_JS_CLASS(CJS_Global, JSGlobalAlternate, global);
89
90void CJS_Global::InitInstance(IJS_Runtime* pIRuntime) {
91  CJS_Runtime* pRuntime = static_cast<CJS_Runtime*>(pIRuntime);
92  JSGlobalAlternate* pGlobal =
93      static_cast<JSGlobalAlternate*>(GetEmbedObject());
94  pGlobal->Initial(pRuntime->GetReaderApp());
95}
96
97JSGlobalAlternate::JSGlobalAlternate(CJS_Object* pJSObject)
98    : CJS_EmbedObj(pJSObject), m_pApp(NULL) {
99}
100
101JSGlobalAlternate::~JSGlobalAlternate() {
102  DestroyGlobalPersisitentVariables();
103  m_pGlobalData->Release();
104}
105
106void JSGlobalAlternate::Initial(CPDFDoc_Environment* pApp) {
107  m_pApp = pApp;
108  m_pGlobalData = CJS_GlobalData::GetRetainedInstance(pApp);
109  UpdateGlobalPersistentVariables();
110}
111
112FX_BOOL JSGlobalAlternate::QueryProperty(const FX_WCHAR* propname) {
113  return CFX_WideString(propname) != L"setPersistent";
114}
115
116FX_BOOL JSGlobalAlternate::DelProperty(IJS_Context* cc,
117                                       const FX_WCHAR* propname,
118                                       CFX_WideString& sError) {
119  auto it = m_mapGlobal.find(CFX_ByteString::FromUnicode(propname));
120  if (it == m_mapGlobal.end())
121    return FALSE;
122
123  it->second->bDeleted = TRUE;
124  return TRUE;
125}
126
127FX_BOOL JSGlobalAlternate::DoProperty(IJS_Context* cc,
128                                      const FX_WCHAR* propname,
129                                      CJS_PropValue& vp,
130                                      CFX_WideString& sError) {
131  if (vp.IsSetting()) {
132    CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname);
133    switch (vp.GetType()) {
134      case CJS_Value::VT_number: {
135        double dData;
136        vp >> dData;
137        return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NUMBER, dData,
138                                  false, "", v8::Local<v8::Object>(), FALSE);
139      }
140      case CJS_Value::VT_boolean: {
141        bool bData;
142        vp >> bData;
143        return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_BOOLEAN, 0,
144                                  bData, "", v8::Local<v8::Object>(), FALSE);
145      }
146      case CJS_Value::VT_string: {
147        CFX_ByteString sData;
148        vp >> sData;
149        return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_STRING, 0,
150                                  false, sData, v8::Local<v8::Object>(), FALSE);
151      }
152      case CJS_Value::VT_object: {
153        v8::Local<v8::Object> pData;
154        vp >> pData;
155        return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_OBJECT, 0,
156                                  false, "", pData, FALSE);
157      }
158      case CJS_Value::VT_null: {
159        return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NULL, 0, false,
160                                  "", v8::Local<v8::Object>(), FALSE);
161      }
162      case CJS_Value::VT_undefined: {
163        DelProperty(cc, propname, sError);
164        return TRUE;
165      }
166      default:
167        break;
168    }
169  } else {
170    auto it = m_mapGlobal.find(CFX_ByteString::FromUnicode(propname));
171    if (it == m_mapGlobal.end()) {
172      vp.SetNull();
173      return TRUE;
174    }
175    JSGlobalData* pData = it->second;
176    if (pData->bDeleted) {
177      vp.SetNull();
178      return TRUE;
179    }
180    switch (pData->nType) {
181      case JS_GLOBALDATA_TYPE_NUMBER:
182        vp << pData->dData;
183        return TRUE;
184      case JS_GLOBALDATA_TYPE_BOOLEAN:
185        vp << pData->bData;
186        return TRUE;
187      case JS_GLOBALDATA_TYPE_STRING:
188        vp << pData->sData;
189        return TRUE;
190      case JS_GLOBALDATA_TYPE_OBJECT: {
191        v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(
192            vp.GetJSRuntime()->GetIsolate(), pData->pData);
193        vp << obj;
194        return TRUE;
195      }
196      case JS_GLOBALDATA_TYPE_NULL:
197        vp.SetNull();
198        return TRUE;
199      default:
200        break;
201    }
202  }
203  return FALSE;
204}
205
206FX_BOOL JSGlobalAlternate::setPersistent(IJS_Context* cc,
207                                         const std::vector<CJS_Value>& params,
208                                         CJS_Value& vRet,
209                                         CFX_WideString& sError) {
210  CJS_Context* pContext = static_cast<CJS_Context*>(cc);
211  if (params.size() != 2) {
212    sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
213    return FALSE;
214  }
215
216  auto it = m_mapGlobal.find(params[0].ToCFXByteString());
217  if (it != m_mapGlobal.end()) {
218    JSGlobalData* pData = it->second;
219    if (!pData->bDeleted) {
220      pData->bPersistent = params[1].ToBool();
221      return TRUE;
222    }
223  }
224
225  sError = JSGetStringFromID(pContext, IDS_STRING_JSNOGLOBAL);
226  return FALSE;
227}
228
229void JSGlobalAlternate::UpdateGlobalPersistentVariables() {
230  for (int i = 0, sz = m_pGlobalData->GetSize(); i < sz; i++) {
231    CJS_GlobalData_Element* pData = m_pGlobalData->GetAt(i);
232    switch (pData->data.nType) {
233      case JS_GLOBALDATA_TYPE_NUMBER:
234        SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NUMBER,
235                           pData->data.dData, false, "",
236                           v8::Local<v8::Object>(), pData->bPersistent == 1);
237        FXJS_PutObjectNumber(NULL, m_pJSObject->ToV8Object(),
238                             pData->data.sKey.UTF8Decode().c_str(),
239                             pData->data.dData);
240        break;
241      case JS_GLOBALDATA_TYPE_BOOLEAN:
242        SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_BOOLEAN, 0,
243                           (bool)(pData->data.bData == 1), "",
244                           v8::Local<v8::Object>(), pData->bPersistent == 1);
245        FXJS_PutObjectBoolean(NULL, m_pJSObject->ToV8Object(),
246                              pData->data.sKey.UTF8Decode().c_str(),
247                              (bool)(pData->data.bData == 1));
248        break;
249      case JS_GLOBALDATA_TYPE_STRING:
250        SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_STRING, 0,
251                           false, pData->data.sData, v8::Local<v8::Object>(),
252                           pData->bPersistent == 1);
253        FXJS_PutObjectString(NULL, m_pJSObject->ToV8Object(),
254                             pData->data.sKey.UTF8Decode().c_str(),
255                             pData->data.sData.UTF8Decode().c_str());
256        break;
257      case JS_GLOBALDATA_TYPE_OBJECT: {
258        v8::Isolate* pRuntime = m_pJSObject->ToV8Object()->GetIsolate();
259        v8::Local<v8::Object> pObj = FXJS_NewFxDynamicObj(pRuntime, NULL, -1);
260
261        PutObjectProperty(pObj, &pData->data);
262
263        SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_OBJECT, 0,
264                           false, "", pObj, pData->bPersistent == 1);
265        FXJS_PutObjectObject(NULL, m_pJSObject->ToV8Object(),
266                             pData->data.sKey.UTF8Decode().c_str(), pObj);
267
268      } break;
269      case JS_GLOBALDATA_TYPE_NULL:
270        SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NULL, 0, false,
271                           "", v8::Local<v8::Object>(),
272                           pData->bPersistent == 1);
273        FXJS_PutObjectNull(NULL, m_pJSObject->ToV8Object(),
274                           pData->data.sKey.UTF8Decode().c_str());
275        break;
276    }
277  }
278}
279
280void JSGlobalAlternate::CommitGlobalPersisitentVariables(IJS_Context* cc) {
281  for (auto it = m_mapGlobal.begin(); it != m_mapGlobal.end(); ++it) {
282    CFX_ByteString name = it->first;
283    JSGlobalData* pData = it->second;
284    if (pData->bDeleted) {
285      m_pGlobalData->DeleteGlobalVariable(name);
286    } else {
287      switch (pData->nType) {
288        case JS_GLOBALDATA_TYPE_NUMBER:
289          m_pGlobalData->SetGlobalVariableNumber(name, pData->dData);
290          m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
291          break;
292        case JS_GLOBALDATA_TYPE_BOOLEAN:
293          m_pGlobalData->SetGlobalVariableBoolean(name, pData->bData);
294          m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
295          break;
296        case JS_GLOBALDATA_TYPE_STRING:
297          m_pGlobalData->SetGlobalVariableString(name, pData->sData);
298          m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
299          break;
300        case JS_GLOBALDATA_TYPE_OBJECT:
301          {
302            CJS_GlobalVariableArray array;
303            v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(
304                GetJSObject()->GetIsolate(), pData->pData);
305            ObjectToArray(cc, obj, array);
306            m_pGlobalData->SetGlobalVariableObject(name, array);
307            m_pGlobalData->SetGlobalVariablePersistent(name,
308                                                       pData->bPersistent);
309          }
310          break;
311        case JS_GLOBALDATA_TYPE_NULL:
312          m_pGlobalData->SetGlobalVariableNull(name);
313          m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
314          break;
315      }
316    }
317  }
318}
319
320void JSGlobalAlternate::ObjectToArray(IJS_Context* cc,
321                                      v8::Local<v8::Object> pObj,
322                                      CJS_GlobalVariableArray& array) {
323  v8::Isolate* isolate = pObj->GetIsolate();
324  CJS_Runtime* pRuntime = CJS_Runtime::FromContext(cc);
325
326  v8::Local<v8::Array> pKeyList = FXJS_GetObjectElementNames(isolate, pObj);
327  int nObjElements = pKeyList->Length();
328  for (int i = 0; i < nObjElements; i++) {
329    CFX_WideString ws =
330        FXJS_ToString(isolate, FXJS_GetArrayElement(isolate, pKeyList, i));
331    CFX_ByteString sKey = ws.UTF8Encode();
332
333    v8::Local<v8::Value> v = FXJS_GetObjectElement(isolate, pObj, ws.c_str());
334    switch (GET_VALUE_TYPE(v)) {
335      case CJS_Value::VT_number: {
336        CJS_KeyValue* pObjElement = new CJS_KeyValue;
337        pObjElement->nType = JS_GLOBALDATA_TYPE_NUMBER;
338        pObjElement->sKey = sKey;
339        pObjElement->dData = FXJS_ToNumber(isolate, v);
340        array.Add(pObjElement);
341      } break;
342      case CJS_Value::VT_boolean: {
343        CJS_KeyValue* pObjElement = new CJS_KeyValue;
344        pObjElement->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
345        pObjElement->sKey = sKey;
346        pObjElement->dData = FXJS_ToBoolean(isolate, v);
347        array.Add(pObjElement);
348      } break;
349      case CJS_Value::VT_string: {
350        CFX_ByteString sValue =
351            CJS_Value(pRuntime, v, CJS_Value::VT_string).ToCFXByteString();
352        CJS_KeyValue* pObjElement = new CJS_KeyValue;
353        pObjElement->nType = JS_GLOBALDATA_TYPE_STRING;
354        pObjElement->sKey = sKey;
355        pObjElement->sData = sValue;
356        array.Add(pObjElement);
357      } break;
358      case CJS_Value::VT_object: {
359        CJS_KeyValue* pObjElement = new CJS_KeyValue;
360        pObjElement->nType = JS_GLOBALDATA_TYPE_OBJECT;
361        pObjElement->sKey = sKey;
362        ObjectToArray(cc, FXJS_ToObject(isolate, v), pObjElement->objData);
363        array.Add(pObjElement);
364      } break;
365      case CJS_Value::VT_null: {
366        CJS_KeyValue* pObjElement = new CJS_KeyValue;
367        pObjElement->nType = JS_GLOBALDATA_TYPE_NULL;
368        pObjElement->sKey = sKey;
369        array.Add(pObjElement);
370      } break;
371      default:
372        break;
373    }
374  }
375}
376
377void JSGlobalAlternate::PutObjectProperty(v8::Local<v8::Object> pObj,
378                                          CJS_KeyValue* pData) {
379  for (int i = 0, sz = pData->objData.Count(); i < sz; i++) {
380    CJS_KeyValue* pObjData = pData->objData.GetAt(i);
381    switch (pObjData->nType) {
382      case JS_GLOBALDATA_TYPE_NUMBER:
383        FXJS_PutObjectNumber(NULL, pObj, pObjData->sKey.UTF8Decode().c_str(),
384                             pObjData->dData);
385        break;
386      case JS_GLOBALDATA_TYPE_BOOLEAN:
387        FXJS_PutObjectBoolean(NULL, pObj, pObjData->sKey.UTF8Decode().c_str(),
388                              pObjData->bData == 1);
389        break;
390      case JS_GLOBALDATA_TYPE_STRING:
391        FXJS_PutObjectString(NULL, pObj, pObjData->sKey.UTF8Decode().c_str(),
392                             pObjData->sData.UTF8Decode().c_str());
393        break;
394      case JS_GLOBALDATA_TYPE_OBJECT: {
395        v8::Isolate* pRuntime = m_pJSObject->ToV8Object()->GetIsolate();
396        v8::Local<v8::Object> pNewObj =
397            FXJS_NewFxDynamicObj(pRuntime, NULL, -1);
398        PutObjectProperty(pNewObj, pObjData);
399        FXJS_PutObjectObject(NULL, pObj, pObjData->sKey.UTF8Decode().c_str(),
400                             pNewObj);
401      } break;
402      case JS_GLOBALDATA_TYPE_NULL:
403        FXJS_PutObjectNull(NULL, pObj, pObjData->sKey.UTF8Decode().c_str());
404        break;
405    }
406  }
407}
408
409void JSGlobalAlternate::DestroyGlobalPersisitentVariables() {
410  for (const auto& pair : m_mapGlobal) {
411    delete pair.second;
412  }
413  m_mapGlobal.clear();
414}
415
416FX_BOOL JSGlobalAlternate::SetGlobalVariables(const FX_CHAR* propname,
417                                              int nType,
418                                              double dData,
419                                              bool bData,
420                                              const CFX_ByteString& sData,
421                                              v8::Local<v8::Object> pData,
422                                              bool bDefaultPersistent) {
423  if (!propname)
424    return FALSE;
425
426  auto it = m_mapGlobal.find(propname);
427  if (it != m_mapGlobal.end()) {
428    JSGlobalData* pTemp = it->second;
429    if (pTemp->bDeleted || pTemp->nType != nType) {
430      pTemp->dData = 0;
431      pTemp->bData = 0;
432      pTemp->sData = "";
433      pTemp->nType = nType;
434    }
435
436    pTemp->bDeleted = FALSE;
437    switch (nType) {
438      case JS_GLOBALDATA_TYPE_NUMBER: {
439        pTemp->dData = dData;
440      } break;
441      case JS_GLOBALDATA_TYPE_BOOLEAN: {
442        pTemp->bData = bData;
443      } break;
444      case JS_GLOBALDATA_TYPE_STRING: {
445        pTemp->sData = sData;
446      } break;
447      case JS_GLOBALDATA_TYPE_OBJECT: {
448        pTemp->pData.Reset(pData->GetIsolate(), pData);
449      } break;
450      case JS_GLOBALDATA_TYPE_NULL:
451        break;
452      default:
453        return FALSE;
454    }
455    return TRUE;
456  }
457
458  JSGlobalData* pNewData = NULL;
459
460  switch (nType) {
461    case JS_GLOBALDATA_TYPE_NUMBER: {
462      pNewData = new JSGlobalData;
463      pNewData->nType = JS_GLOBALDATA_TYPE_NUMBER;
464      pNewData->dData = dData;
465      pNewData->bPersistent = bDefaultPersistent;
466    } break;
467    case JS_GLOBALDATA_TYPE_BOOLEAN: {
468      pNewData = new JSGlobalData;
469      pNewData->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
470      pNewData->bData = bData;
471      pNewData->bPersistent = bDefaultPersistent;
472    } break;
473    case JS_GLOBALDATA_TYPE_STRING: {
474      pNewData = new JSGlobalData;
475      pNewData->nType = JS_GLOBALDATA_TYPE_STRING;
476      pNewData->sData = sData;
477      pNewData->bPersistent = bDefaultPersistent;
478    } break;
479    case JS_GLOBALDATA_TYPE_OBJECT: {
480      pNewData = new JSGlobalData;
481      pNewData->nType = JS_GLOBALDATA_TYPE_OBJECT;
482      pNewData->pData.Reset(pData->GetIsolate(), pData);
483      pNewData->bPersistent = bDefaultPersistent;
484    } break;
485    case JS_GLOBALDATA_TYPE_NULL: {
486      pNewData = new JSGlobalData;
487      pNewData->nType = JS_GLOBALDATA_TYPE_NULL;
488      pNewData->bPersistent = bDefaultPersistent;
489    } break;
490    default:
491      return FALSE;
492  }
493
494  m_mapGlobal[propname] = pNewData;
495  return TRUE;
496}
497
498CJS_Value::Type GET_VALUE_TYPE(v8::Local<v8::Value> p) {
499  const unsigned int nHash = JS_CalcHash(FXJS_GetTypeof(p));
500
501  if (nHash == JSCONST_nUndefHash)
502    return CJS_Value::VT_undefined;
503  if (nHash == JSCONST_nNullHash)
504    return CJS_Value::VT_null;
505  if (nHash == JSCONST_nStringHash)
506    return CJS_Value::VT_string;
507  if (nHash == JSCONST_nNumberHash)
508    return CJS_Value::VT_number;
509  if (nHash == JSCONST_nBoolHash)
510    return CJS_Value::VT_boolean;
511  if (nHash == JSCONST_nDateHash)
512    return CJS_Value::VT_date;
513  if (nHash == JSCONST_nObjectHash)
514    return CJS_Value::VT_object;
515  if (nHash == JSCONST_nFXobjHash)
516    return CJS_Value::VT_fxobject;
517
518  return CJS_Value::VT_unknown;
519}
520