Ensure functions in FXJS_V8 are prefixed by FXJS_.
[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 "../../../core/include/fxcrt/fx_ext.h"
8 #include "../../include/javascript/IJavaScript.h"
9 #include "../../include/javascript/JS_Context.h"
10 #include "../../include/javascript/JS_Define.h"
11 #include "../../include/javascript/JS_EventHandler.h"
12 #include "../../include/javascript/JS_GlobalData.h"
13 #include "../../include/javascript/JS_Object.h"
14 #include "../../include/javascript/JS_Value.h"
15 #include "../../include/javascript/JavaScript.h"
16 #include "../../include/javascript/global.h"
17 #include "../../include/javascript/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, (v8::Local<v8::Object>)(*m_pJSObject),
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, (v8::Local<v8::Object>)(*m_pJSObject),
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, (v8::Local<v8::Object>)(*m_pJSObject),
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 =
268             FXJS_GetRuntime((v8::Local<v8::Object>)(*m_pJSObject));
269         v8::Local<v8::Object> pObj = FXJS_NewFxDynamicObj(pRuntime, NULL, -1);
270
271         PutObjectProperty(pObj, &pData->data);
272
273         SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_OBJECT, 0,
274                            false, "", (v8::Local<v8::Object>)pObj,
275                            pData->bPersistent == 1);
276         FXJS_PutObjectObject(NULL, (v8::Local<v8::Object>)(*m_pJSObject),
277                              pData->data.sKey.UTF8Decode().c_str(),
278                              (v8::Local<v8::Object>)pObj);
279       } break;
280       case JS_GLOBALDATA_TYPE_NULL:
281         SetGlobalVariables(pData->data.sKey, JS_GLOBALDATA_TYPE_NULL, 0, false,
282                            "", v8::Local<v8::Object>(),
283                            pData->bPersistent == 1);
284         FXJS_PutObjectNull(NULL, (v8::Local<v8::Object>)(*m_pJSObject),
285                            pData->data.sKey.UTF8Decode().c_str());
286         break;
287     }
288   }
289 }
290
291 void JSGlobalAlternate::CommitGlobalPersisitentVariables() {
292   ASSERT(m_pGlobalData);
293   for (auto it = m_mapGlobal.begin(); it != m_mapGlobal.end(); ++it) {
294     CFX_ByteString name = it->first;
295     JSGlobalData* pData = it->second;
296     if (pData->bDeleted) {
297       m_pGlobalData->DeleteGlobalVariable(name);
298     } else {
299       switch (pData->nType) {
300         case JS_GLOBALDATA_TYPE_NUMBER:
301           m_pGlobalData->SetGlobalVariableNumber(name, pData->dData);
302           m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
303           break;
304         case JS_GLOBALDATA_TYPE_BOOLEAN:
305           m_pGlobalData->SetGlobalVariableBoolean(name, pData->bData);
306           m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
307           break;
308         case JS_GLOBALDATA_TYPE_STRING:
309           m_pGlobalData->SetGlobalVariableString(name, pData->sData);
310           m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
311           break;
312         case JS_GLOBALDATA_TYPE_OBJECT:
313           // if (pData->pData)
314           {
315             CJS_GlobalVariableArray array;
316             v8::Local<v8::Object> obj = v8::Local<v8::Object>::New(
317                 GetJSObject()->GetIsolate(), pData->pData);
318             ObjectToArray(obj, array);
319             m_pGlobalData->SetGlobalVariableObject(name, array);
320             m_pGlobalData->SetGlobalVariablePersistent(name,
321                                                        pData->bPersistent);
322           }
323           break;
324         case JS_GLOBALDATA_TYPE_NULL:
325           m_pGlobalData->SetGlobalVariableNull(name);
326           m_pGlobalData->SetGlobalVariablePersistent(name, pData->bPersistent);
327           break;
328       }
329     }
330   }
331 }
332
333 void JSGlobalAlternate::ObjectToArray(v8::Local<v8::Object> pObj,
334                                       CJS_GlobalVariableArray& array) {
335   v8::Local<v8::Context> context = pObj->CreationContext();
336   v8::Isolate* isolate = context->GetIsolate();
337   v8::Local<v8::Array> pKeyList = FXJS_GetObjectElementNames(isolate, pObj);
338   int nObjElements = pKeyList->Length();
339
340   for (int i = 0; i < nObjElements; i++) {
341     CFX_WideString ws =
342         FXJS_ToString(isolate, FXJS_GetArrayElement(isolate, pKeyList, i));
343     CFX_ByteString sKey = ws.UTF8Encode();
344
345     v8::Local<v8::Value> v = FXJS_GetObjectElement(isolate, pObj, ws.c_str());
346     switch (GET_VALUE_TYPE(v)) {
347       case CJS_Value::VT_number: {
348         CJS_KeyValue* pObjElement = new CJS_KeyValue;
349         pObjElement->nType = JS_GLOBALDATA_TYPE_NUMBER;
350         pObjElement->sKey = sKey;
351         pObjElement->dData = FXJS_ToNumber(isolate, v);
352         array.Add(pObjElement);
353       } break;
354       case CJS_Value::VT_boolean: {
355         CJS_KeyValue* pObjElement = new CJS_KeyValue;
356         pObjElement->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
357         pObjElement->sKey = sKey;
358         pObjElement->dData = FXJS_ToBoolean(isolate, v);
359         array.Add(pObjElement);
360       } break;
361       case CJS_Value::VT_string: {
362         CFX_ByteString sValue =
363             CJS_Value(isolate, v, CJS_Value::VT_string).ToCFXByteString();
364         CJS_KeyValue* pObjElement = new CJS_KeyValue;
365         pObjElement->nType = JS_GLOBALDATA_TYPE_STRING;
366         pObjElement->sKey = sKey;
367         pObjElement->sData = sValue;
368         array.Add(pObjElement);
369       } break;
370       case CJS_Value::VT_object: {
371         CJS_KeyValue* pObjElement = new CJS_KeyValue;
372         pObjElement->nType = JS_GLOBALDATA_TYPE_OBJECT;
373         pObjElement->sKey = sKey;
374         ObjectToArray(FXJS_ToObject(isolate, v), pObjElement->objData);
375         array.Add(pObjElement);
376       } break;
377       case CJS_Value::VT_null: {
378         CJS_KeyValue* pObjElement = new CJS_KeyValue;
379         pObjElement->nType = JS_GLOBALDATA_TYPE_NULL;
380         pObjElement->sKey = sKey;
381         array.Add(pObjElement);
382       } break;
383       default:
384         break;
385     }
386   }
387 }
388
389 void JSGlobalAlternate::PutObjectProperty(v8::Local<v8::Object> pObj,
390                                           CJS_KeyValue* pData) {
391   ASSERT(pData != NULL);
392
393   for (int i = 0, sz = pData->objData.Count(); i < sz; i++) {
394     CJS_KeyValue* pObjData = pData->objData.GetAt(i);
395     ASSERT(pObjData != NULL);
396
397     switch (pObjData->nType) {
398       case JS_GLOBALDATA_TYPE_NUMBER:
399         FXJS_PutObjectNumber(NULL, (v8::Local<v8::Object>)pObj,
400                              pObjData->sKey.UTF8Decode().c_str(),
401                              pObjData->dData);
402         break;
403       case JS_GLOBALDATA_TYPE_BOOLEAN:
404         FXJS_PutObjectBoolean(NULL, (v8::Local<v8::Object>)pObj,
405                               pObjData->sKey.UTF8Decode().c_str(),
406                               (bool)(pObjData->bData == 1));
407         break;
408       case JS_GLOBALDATA_TYPE_STRING:
409         FXJS_PutObjectString(NULL, (v8::Local<v8::Object>)pObj,
410                              pObjData->sKey.UTF8Decode().c_str(),
411                              pObjData->sData.UTF8Decode().c_str());
412         break;
413       case JS_GLOBALDATA_TYPE_OBJECT: {
414         v8::Isolate* pRuntime =
415             FXJS_GetRuntime((v8::Local<v8::Object>)(*m_pJSObject));
416         v8::Local<v8::Object> pNewObj =
417             FXJS_NewFxDynamicObj(pRuntime, NULL, -1);
418         PutObjectProperty(pNewObj, pObjData);
419         FXJS_PutObjectObject(NULL, (v8::Local<v8::Object>)pObj,
420                              pObjData->sKey.UTF8Decode().c_str(),
421                              (v8::Local<v8::Object>)pNewObj);
422       } break;
423       case JS_GLOBALDATA_TYPE_NULL:
424         FXJS_PutObjectNull(NULL, (v8::Local<v8::Object>)pObj,
425                            pObjData->sKey.UTF8Decode().c_str());
426         break;
427     }
428   }
429 }
430
431 void JSGlobalAlternate::DestroyGlobalPersisitentVariables() {
432   for (const auto& pair : m_mapGlobal) {
433     delete pair.second;
434   }
435   m_mapGlobal.clear();
436 }
437
438 FX_BOOL JSGlobalAlternate::SetGlobalVariables(const FX_CHAR* propname,
439                                               int nType,
440                                               double dData,
441                                               bool bData,
442                                               const CFX_ByteString& sData,
443                                               v8::Local<v8::Object> pData,
444                                               bool bDefaultPersistent) {
445   if (!propname)
446     return FALSE;
447
448   auto it = m_mapGlobal.find(propname);
449   if (it != m_mapGlobal.end()) {
450     JSGlobalData* pTemp = it->second;
451     if (pTemp->bDeleted || pTemp->nType != nType) {
452       pTemp->dData = 0;
453       pTemp->bData = 0;
454       pTemp->sData = "";
455       pTemp->nType = nType;
456     }
457
458     pTemp->bDeleted = FALSE;
459     switch (nType) {
460       case JS_GLOBALDATA_TYPE_NUMBER: {
461         pTemp->dData = dData;
462       } break;
463       case JS_GLOBALDATA_TYPE_BOOLEAN: {
464         pTemp->bData = bData;
465       } break;
466       case JS_GLOBALDATA_TYPE_STRING: {
467         pTemp->sData = sData;
468       } break;
469       case JS_GLOBALDATA_TYPE_OBJECT: {
470         pTemp->pData.Reset(FXJS_GetRuntime(pData), pData);
471       } break;
472       case JS_GLOBALDATA_TYPE_NULL:
473         break;
474       default:
475         return FALSE;
476     }
477     return TRUE;
478   }
479
480   JSGlobalData* pNewData = NULL;
481
482   switch (nType) {
483     case JS_GLOBALDATA_TYPE_NUMBER: {
484       pNewData = new JSGlobalData;
485       pNewData->nType = JS_GLOBALDATA_TYPE_NUMBER;
486       pNewData->dData = dData;
487       pNewData->bPersistent = bDefaultPersistent;
488     } break;
489     case JS_GLOBALDATA_TYPE_BOOLEAN: {
490       pNewData = new JSGlobalData;
491       pNewData->nType = JS_GLOBALDATA_TYPE_BOOLEAN;
492       pNewData->bData = bData;
493       pNewData->bPersistent = bDefaultPersistent;
494     } break;
495     case JS_GLOBALDATA_TYPE_STRING: {
496       pNewData = new JSGlobalData;
497       pNewData->nType = JS_GLOBALDATA_TYPE_STRING;
498       pNewData->sData = sData;
499       pNewData->bPersistent = bDefaultPersistent;
500     } break;
501     case JS_GLOBALDATA_TYPE_OBJECT: {
502       pNewData = new JSGlobalData;
503       pNewData->nType = JS_GLOBALDATA_TYPE_OBJECT;
504       pNewData->pData.Reset(FXJS_GetRuntime(pData), pData);
505       pNewData->bPersistent = bDefaultPersistent;
506     } break;
507     case JS_GLOBALDATA_TYPE_NULL: {
508       pNewData = new JSGlobalData;
509       pNewData->nType = JS_GLOBALDATA_TYPE_NULL;
510       pNewData->bPersistent = bDefaultPersistent;
511     } break;
512     default:
513       return FALSE;
514   }
515
516   m_mapGlobal[propname] = pNewData;
517   return TRUE;
518 }
519
520 CJS_Value::Type GET_VALUE_TYPE(v8::Local<v8::Value> p) {
521   const unsigned int nHash = JS_CalcHash(FXJS_GetTypeof(p));
522
523   if (nHash == JSCONST_nUndefHash)
524     return CJS_Value::VT_undefined;
525   if (nHash == JSCONST_nNullHash)
526     return CJS_Value::VT_null;
527   if (nHash == JSCONST_nStringHash)
528     return CJS_Value::VT_string;
529   if (nHash == JSCONST_nNumberHash)
530     return CJS_Value::VT_number;
531   if (nHash == JSCONST_nBoolHash)
532     return CJS_Value::VT_boolean;
533   if (nHash == JSCONST_nDateHash)
534     return CJS_Value::VT_date;
535   if (nHash == JSCONST_nObjectHash)
536     return CJS_Value::VT_object;
537   if (nHash == JSCONST_nFXobjHash)
538     return CJS_Value::VT_fxobject;
539
540   return CJS_Value::VT_unknown;
541 }