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