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