Revert "FX_BOOL considered harmful, part 2."
[pdfium.git] / fpdfsdk / src / javascript / global.cpp
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 "../../include/javascript/IJavaScript.h"
8 #include "../../include/javascript/JS_Context.h"
9 #include "../../include/javascript/JS_Define.h"
10 #include "../../include/javascript/JS_EventHandler.h"
11 #include "../../include/javascript/JS_GlobalData.h"
12 #include "../../include/javascript/JS_Object.h"
13 #include "../../include/javascript/JS_Value.h"
14 #include "../../include/javascript/JavaScript.h"
15 #include "../../include/javascript/global.h"
16 #include "../../include/javascript/resource.h"
17
18 /* ---------------------------- global ---------------------------- */
19
20 // Helper class for compile-time calculation of hash values in order to
21 // avoid having global object initializers.
22 template <unsigned ACC, wchar_t... Ns>
23 struct CHash;
24
25 // Only needed to hash single-character strings.
26 template <wchar_t N>
27 struct CHash<N> {
28   static const unsigned value = N;
29 };
30
31 template <unsigned ACC, wchar_t N>
32 struct CHash<ACC, N> {
33   static const unsigned value = (ACC * 1313LLU + N) & 0xFFFFFFFF;
34 };
35
36 template <unsigned ACC, wchar_t N, wchar_t... Ns>
37 struct CHash<ACC, N, Ns...> {
38   static const unsigned value = CHash<CHash<ACC, N>::value, Ns...>::value;
39 };
40
41 extern const unsigned int JSCONST_nStringHash =
42   CHash<'s','t','r','i','n','g'>::value;
43 extern const unsigned int JSCONST_nNumberHash =
44   CHash<'n','u','m','b','e','r'>::value;
45 extern const unsigned int JSCONST_nBoolHash =
46   CHash<'b','o','o','l','e','a','n'>::value;
47 extern const unsigned int JSCONST_nDateHash =
48   CHash<'d','a','t','e'>::value;
49 extern const unsigned int JSCONST_nObjectHash =
50   CHash<'o','b','j','e','c','t'>::value;
51 extern const unsigned int JSCONST_nFXobjHash =
52   CHash<'f','x','o','b','j'>::value;
53 extern const unsigned int JSCONST_nNullHash =
54   CHash<'n','u','l','l'>::value;
55 extern const unsigned int JSCONST_nUndefHash =
56   CHash<'u','n','d','e','f','i','n','e','d'>::value;
57
58 #ifdef _DEBUG
59 class HashVerify
60 {
61 public:
62   HashVerify();
63 } g_hashVerify;
64
65 HashVerify::HashVerify()
66 {
67   ASSERT(JSCONST_nStringHash ==
68     JS_CalcHash(VALUE_NAME_STRING,wcslen(VALUE_NAME_STRING)));
69   ASSERT(JSCONST_nNumberHash ==
70     JS_CalcHash(VALUE_NAME_NUMBER,wcslen(VALUE_NAME_NUMBER)));
71   ASSERT(JSCONST_nBoolHash ==
72     JS_CalcHash(VALUE_NAME_BOOLEAN,wcslen(VALUE_NAME_BOOLEAN)));
73   ASSERT(JSCONST_nDateHash ==
74     JS_CalcHash(VALUE_NAME_DATE,wcslen(VALUE_NAME_DATE)));
75   ASSERT(JSCONST_nObjectHash ==
76     JS_CalcHash(VALUE_NAME_OBJECT,wcslen(VALUE_NAME_OBJECT)));
77   ASSERT(JSCONST_nFXobjHash ==
78     JS_CalcHash(VALUE_NAME_FXOBJ,wcslen(VALUE_NAME_FXOBJ)));
79   ASSERT(JSCONST_nNullHash ==
80     JS_CalcHash(VALUE_NAME_NULL,wcslen(VALUE_NAME_NULL)));
81   ASSERT(JSCONST_nUndefHash ==
82     JS_CalcHash(VALUE_NAME_UNDEFINED,wcslen(VALUE_NAME_UNDEFINED)));
83 }
84 #endif
85
86
87 BEGIN_JS_STATIC_CONST(CJS_Global)
88 END_JS_STATIC_CONST()
89
90 BEGIN_JS_STATIC_PROP(CJS_Global)
91 END_JS_STATIC_PROP()
92
93 BEGIN_JS_STATIC_METHOD(CJS_Global)
94     JS_STATIC_METHOD_ENTRY(setPersistent)
95 END_JS_STATIC_METHOD()
96
97 IMPLEMENT_SPECIAL_JS_CLASS(CJS_Global, global_alternate, global);
98
99 FX_BOOL CJS_Global::InitInstance(IFXJS_Context* cc)
100 {
101     CJS_Context* pContext = (CJS_Context*)cc;
102     ASSERT(pContext != NULL);
103
104     global_alternate* pGlobal = (global_alternate*)GetEmbedObject();
105     ASSERT(pGlobal != NULL);
106
107     pGlobal->Initial(pContext->GetReaderApp());
108
109     return TRUE;
110 };
111
112 global_alternate::global_alternate(CJS_Object* pJSObject)
113     : CJS_EmbedObj(pJSObject),
114     m_pApp(NULL)
115 {
116 }
117
118 global_alternate::~global_alternate(void)
119 {
120     DestroyGlobalPersisitentVariables();
121     m_pApp->GetRuntimeFactory()->ReleaseGlobalData();
122 }
123
124 void global_alternate::Initial(CPDFDoc_Environment* pApp)
125 {
126     m_pApp = pApp;
127     m_pGlobalData = pApp->GetRuntimeFactory()->NewGlobalData(pApp);
128     UpdateGlobalPersistentVariables();
129 }
130
131 FX_BOOL global_alternate::QueryProperty(const FX_WCHAR* propname)
132 {
133     return CFX_WideString(propname) != L"setPersistent";
134 }
135
136 FX_BOOL global_alternate::DelProperty(IFXJS_Context* cc, const FX_WCHAR* propname, CFX_WideString& sError)
137 {
138     js_global_data* pData = NULL;
139     CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname);
140
141     if (m_mapGlobal.Lookup(sPropName, (void*&)pData))
142     {
143         pData->bDeleted = TRUE;
144         return TRUE;
145     }
146
147     return FALSE;
148 }
149
150 FX_BOOL global_alternate::DoProperty(IFXJS_Context* cc, const FX_WCHAR* propname, CJS_PropValue& vp, CFX_WideString& sError)
151 {
152     if (vp.IsSetting())
153     {
154         CFX_ByteString sPropName = CFX_ByteString::FromUnicode(propname);
155         switch (vp.GetType())
156         {
157         case VT_number:
158             {
159                 double dData;
160                 vp >> dData;
161                 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NUMBER, dData, false, "", v8::Local<v8::Object>(), FALSE);
162             }
163         case VT_boolean:
164             {
165                 bool bData;
166                 vp >> bData;
167                 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_BOOLEAN, 0, bData, "", v8::Local<v8::Object>(), FALSE);
168             }
169         case VT_string:
170             {
171                 CFX_ByteString sData;
172                 vp >> sData;
173                 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_STRING, 0, false, sData, v8::Local<v8::Object>(), FALSE);
174             }
175         case VT_object:
176             {
177                 JSObject pData;
178                 vp >> pData;
179                 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_OBJECT, 0, false, "", pData, FALSE);
180             }
181         case VT_null:
182             {
183                 return SetGlobalVariables(sPropName, JS_GLOBALDATA_TYPE_NULL, 0, false, "", v8::Local<v8::Object>(), FALSE);
184             }
185         case VT_undefined:
186             {
187                 DelProperty(cc, propname, sError);
188                 return TRUE;
189             }
190         default:
191             break;
192         }
193     }
194     else
195     {
196         void* pVoid = nullptr;
197         if (!m_mapGlobal.Lookup(CFX_ByteString::FromUnicode(propname), pVoid))
198         {
199             vp.SetNull();
200             return TRUE;
201         }
202         if (!pVoid)
203         {
204             vp.SetNull();
205             return TRUE;
206         }
207         js_global_data* pData = (js_global_data*)pVoid;
208         if (pData->bDeleted)
209             return TRUE;
210
211         switch (pData->nType)
212         {
213             case JS_GLOBALDATA_TYPE_NUMBER:
214                 vp << pData->dData;
215                 return TRUE;
216             case JS_GLOBALDATA_TYPE_BOOLEAN:
217                 vp << pData->bData;
218                 return TRUE;
219             case JS_GLOBALDATA_TYPE_STRING:
220                 vp << pData->sData;
221                 return TRUE;
222             case JS_GLOBALDATA_TYPE_OBJECT:
223                 {
224                     v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(vp.GetIsolate(),pData->pData);
225                     vp << obj;
226                     return TRUE;
227                 }
228             case JS_GLOBALDATA_TYPE_NULL:
229                 vp.SetNull();
230                 return TRUE;
231             default:
232                 break;
233         }
234     }
235     return FALSE;
236 }
237
238 FX_BOOL global_alternate::setPersistent(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
239 {
240     CJS_Context* pContext = static_cast<CJS_Context*>(cc);
241     if (params.size() != 2)
242     {
243         sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
244         return FALSE;
245     }
246
247     CFX_ByteString sName = params[0].ToCFXByteString();
248
249     js_global_data* pData = NULL;
250     if (m_mapGlobal.Lookup(sName, (void*&)pData))
251     {
252         if (pData && !pData->bDeleted)
253         {
254             pData->bPersistent = params[1].ToBool();
255             return TRUE;
256         }
257     }
258
259     sError = JSGetStringFromID(pContext, IDS_STRING_JSNOGLOBAL);
260     return FALSE;
261 }
262
263 void global_alternate::UpdateGlobalPersistentVariables()
264 {
265     ASSERT(m_pGlobalData != NULL);
266
267     for (int i=0,sz=m_pGlobalData->GetSize(); i<sz; i++)
268     {
269         CJS_GlobalData_Element* pData = m_pGlobalData->GetAt(i);
270         ASSERT(pData != NULL);
271
272         switch (pData->data.nType)
273         {
274         case JS_GLOBALDATA_TYPE_NUMBER:
275             SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NUMBER, pData->data.dData, false, "", v8::Local<v8::Object>(), pData->bPersistent == 1);
276             JS_PutObjectNumber(NULL,(JSFXObject)(*m_pJSObject),
277                                pData->data.sKey.UTF8Decode().c_str(), pData->data.dData);
278             break;
279         case JS_GLOBALDATA_TYPE_BOOLEAN:
280             SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_BOOLEAN, 0, (bool)(pData->data.bData == 1), "", v8::Local<v8::Object>(), pData->bPersistent == 1);
281             JS_PutObjectBoolean(NULL,(JSFXObject)(*m_pJSObject),
282                                 pData->data.sKey.UTF8Decode().c_str(), (bool)(pData->data.bData == 1));
283             break;
284         case JS_GLOBALDATA_TYPE_STRING:
285             SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_STRING, 0, false, pData->data.sData, v8::Local<v8::Object>(), pData->bPersistent == 1);
286             JS_PutObjectString(NULL, (JSFXObject)(*m_pJSObject),
287                                pData->data.sKey.UTF8Decode().c_str(),
288                                pData->data.sData.UTF8Decode().c_str());
289             break;
290         case JS_GLOBALDATA_TYPE_OBJECT:
291             {
292                 IJS_Runtime* pRuntime = JS_GetRuntime((JSFXObject)(*m_pJSObject));
293                 v8::Local<v8::Object> pObj = JS_NewFxDynamicObj(pRuntime, NULL, -1);
294
295                 PutObjectProperty(pObj, &pData->data);
296
297                 SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_OBJECT, 0, false, "",
298                     (JSObject)pObj, pData->bPersistent == 1);
299                 JS_PutObjectObject(NULL,(JSFXObject)(*m_pJSObject),
300                                    pData->data.sKey.UTF8Decode().c_str(), (JSObject)pObj);
301             }
302             break;
303         case JS_GLOBALDATA_TYPE_NULL:
304             SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NULL, 0, false, "", v8::Local<v8::Object>(), pData->bPersistent == 1);
305             JS_PutObjectNull(NULL,(JSFXObject)(*m_pJSObject),
306                              pData->data.sKey.UTF8Decode().c_str());
307             break;
308         }
309     }
310 }
311
312 void global_alternate::CommitGlobalPersisitentVariables()
313 {
314     ASSERT(m_pGlobalData != NULL);
315
316     FX_POSITION  pos = m_mapGlobal.GetStartPosition();
317     while (pos)
318     {
319         CFX_ByteString name;
320         js_global_data* pData = NULL;
321         m_mapGlobal.GetNextAssoc(pos, name, (void*&)pData);
322
323         if (pData)
324         {
325             if (pData->bDeleted)
326             {
327                 m_pGlobalData->DeleteGlobalVariable(name);
328             }
329             else
330             {
331                 switch (pData->nType)
332                 {
333                 case JS_GLOBALDATA_TYPE_NUMBER:
334                     m_pGlobalData->SetGlobalVariableNumber(name, pData->dData);
335                     m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
336                     break;
337                 case JS_GLOBALDATA_TYPE_BOOLEAN:
338                     m_pGlobalData->SetGlobalVariableBoolean(name, pData->bData);
339                     m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
340                     break;
341                 case JS_GLOBALDATA_TYPE_STRING:
342                     m_pGlobalData->SetGlobalVariableString(name, pData->sData);
343                     m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
344                     break;
345                 case JS_GLOBALDATA_TYPE_OBJECT:
346                     //if (pData->pData)
347                     {
348                         CJS_GlobalVariableArray array;
349                         v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(GetJSObject()->GetIsolate(),pData->pData);
350                         ObjectToArray(obj, array);
351                         m_pGlobalData->SetGlobalVariableObject(name, array);
352                         m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
353                     }
354                     break;
355                 case JS_GLOBALDATA_TYPE_NULL:
356                     m_pGlobalData->SetGlobalVariableNull(name);
357                     m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
358                     break;
359                 }
360             }
361         }
362     }
363 }
364
365 void global_alternate::ObjectToArray(v8::Local<v8::Object> pObj, CJS_GlobalVariableArray& array)
366 {
367     v8::Local<v8::Context> context = pObj->CreationContext();
368     v8::Isolate* isolate = context->GetIsolate();
369     v8::Local<v8::Array> pKeyList = JS_GetObjectElementNames(isolate, pObj);
370     int nObjElements = pKeyList->Length();
371
372     for (int i=0; i<nObjElements; i++)
373     {
374
375         CFX_WideString ws = JS_ToString(isolate, JS_GetArrayElement(isolate, pKeyList, i));
376         CFX_ByteString sKey = ws.UTF8Encode();
377
378         v8::Local<v8::Value> v = JS_GetObjectElement(isolate, pObj, ws.c_str());
379         FXJSVALUETYPE vt = GET_VALUE_TYPE(v);
380         switch (vt)
381         {
382         case VT_number:
383             {
384                 CJS_KeyValue* pObjElement = new CJS_KeyValue;
385                 pObjElement->nType = JS_GLOBALDATA_TYPE_NUMBER;
386                 pObjElement->sKey = sKey;
387                 pObjElement->dData = JS_ToNumber(isolate, v);
388                 array.Add(pObjElement);
389             }
390             break;
391         case VT_boolean:
392             {
393                 CJS_KeyValue* pObjElement = new CJS_KeyValue;
394                 pObjElement->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
395                 pObjElement->sKey = sKey;
396                 pObjElement->dData = JS_ToBoolean(isolate, v);
397                 array.Add(pObjElement);
398             }
399             break;
400         case VT_string:
401             {
402                 CFX_ByteString sValue = CJS_Value(isolate, v, VT_string).ToCFXByteString();
403                 CJS_KeyValue* pObjElement = new CJS_KeyValue;
404                 pObjElement->nType = JS_GLOBALDATA_TYPE_STRING;
405                 pObjElement->sKey = sKey;
406                 pObjElement->sData = sValue;
407                 array.Add(pObjElement);
408             }
409             break;
410         case VT_object:
411             {
412                 CJS_KeyValue* pObjElement = new CJS_KeyValue;
413                 pObjElement->nType = JS_GLOBALDATA_TYPE_OBJECT;
414                 pObjElement->sKey = sKey;
415                 ObjectToArray(JS_ToObject(isolate, v), pObjElement->objData);
416                 array.Add(pObjElement);
417             }
418             break;
419         case VT_null:
420             {
421                 CJS_KeyValue* pObjElement = new CJS_KeyValue;
422                 pObjElement->nType = JS_GLOBALDATA_TYPE_NULL;
423                 pObjElement->sKey = sKey;
424                 array.Add(pObjElement);
425             }
426             break;
427         default:
428             break;
429         }
430     }
431 }
432
433 void global_alternate::PutObjectProperty(v8::Local<v8::Object> pObj, CJS_KeyValue* pData)
434 {
435     ASSERT(pData != NULL);
436
437     for (int i=0,sz=pData->objData.Count(); i<sz; i++)
438     {
439         CJS_KeyValue* pObjData = pData->objData.GetAt(i);
440         ASSERT(pObjData != NULL);
441
442         switch (pObjData->nType)
443         {
444         case JS_GLOBALDATA_TYPE_NUMBER:
445             JS_PutObjectNumber(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), pObjData->dData);
446             break;
447         case JS_GLOBALDATA_TYPE_BOOLEAN:
448             JS_PutObjectBoolean(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), (bool)(pObjData->bData == 1));
449             break;
450         case JS_GLOBALDATA_TYPE_STRING:
451             JS_PutObjectString(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), pObjData->sData.UTF8Decode().c_str());
452             break;
453         case JS_GLOBALDATA_TYPE_OBJECT:
454             {
455                 IJS_Runtime* pRuntime = JS_GetRuntime((JSFXObject)(*m_pJSObject));
456                 v8::Local<v8::Object> pNewObj = JS_NewFxDynamicObj(pRuntime, NULL, -1);
457                 PutObjectProperty(pNewObj, pObjData);
458                 JS_PutObjectObject(NULL, (JSObject)pObj, pObjData->sKey.UTF8Decode().c_str(), (JSObject)pNewObj);
459             }
460             break;
461         case JS_GLOBALDATA_TYPE_NULL:
462             JS_PutObjectNull(NULL,(JSObject)pObj, pObjData->sKey.UTF8Decode().c_str());
463             break;
464         }
465     }
466 }
467
468 void global_alternate::DestroyGlobalPersisitentVariables()
469 {
470     FX_POSITION  pos = m_mapGlobal.GetStartPosition();
471     while (pos)
472     {
473         CFX_ByteString name;
474         js_global_data* pData = NULL;
475         m_mapGlobal.GetNextAssoc(pos, name, (void*&)pData);
476         delete pData;
477     }
478
479     m_mapGlobal.RemoveAll();
480 }
481
482
483 FX_BOOL global_alternate::SetGlobalVariables(const FX_CHAR* propname, int nType,
484                 double dData, bool bData, const CFX_ByteString& sData, JSObject pData, bool bDefaultPersistent)
485 {
486     if (propname == NULL) return FALSE;
487
488     js_global_data* pTemp = NULL;
489     m_mapGlobal.Lookup(propname, (void*&)pTemp);
490
491     if (pTemp)
492     {
493         if (pTemp->bDeleted || pTemp->nType != nType)
494         {
495             pTemp->dData = 0;
496             pTemp->bData = 0;
497             pTemp->sData = "";
498             pTemp->nType = nType;
499         }
500
501         pTemp->bDeleted = FALSE;
502
503         switch (nType)
504         {
505         case JS_GLOBALDATA_TYPE_NUMBER:
506             {
507                 pTemp->dData = dData;
508             }
509             break;
510         case JS_GLOBALDATA_TYPE_BOOLEAN:
511             {
512                 pTemp->bData = bData;
513             }
514             break;
515         case JS_GLOBALDATA_TYPE_STRING:
516             {
517                 pTemp->sData = sData;
518             }
519             break;
520         case JS_GLOBALDATA_TYPE_OBJECT:
521             {
522                 pTemp->pData.Reset(JS_GetRuntime(pData), pData);
523             }
524             break;
525         case JS_GLOBALDATA_TYPE_NULL:
526             break;
527         default:
528             return FALSE;
529         }
530
531         return TRUE;
532     }
533
534     js_global_data* pNewData = NULL;
535
536     switch (nType)
537     {
538     case JS_GLOBALDATA_TYPE_NUMBER:
539         {
540             pNewData = new js_global_data;
541             pNewData->nType = JS_GLOBALDATA_TYPE_NUMBER;
542             pNewData->dData = dData;
543             pNewData->bPersistent = bDefaultPersistent;
544         }
545         break;
546     case JS_GLOBALDATA_TYPE_BOOLEAN:
547         {
548             pNewData = new js_global_data;
549             pNewData->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
550             pNewData->bData = bData;
551             pNewData->bPersistent = bDefaultPersistent;
552         }
553         break;
554     case JS_GLOBALDATA_TYPE_STRING:
555         {
556             pNewData = new js_global_data;
557             pNewData->nType = JS_GLOBALDATA_TYPE_STRING;
558             pNewData->sData = sData;
559             pNewData->bPersistent = bDefaultPersistent;
560         }
561         break;
562     case JS_GLOBALDATA_TYPE_OBJECT:
563         {
564             pNewData = new js_global_data;
565             pNewData->nType = JS_GLOBALDATA_TYPE_OBJECT;
566             pNewData->pData.Reset(JS_GetRuntime(pData), pData);
567             pNewData->bPersistent = bDefaultPersistent;
568         }
569         break;
570     case JS_GLOBALDATA_TYPE_NULL:
571         {
572             pNewData = new js_global_data;
573             pNewData->nType = JS_GLOBALDATA_TYPE_NULL;
574             pNewData->bPersistent = bDefaultPersistent;
575         }
576         break;
577     default:
578         return FALSE;
579     }
580
581     m_mapGlobal.SetAt(propname, (void*)pNewData);
582
583     return TRUE;
584 }
585
586 FXJSVALUETYPE GET_VALUE_TYPE(v8::Local<v8::Value> p)
587 {
588   const unsigned int nHash = JS_CalcHash(JS_GetTypeof(p));
589
590   if (nHash == JSCONST_nUndefHash)
591     return VT_undefined;
592   if (nHash == JSCONST_nNullHash)
593     return VT_null;
594   if (nHash == JSCONST_nStringHash)
595     return VT_string;
596   if (nHash == JSCONST_nNumberHash)
597     return VT_number;
598   if (nHash == JSCONST_nBoolHash)
599     return VT_boolean;
600   if (nHash == JSCONST_nDateHash)
601     return VT_date;
602   if (nHash == JSCONST_nObjectHash)
603     return VT_object;
604   if (nHash == JSCONST_nFXobjHash)
605     return VT_fxobject;
606
607   return VT_unknown;
608 }