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