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