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