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 "JS_Runtime.h"
8 
9 #include "Consts.h"
10 #include "Document.h"
11 #include "Field.h"
12 #include "Icon.h"
13 #include "JS_Context.h"
14 #include "JS_Define.h"
15 #include "JS_EventHandler.h"
16 #include "JS_GlobalData.h"
17 #include "JS_Object.h"
18 #include "JS_Value.h"
19 #include "PublicMethods.h"
20 #include "app.h"
21 #include "color.h"
22 #include "console.h"
23 #include "event.h"
24 #include "fpdfsdk/include/fsdk_mgr.h"  // For CPDFDoc_Environment.
25 #include "fpdfsdk/include/javascript/IJavaScript.h"
26 #include "global.h"
27 #include "report.h"
28 #include "util.h"
29 #include "third_party/base/stl_util.h"
30 
31 #ifdef PDF_ENABLE_XFA
32 #include "fpdfsdk/include/fpdfxfa/fpdfxfa_app.h"
33 #include "xfa/src/fxjse/src/value.h"
34 #endif  // PDF_ENABLE_XFA
35 
36 // static
Initialize(unsigned int slot,void * isolate)37 void IJS_Runtime::Initialize(unsigned int slot, void* isolate) {
38   FXJS_Initialize(slot, reinterpret_cast<v8::Isolate*>(isolate));
39 }
40 
41 // static
Create(CPDFDoc_Environment * pEnv)42 IJS_Runtime* IJS_Runtime::Create(CPDFDoc_Environment* pEnv) {
43   return new CJS_Runtime(pEnv);
44 }
45 
46 // static
FromContext(const IJS_Context * cc)47 CJS_Runtime* CJS_Runtime::FromContext(const IJS_Context* cc) {
48   const CJS_Context* pContext = static_cast<const CJS_Context*>(cc);
49   return pContext->GetJSRuntime();
50 }
51 
CJS_Runtime(CPDFDoc_Environment * pApp)52 CJS_Runtime::CJS_Runtime(CPDFDoc_Environment* pApp)
53     : m_pApp(pApp),
54       m_pDocument(NULL),
55       m_bBlocking(FALSE),
56       m_isolate(NULL),
57       m_isolateManaged(false) {
58 #ifndef PDF_ENABLE_XFA
59   IPDF_JSPLATFORM* pPlatform = m_pApp->GetFormFillInfo()->m_pJsPlatform;
60   if (pPlatform->version <= 2) {
61     unsigned int embedderDataSlot = 0;
62     v8::Isolate* pExternalIsolate = nullptr;
63     if (pPlatform->version == 2) {
64       pExternalIsolate = reinterpret_cast<v8::Isolate*>(pPlatform->m_isolate);
65       embedderDataSlot = pPlatform->m_v8EmbedderSlot;
66 #else
67   if (CPDFXFA_App::GetInstance()->GetJSERuntime()) {
68     // TODO(tsepez): CPDFXFA_App should also use the embedder provided isolate.
69     m_isolate = (v8::Isolate*)CPDFXFA_App::GetInstance()->GetJSERuntime();
70   } else {
71     IPDF_JSPLATFORM* pPlatform = m_pApp->GetFormFillInfo()->m_pJsPlatform;
72     if (pPlatform->version <= 2) {
73       unsigned int embedderDataSlot = 0;
74       v8::Isolate* pExternalIsolate = nullptr;
75       if (pPlatform->version == 2) {
76         pExternalIsolate = reinterpret_cast<v8::Isolate*>(pPlatform->m_isolate);
77         embedderDataSlot = pPlatform->m_v8EmbedderSlot;
78       }
79       FXJS_Initialize(embedderDataSlot, pExternalIsolate);
80 #endif
81     }
82 #ifndef PDF_ENABLE_XFA
83     FXJS_Initialize(embedderDataSlot, pExternalIsolate);
84 #else
85     m_isolateManaged = FXJS_GetIsolate(&m_isolate);
86   }
87 
88   v8::Isolate* isolate = m_isolate;
89   v8::Isolate::Scope isolate_scope(isolate);
90   v8::Locker locker(isolate);
91   v8::HandleScope handle_scope(isolate);
92   if (CPDFXFA_App::GetInstance()->IsJavaScriptInitialized()) {
93     CJS_Context* pContext = (CJS_Context*)NewContext();
94     FXJS_InitializeRuntime(GetIsolate(), this, &m_context, &m_StaticObjects);
95     ReleaseContext(pContext);
96     return;
97 #endif
98   }
99 #ifndef PDF_ENABLE_XFA
100   m_isolateManaged = FXJS_GetIsolate(&m_isolate);
101 #else
102 
103 #endif
104   if (m_isolateManaged || FXJS_GlobalIsolateRefCount() == 0)
105     DefineJSObjects();
106 
107 #ifdef PDF_ENABLE_XFA
108   CPDFXFA_App::GetInstance()->SetJavaScriptInitialized(TRUE);
109 
110 #endif
111   CJS_Context* pContext = (CJS_Context*)NewContext();
112   FXJS_InitializeRuntime(GetIsolate(), this, &m_context, &m_StaticObjects);
113   ReleaseContext(pContext);
114 }
115 
116 CJS_Runtime::~CJS_Runtime() {
117   for (auto* obs : m_observers)
118     obs->OnDestroyed();
119 
120   for (int i = 0, sz = m_ContextArray.GetSize(); i < sz; i++)
121     delete m_ContextArray.GetAt(i);
122 
123   m_ContextArray.RemoveAll();
124 #ifndef PDF_ENABLE_XFA
125   FXJS_ReleaseRuntime(GetIsolate(), &m_context, &m_StaticObjects);
126 #endif
127 
128   m_pApp = NULL;
129   m_pDocument = NULL;
130   m_context.Reset();
131 
132   if (m_isolateManaged)
133     m_isolate->Dispose();
134 }
135 
136 void CJS_Runtime::DefineJSObjects() {
137   v8::Isolate::Scope isolate_scope(GetIsolate());
138 #ifdef PDF_ENABLE_XFA
139   v8::Locker locker(GetIsolate());
140 #endif
141   v8::HandleScope handle_scope(GetIsolate());
142   v8::Local<v8::Context> context = v8::Context::New(GetIsolate());
143   v8::Context::Scope context_scope(context);
144 
145   // The call order determines the "ObjDefID" assigned to each class.
146   // ObjDefIDs 0 - 2
147   CJS_Border::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
148   CJS_Display::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
149   CJS_Font::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
150 
151   // ObjDefIDs 3 - 5
152   CJS_Highlight::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
153   CJS_Position::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
154   CJS_ScaleHow::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
155 
156   // ObjDefIDs 6 - 8
157   CJS_ScaleWhen::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
158   CJS_Style::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
159   CJS_Zoomtype::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
160 
161   // ObjDefIDs 9 - 11
162   CJS_App::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
163   CJS_Color::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
164   CJS_Console::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
165 
166   // ObjDefIDs 12 - 14
167   CJS_Document::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_GLOBAL);
168   CJS_Event::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
169   CJS_Field::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_DYNAMIC);
170 
171   // ObjDefIDs 15 - 17
172   CJS_Global::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
173   CJS_Icon::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_DYNAMIC);
174   CJS_Util::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_STATIC);
175 
176   // ObjDefIDs 18 - 20 (these can't fail, return void).
177   CJS_PublicMethods::DefineJSObjects(GetIsolate());
178   CJS_GlobalConsts::DefineJSObjects(this);
179   CJS_GlobalArrays::DefineJSObjects(this);
180 
181   // ObjDefIDs 21 - 22.
182   CJS_TimerObj::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_DYNAMIC);
183   CJS_PrintParamsObj::DefineJSObjects(GetIsolate(), FXJSOBJTYPE_DYNAMIC);
184 }
185 
186 IJS_Context* CJS_Runtime::NewContext() {
187   CJS_Context* p = new CJS_Context(this);
188   m_ContextArray.Add(p);
189   return p;
190 }
191 
192 void CJS_Runtime::ReleaseContext(IJS_Context* pContext) {
193   CJS_Context* pJSContext = (CJS_Context*)pContext;
194 
195   for (int i = 0, sz = m_ContextArray.GetSize(); i < sz; i++) {
196     if (pJSContext == m_ContextArray.GetAt(i)) {
197       delete pJSContext;
198       m_ContextArray.RemoveAt(i);
199       break;
200     }
201   }
202 }
203 
204 IJS_Context* CJS_Runtime::GetCurrentContext() {
205   if (!m_ContextArray.GetSize())
206     return NULL;
207   return m_ContextArray.GetAt(m_ContextArray.GetSize() - 1);
208 }
209 
210 void CJS_Runtime::SetReaderDocument(CPDFSDK_Document* pReaderDoc) {
211   if (m_pDocument != pReaderDoc) {
212     v8::Isolate::Scope isolate_scope(m_isolate);
213 #ifdef PDF_ENABLE_XFA
214     v8::Locker locker(m_isolate);
215 #endif
216     v8::HandleScope handle_scope(m_isolate);
217     v8::Local<v8::Context> context =
218         v8::Local<v8::Context>::New(m_isolate, m_context);
219     v8::Context::Scope context_scope(context);
220 
221     m_pDocument = pReaderDoc;
222     if (pReaderDoc) {
223       v8::Local<v8::Object> pThis = FXJS_GetThisObj(GetIsolate());
224       if (!pThis.IsEmpty()) {
225         if (FXJS_GetObjDefnID(pThis) == CJS_Document::g_nObjDefnID) {
226           if (CJS_Document* pJSDocument =
227                   (CJS_Document*)FXJS_GetPrivate(GetIsolate(), pThis)) {
228             if (Document* pDocument = (Document*)pJSDocument->GetEmbedObject())
229               pDocument->AttachDoc(pReaderDoc);
230           }
231         }
232       }
233     }
234   }
235 }
236 
237 int CJS_Runtime::Execute(IJS_Context* cc,
238                          const wchar_t* script,
239                          CFX_WideString* info) {
240   FXJSErr error = {};
241   int nRet = FXJS_Execute(m_isolate, cc, script, &error);
242   if (nRet < 0) {
243     info->Format(L"[ Line: %05d { %s } ] : %s", error.linnum - 1, error.srcline,
244                  error.message);
245   }
246   return nRet;
247 }
248 
249 bool CJS_Runtime::AddEventToSet(const FieldEvent& event) {
250   return m_FieldEventSet.insert(event).second;
251 }
252 
253 void CJS_Runtime::RemoveEventFromSet(const FieldEvent& event) {
254   m_FieldEventSet.erase(event);
255 }
256 
257 v8::Local<v8::Context> CJS_Runtime::NewJSContext() {
258   return v8::Local<v8::Context>::New(m_isolate, m_context);
259 }
260 
261 #ifdef PDF_ENABLE_XFA
262 CFX_WideString ChangeObjName(const CFX_WideString& str) {
263   CFX_WideString sRet = str;
264   sRet.Replace(L"_", L".");
265   return sRet;
266 }
267 FX_BOOL CJS_Runtime::GetHValueByName(const CFX_ByteStringC& utf8Name,
268                                      FXJSE_HVALUE hValue) {
269 #ifdef PDF_ENABLE_XFA
270   const FX_CHAR* name = utf8Name.GetCStr();
271 
272   v8::Locker lock(GetIsolate());
273   v8::Isolate::Scope isolate_scope(GetIsolate());
274   v8::HandleScope handle_scope(GetIsolate());
275   v8::Local<v8::Context> old_context = GetIsolate()->GetCurrentContext();
276   v8::Local<v8::Context> context =
277       v8::Local<v8::Context>::New(GetIsolate(), m_context);
278   v8::Context::Scope context_scope(context);
279 
280   // Caution: We're about to hand to XFA an object that in order to invoke
281   // methods will require that the current v8::Context always has a pointer
282   // to a CJS_Runtime in its embedder data slot. Unfortunately, XFA creates
283   // its own v8::Context which has not initialized the embedder data slot.
284   // Do so now.
285   // TODO(tsepez): redesign PDF-side objects to not rely on v8::Context's
286   // embedder data slots, and/or to always use the right context.
287   FXJS_SetRuntimeForV8Context(old_context, this);
288 
289   v8::Local<v8::Value> propvalue =
290       context->Global()->Get(v8::String::NewFromUtf8(
291           GetIsolate(), name, v8::String::kNormalString, utf8Name.GetLength()));
292 
293   if (propvalue.IsEmpty()) {
294     FXJSE_Value_SetUndefined(hValue);
295     return FALSE;
296   }
297   ((CFXJSE_Value*)hValue)->ForceSetValue(propvalue);
298 #endif
299 
300   return TRUE;
301 }
302 FX_BOOL CJS_Runtime::SetHValueByName(const CFX_ByteStringC& utf8Name,
303                                      FXJSE_HVALUE hValue) {
304 #ifdef PDF_ENABLE_XFA
305   if (utf8Name.IsEmpty() || hValue == NULL)
306     return FALSE;
307   const FX_CHAR* name = utf8Name.GetCStr();
308   v8::Isolate* pIsolate = GetIsolate();
309   v8::Locker lock(pIsolate);
310   v8::Isolate::Scope isolate_scope(pIsolate);
311   v8::HandleScope handle_scope(pIsolate);
312   v8::Local<v8::Context> context =
313       v8::Local<v8::Context>::New(pIsolate, m_context);
314   v8::Context::Scope context_scope(context);
315 
316   // v8::Local<v8::Context> tmpCotext =
317   // v8::Local<v8::Context>::New(GetIsolate(), m_context);
318   v8::Local<v8::Value> propvalue = v8::Local<v8::Value>::New(
319       GetIsolate(), ((CFXJSE_Value*)hValue)->DirectGetValue());
320   context->Global()->Set(
321       v8::String::NewFromUtf8(pIsolate, name, v8::String::kNormalString,
322                               utf8Name.GetLength()),
323       propvalue);
324 #endif
325   return TRUE;
326 }
327 
328 #endif
329 void CJS_Runtime::AddObserver(Observer* observer) {
330   ASSERT(!pdfium::ContainsKey(m_observers, observer));
331   m_observers.insert(observer);
332 }
333 
334 void CJS_Runtime::RemoveObserver(Observer* observer) {
335   ASSERT(pdfium::ContainsKey(m_observers, observer));
336   m_observers.erase(observer);
337 }
338