1 // Copyright 2017 The PDFium Authors
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 "fxjs/xfa/cjx_instancemanager.h"
8
9 #include <algorithm>
10
11 #include "core/fxcrt/check_op.h"
12 #include "core/fxcrt/span.h"
13 #include "fxjs/fxv8.h"
14 #include "fxjs/js_resources.h"
15 #include "fxjs/xfa/cfxjse_engine.h"
16 #include "v8/include/v8-object.h"
17 #include "v8/include/v8-primitive.h"
18 #include "xfa/fxfa/cxfa_ffdoc.h"
19 #include "xfa/fxfa/cxfa_ffnotify.h"
20 #include "xfa/fxfa/parser/cxfa_document.h"
21 #include "xfa/fxfa/parser/cxfa_instancemanager.h"
22 #include "xfa/fxfa/parser/cxfa_occur.h"
23 #include "xfa/fxfa/parser/cxfa_subform.h"
24
25 const CJX_MethodSpec CJX_InstanceManager::MethodSpecs[] = {
26 {"addInstance", addInstance_static},
27 {"insertInstance", insertInstance_static},
28 {"moveInstance", moveInstance_static},
29 {"removeInstance", removeInstance_static},
30 {"setInstances", setInstances_static}};
31
CJX_InstanceManager(CXFA_InstanceManager * mgr)32 CJX_InstanceManager::CJX_InstanceManager(CXFA_InstanceManager* mgr)
33 : CJX_Node(mgr) {
34 DefineMethods(MethodSpecs);
35 }
36
37 CJX_InstanceManager::~CJX_InstanceManager() = default;
38
DynamicTypeIs(TypeTag eType) const39 bool CJX_InstanceManager::DynamicTypeIs(TypeTag eType) const {
40 return eType == static_type__ || ParentType__::DynamicTypeIs(eType);
41 }
42
SetInstances(v8::Isolate * pIsolate,int32_t iDesired)43 int32_t CJX_InstanceManager::SetInstances(v8::Isolate* pIsolate,
44 int32_t iDesired) {
45 CXFA_Occur* occur = GetXFANode()->GetOccurIfExists();
46 int32_t iMin = occur ? occur->GetMin() : CXFA_Occur::kDefaultMin;
47 if (iDesired < iMin) {
48 ThrowTooManyOccurrencesException(pIsolate, L"min");
49 return 1;
50 }
51
52 int32_t iMax = occur ? occur->GetMax() : CXFA_Occur::kDefaultMax;
53 if (iMax >= 0 && iDesired > iMax) {
54 ThrowTooManyOccurrencesException(pIsolate, L"max");
55 return 2;
56 }
57
58 int32_t iCount = GetXFANode()->GetCount();
59 if (iDesired == iCount)
60 return 0;
61
62 if (iDesired < iCount) {
63 WideString wsInstManagerName = GetCData(XFA_Attribute::Name);
64 WideString wsInstanceName = WideString(
65 wsInstManagerName.IsEmpty()
66 ? wsInstManagerName
67 : wsInstManagerName.Last(wsInstManagerName.GetLength() - 1));
68 uint32_t dInstanceNameHash =
69 FX_HashCode_GetW(wsInstanceName.AsStringView());
70 CXFA_Node* pPrevSibling = iDesired == 0
71 ? GetXFANode()
72 : GetXFANode()->GetItemIfExists(iDesired - 1);
73 if (!pPrevSibling) {
74 // TODO(dsinclair): Better error?
75 ThrowIndexOutOfBoundsException(pIsolate);
76 return 0;
77 }
78
79 while (iCount > iDesired) {
80 CXFA_Node* pRemoveInstance = pPrevSibling->GetNextSibling();
81 const XFA_Element type = pRemoveInstance->GetElementType();
82 if (type != XFA_Element::Subform && type != XFA_Element::SubformSet) {
83 continue;
84 }
85 CHECK_NE(type, XFA_Element::InstanceManager);
86 if (pRemoveInstance->GetNameHash() == dInstanceNameHash) {
87 GetXFANode()->RemoveItem(pRemoveInstance, true);
88 iCount--;
89 }
90 }
91 } else {
92 while (iCount < iDesired) {
93 CXFA_Node* pNewInstance = GetXFANode()->CreateInstanceIfPossible(true);
94 if (!pNewInstance)
95 return 0;
96
97 GetXFANode()->InsertItem(pNewInstance, iCount, iCount, false);
98 ++iCount;
99
100 CXFA_FFNotify* pNotify = GetDocument()->GetNotify();
101 if (!pNotify)
102 return 0;
103
104 pNotify->RunNodeInitialize(pNewInstance);
105 }
106 }
107 GetDocument()->GetLayoutProcessor()->SetHasChangedContainer();
108 return 0;
109 }
110
MoveInstance(v8::Isolate * pIsolate,int32_t iTo,int32_t iFrom)111 int32_t CJX_InstanceManager::MoveInstance(v8::Isolate* pIsolate,
112 int32_t iTo,
113 int32_t iFrom) {
114 int32_t iCount = GetXFANode()->GetCount();
115 if (iFrom > iCount || iTo > iCount - 1) {
116 ThrowIndexOutOfBoundsException(pIsolate);
117 return 1;
118 }
119 if (iFrom < 0 || iTo < 0 || iFrom == iTo)
120 return 0;
121
122 CXFA_Node* pMoveInstance = GetXFANode()->GetItemIfExists(iFrom);
123 if (!pMoveInstance) {
124 ThrowIndexOutOfBoundsException(pIsolate);
125 return 1;
126 }
127
128 GetXFANode()->RemoveItem(pMoveInstance, false);
129 GetXFANode()->InsertItem(pMoveInstance, iTo, iCount - 1, true);
130 GetDocument()->GetLayoutProcessor()->SetHasChangedContainer();
131 return 0;
132 }
133
moveInstance(CFXJSE_Engine * runtime,pdfium::span<v8::Local<v8::Value>> params)134 CJS_Result CJX_InstanceManager::moveInstance(
135 CFXJSE_Engine* runtime,
136 pdfium::span<v8::Local<v8::Value>> params) {
137 CXFA_Document* doc = runtime->GetDocument();
138 if (doc->GetFormType() != FormType::kXFAFull)
139 return CJS_Result::Failure(JSMessage::kNotSupportedError);
140
141 if (params.size() != 2)
142 return CJS_Result::Failure(JSMessage::kParamError);
143
144 int32_t iFrom = runtime->ToInt32(params[0]);
145 int32_t iTo = runtime->ToInt32(params[1]);
146 MoveInstance(runtime->GetIsolate(), iTo, iFrom);
147
148 CXFA_FFNotify* pNotify = GetDocument()->GetNotify();
149 if (!pNotify)
150 return CJS_Result::Success();
151
152 CXFA_Node* pXFA = GetXFANode();
153 auto* pToInstance = CXFA_Subform::FromNode(pXFA->GetItemIfExists(iTo));
154 if (pToInstance)
155 pNotify->RunSubformIndexChange(pToInstance);
156
157 auto* pFromInstance = CXFA_Subform::FromNode(pXFA->GetItemIfExists(iFrom));
158 if (pFromInstance)
159 pNotify->RunSubformIndexChange(pFromInstance);
160
161 return CJS_Result::Success();
162 }
163
removeInstance(CFXJSE_Engine * runtime,pdfium::span<v8::Local<v8::Value>> params)164 CJS_Result CJX_InstanceManager::removeInstance(
165 CFXJSE_Engine* runtime,
166 pdfium::span<v8::Local<v8::Value>> params) {
167 CXFA_Document* doc = runtime->GetDocument();
168 if (doc->GetFormType() != FormType::kXFAFull)
169 return CJS_Result::Failure(JSMessage::kNotSupportedError);
170
171 if (params.size() != 1)
172 return CJS_Result::Failure(JSMessage::kParamError);
173
174 int32_t iIndex = runtime->ToInt32(params[0]);
175 int32_t iCount = GetXFANode()->GetCount();
176 if (iIndex < 0 || iIndex >= iCount)
177 return CJS_Result::Failure(JSMessage::kInvalidInputError);
178
179 CXFA_Occur* occur = GetXFANode()->GetOccurIfExists();
180 int32_t iMin = occur ? occur->GetMin() : CXFA_Occur::kDefaultMin;
181 if (iCount - 1 < iMin)
182 return CJS_Result::Failure(JSMessage::kTooManyOccurrences);
183
184 CXFA_Node* pRemoveInstance = GetXFANode()->GetItemIfExists(iIndex);
185 if (!pRemoveInstance)
186 return CJS_Result::Failure(JSMessage::kParamError);
187
188 GetXFANode()->RemoveItem(pRemoveInstance, true);
189
190 CXFA_FFNotify* pNotify = GetDocument()->GetNotify();
191 if (pNotify) {
192 CXFA_Node* pXFA = GetXFANode();
193 for (int32_t i = iIndex; i < iCount - 1; i++) {
194 auto* pSubformInstance = CXFA_Subform::FromNode(pXFA->GetItemIfExists(i));
195 if (pSubformInstance)
196 pNotify->RunSubformIndexChange(pSubformInstance);
197 }
198 }
199 GetDocument()->GetLayoutProcessor()->SetHasChangedContainer();
200 return CJS_Result::Success();
201 }
202
setInstances(CFXJSE_Engine * runtime,pdfium::span<v8::Local<v8::Value>> params)203 CJS_Result CJX_InstanceManager::setInstances(
204 CFXJSE_Engine* runtime,
205 pdfium::span<v8::Local<v8::Value>> params) {
206 CXFA_Document* doc = runtime->GetDocument();
207 if (doc->GetFormType() != FormType::kXFAFull)
208 return CJS_Result::Failure(JSMessage::kNotSupportedError);
209
210 if (params.size() != 1)
211 return CJS_Result::Failure(JSMessage::kParamError);
212
213 SetInstances(runtime->GetIsolate(), runtime->ToInt32(params[0]));
214 return CJS_Result::Success();
215 }
216
addInstance(CFXJSE_Engine * runtime,pdfium::span<v8::Local<v8::Value>> params)217 CJS_Result CJX_InstanceManager::addInstance(
218 CFXJSE_Engine* runtime,
219 pdfium::span<v8::Local<v8::Value>> params) {
220 CXFA_Document* doc = runtime->GetDocument();
221 if (doc->GetFormType() != FormType::kXFAFull)
222 return CJS_Result::Failure(JSMessage::kNotSupportedError);
223
224 if (!params.empty() && params.size() != 1)
225 return CJS_Result::Failure(JSMessage::kParamError);
226
227 bool fFlags = true;
228 if (params.size() == 1)
229 fFlags = runtime->ToBoolean(params[0]);
230
231 int32_t iCount = GetXFANode()->GetCount();
232 CXFA_Occur* occur = GetXFANode()->GetOccurIfExists();
233 int32_t iMax = occur ? occur->GetMax() : CXFA_Occur::kDefaultMax;
234 if (iMax >= 0 && iCount >= iMax)
235 return CJS_Result::Failure(JSMessage::kTooManyOccurrences);
236
237 CXFA_Node* pNewInstance = GetXFANode()->CreateInstanceIfPossible(fFlags);
238 if (!pNewInstance)
239 return CJS_Result::Success(runtime->NewNull());
240
241 GetXFANode()->InsertItem(pNewInstance, iCount, iCount, false);
242
243 CXFA_FFNotify* pNotify = GetDocument()->GetNotify();
244 if (pNotify) {
245 pNotify->RunNodeInitialize(pNewInstance);
246 GetDocument()->GetLayoutProcessor()->SetHasChangedContainer();
247 }
248
249 return CJS_Result::Success(
250 runtime->GetOrCreateJSBindingFromMap(pNewInstance));
251 }
252
insertInstance(CFXJSE_Engine * runtime,pdfium::span<v8::Local<v8::Value>> params)253 CJS_Result CJX_InstanceManager::insertInstance(
254 CFXJSE_Engine* runtime,
255 pdfium::span<v8::Local<v8::Value>> params) {
256 CXFA_Document* doc = runtime->GetDocument();
257 if (doc->GetFormType() != FormType::kXFAFull)
258 return CJS_Result::Failure(JSMessage::kNotSupportedError);
259
260 if (params.size() != 1 && params.size() != 2)
261 return CJS_Result::Failure(JSMessage::kParamError);
262
263 int32_t iIndex = runtime->ToInt32(params[0]);
264 bool bBind = false;
265 if (params.size() == 2)
266 bBind = runtime->ToBoolean(params[1]);
267
268 int32_t iCount = GetXFANode()->GetCount();
269 if (iIndex < 0 || iIndex > iCount)
270 return CJS_Result::Failure(JSMessage::kInvalidInputError);
271
272 CXFA_Occur* occur = GetXFANode()->GetOccurIfExists();
273 int32_t iMax = occur ? occur->GetMax() : CXFA_Occur::kDefaultMax;
274 if (iMax >= 0 && iCount >= iMax)
275 return CJS_Result::Failure(JSMessage::kInvalidInputError);
276
277 CXFA_Node* pNewInstance = GetXFANode()->CreateInstanceIfPossible(bBind);
278 if (!pNewInstance)
279 return CJS_Result::Success(runtime->NewNull());
280
281 GetXFANode()->InsertItem(pNewInstance, iIndex, iCount, true);
282
283 CXFA_FFNotify* pNotify = GetDocument()->GetNotify();
284 if (pNotify) {
285 pNotify->RunNodeInitialize(pNewInstance);
286 GetDocument()->GetLayoutProcessor()->SetHasChangedContainer();
287 }
288
289 return CJS_Result::Success(
290 runtime->GetOrCreateJSBindingFromMap(pNewInstance));
291 }
292
max(v8::Isolate * pIsolate,v8::Local<v8::Value> * pValue,bool bSetting,XFA_Attribute eAttribute)293 void CJX_InstanceManager::max(v8::Isolate* pIsolate,
294 v8::Local<v8::Value>* pValue,
295 bool bSetting,
296 XFA_Attribute eAttribute) {
297 if (bSetting) {
298 ThrowInvalidPropertyException(pIsolate);
299 return;
300 }
301 CXFA_Occur* occur = GetXFANode()->GetOccurIfExists();
302 *pValue = fxv8::NewNumberHelper(
303 pIsolate, occur ? occur->GetMax() : CXFA_Occur::kDefaultMax);
304 }
305
min(v8::Isolate * pIsolate,v8::Local<v8::Value> * pValue,bool bSetting,XFA_Attribute eAttribute)306 void CJX_InstanceManager::min(v8::Isolate* pIsolate,
307 v8::Local<v8::Value>* pValue,
308 bool bSetting,
309 XFA_Attribute eAttribute) {
310 if (bSetting) {
311 ThrowInvalidPropertyException(pIsolate);
312 return;
313 }
314 CXFA_Occur* occur = GetXFANode()->GetOccurIfExists();
315 *pValue = fxv8::NewNumberHelper(
316 pIsolate, occur ? occur->GetMin() : CXFA_Occur::kDefaultMin);
317 }
318
count(v8::Isolate * pIsolate,v8::Local<v8::Value> * pValue,bool bSetting,XFA_Attribute eAttribute)319 void CJX_InstanceManager::count(v8::Isolate* pIsolate,
320 v8::Local<v8::Value>* pValue,
321 bool bSetting,
322 XFA_Attribute eAttribute) {
323 if (bSetting) {
324 SetInstances(pIsolate, fxv8::ReentrantToInt32Helper(pIsolate, *pValue));
325 return;
326 }
327 *pValue = fxv8::NewNumberHelper(pIsolate, GetXFANode()->GetCount());
328 }
329