1 | /* |
2 | * Copyright (C) 2004-2017 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | |
28 | #if ENABLE(NETSCAPE_PLUGIN_API) |
29 | |
30 | #include "NP_jsobject.h" |
31 | |
32 | #include "IdentifierRep.h" |
33 | #include "JSDOMBinding.h" |
34 | #include "c_instance.h" |
35 | #include "c_utility.h" |
36 | #include "npruntime_priv.h" |
37 | #include "runtime_root.h" |
38 | #include <JavaScriptCore/CatchScope.h> |
39 | #include <JavaScriptCore/Completion.h> |
40 | #include <JavaScriptCore/Error.h> |
41 | #include <JavaScriptCore/JSGlobalObject.h> |
42 | #include <JavaScriptCore/JSLock.h> |
43 | #include <JavaScriptCore/PropertyNameArray.h> |
44 | #include <JavaScriptCore/SourceCode.h> |
45 | #include <wtf/NeverDestroyed.h> |
46 | #include <wtf/text/WTFString.h> |
47 | |
48 | #pragma GCC visibility push(default) |
49 | #include "npruntime_impl.h" |
50 | #pragma GCC visibility pop |
51 | |
52 | namespace JSC { |
53 | using namespace Bindings; |
54 | using namespace WebCore; |
55 | |
56 | class ObjectMap { |
57 | public: |
58 | NPObject* get(RootObject* rootObject, JSObject* jsObject) |
59 | { |
60 | return m_map.get(rootObject).get(jsObject); |
61 | } |
62 | |
63 | void add(RootObject* rootObject, JSObject* jsObject, NPObject* npObject) |
64 | { |
65 | HashMap<RootObject*, JSToNPObjectMap>::iterator iter = m_map.find(rootObject); |
66 | if (iter == m_map.end()) { |
67 | rootObject->addInvalidationCallback(&m_invalidationCallback); |
68 | iter = m_map.add(rootObject, JSToNPObjectMap()).iterator; |
69 | } |
70 | |
71 | ASSERT(iter->value.find(jsObject) == iter->value.end()); |
72 | iter->value.add(jsObject, npObject); |
73 | } |
74 | |
75 | void remove(RootObject* rootObject) |
76 | { |
77 | ASSERT(m_map.contains(rootObject)); |
78 | m_map.remove(rootObject); |
79 | } |
80 | |
81 | void remove(RootObject* rootObject, JSObject* jsObject) |
82 | { |
83 | HashMap<RootObject*, JSToNPObjectMap>::iterator iter = m_map.find(rootObject); |
84 | ASSERT(iter != m_map.end()); |
85 | ASSERT(iter->value.find(jsObject) != iter->value.end()); |
86 | |
87 | iter->value.remove(jsObject); |
88 | } |
89 | |
90 | private: |
91 | struct RootObjectInvalidationCallback : public RootObject::InvalidationCallback { |
92 | void operator()(RootObject*) override; |
93 | }; |
94 | RootObjectInvalidationCallback m_invalidationCallback; |
95 | |
96 | // JSObjects are protected by RootObject. |
97 | typedef HashMap<JSObject*, NPObject*> JSToNPObjectMap; |
98 | HashMap<RootObject*, JSToNPObjectMap> m_map; |
99 | }; |
100 | |
101 | |
102 | static ObjectMap& objectMap() |
103 | { |
104 | static NeverDestroyed<ObjectMap> map; |
105 | return map; |
106 | } |
107 | |
108 | void ObjectMap::RootObjectInvalidationCallback::operator()(RootObject* rootObject) |
109 | { |
110 | objectMap().remove(rootObject); |
111 | } |
112 | |
113 | static void getListFromVariantArgs(ExecState* exec, const NPVariant* args, unsigned argCount, RootObject* rootObject, MarkedArgumentBuffer& aList) |
114 | { |
115 | for (unsigned i = 0; i < argCount; ++i) |
116 | aList.append(convertNPVariantToValue(exec, &args[i], rootObject)); |
117 | } |
118 | |
119 | static NPObject* jsAllocate(NPP, NPClass*) |
120 | { |
121 | return static_cast<NPObject*>(malloc(sizeof(JavaScriptObject))); |
122 | } |
123 | |
124 | static void jsDeallocate(NPObject* npObj) |
125 | { |
126 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(npObj); |
127 | |
128 | if (obj->rootObject && obj->rootObject->isValid()) { |
129 | objectMap().remove(obj->rootObject, obj->imp); |
130 | obj->rootObject->gcUnprotect(obj->imp); |
131 | } |
132 | |
133 | if (obj->rootObject) |
134 | obj->rootObject->deref(); |
135 | |
136 | free(obj); |
137 | } |
138 | |
139 | static NPClass javascriptClass = { 1, jsAllocate, jsDeallocate, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
140 | static NPClass noScriptClass = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
141 | |
142 | extern "C" { |
143 | NPClass* NPScriptObjectClass = &javascriptClass; |
144 | static NPClass* NPNoScriptObjectClass = &noScriptClass; |
145 | |
146 | NPObject* _NPN_CreateScriptObject(NPP npp, JSObject* imp, RefPtr<RootObject>&& rootObject) |
147 | { |
148 | if (NPObject* object = objectMap().get(rootObject.get(), imp)) |
149 | return _NPN_RetainObject(object); |
150 | |
151 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(_NPN_CreateObject(npp, NPScriptObjectClass)); |
152 | |
153 | obj->rootObject = rootObject.leakRef(); |
154 | |
155 | if (obj->rootObject) { |
156 | obj->rootObject->gcProtect(imp); |
157 | objectMap().add(obj->rootObject, imp, reinterpret_cast<NPObject*>(obj)); |
158 | } |
159 | |
160 | obj->imp = imp; |
161 | |
162 | return reinterpret_cast<NPObject*>(obj); |
163 | } |
164 | |
165 | NPObject* _NPN_CreateNoScriptObject(void) |
166 | { |
167 | return _NPN_CreateObject(0, NPNoScriptObjectClass); |
168 | } |
169 | |
170 | bool _NPN_InvokeDefault(NPP, NPObject* o, const NPVariant* args, uint32_t argCount, NPVariant* result) |
171 | { |
172 | if (o->_class == NPScriptObjectClass) { |
173 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
174 | |
175 | VOID_TO_NPVARIANT(*result); |
176 | |
177 | // Lookup the function object. |
178 | RootObject* rootObject = obj->rootObject; |
179 | if (!rootObject || !rootObject->isValid()) |
180 | return false; |
181 | |
182 | auto globalObject = rootObject->globalObject(); |
183 | VM& vm = globalObject->vm(); |
184 | JSLockHolder lock(vm); |
185 | auto scope = DECLARE_CATCH_SCOPE(vm); |
186 | |
187 | ExecState* exec = globalObject->globalExec(); |
188 | |
189 | // Call the function object. |
190 | JSValue function = obj->imp; |
191 | CallData callData; |
192 | CallType callType = getCallData(vm, function, callData); |
193 | if (callType == CallType::None) |
194 | return false; |
195 | |
196 | MarkedArgumentBuffer argList; |
197 | getListFromVariantArgs(exec, args, argCount, rootObject, argList); |
198 | RELEASE_ASSERT(!argList.hasOverflowed()); |
199 | JSValue resultV = JSC::call(exec, function, callType, callData, function, argList); |
200 | |
201 | // Convert and return the result of the function call. |
202 | convertValueToNPVariant(exec, resultV, result); |
203 | scope.clearException(); |
204 | return true; |
205 | } |
206 | |
207 | if (o->_class->invokeDefault) |
208 | return o->_class->invokeDefault(o, args, argCount, result); |
209 | VOID_TO_NPVARIANT(*result); |
210 | return true; |
211 | } |
212 | |
213 | bool _NPN_Invoke(NPP npp, NPObject* o, NPIdentifier methodName, const NPVariant* args, uint32_t argCount, NPVariant* result) |
214 | { |
215 | if (o->_class == NPScriptObjectClass) { |
216 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
217 | |
218 | IdentifierRep* i = static_cast<IdentifierRep*>(methodName); |
219 | if (!i->isString()) |
220 | return false; |
221 | |
222 | // Special case the "eval" method. |
223 | if (methodName == _NPN_GetStringIdentifier("eval" )) { |
224 | if (argCount != 1) |
225 | return false; |
226 | if (args[0].type != NPVariantType_String) |
227 | return false; |
228 | return _NPN_Evaluate(npp, o, const_cast<NPString*>(&args[0].value.stringValue), result); |
229 | } |
230 | |
231 | // Look up the function object. |
232 | RootObject* rootObject = obj->rootObject; |
233 | if (!rootObject || !rootObject->isValid()) |
234 | return false; |
235 | |
236 | auto globalObject = rootObject->globalObject(); |
237 | VM& vm = globalObject->vm(); |
238 | JSLockHolder lock(vm); |
239 | auto scope = DECLARE_CATCH_SCOPE(vm); |
240 | |
241 | ExecState* exec = globalObject->globalExec(); |
242 | JSValue function = obj->imp->get(exec, identifierFromNPIdentifier(exec, i->string())); |
243 | CallData callData; |
244 | CallType callType = getCallData(vm, function, callData); |
245 | if (callType == CallType::None) |
246 | return false; |
247 | |
248 | // Call the function object. |
249 | MarkedArgumentBuffer argList; |
250 | getListFromVariantArgs(exec, args, argCount, rootObject, argList); |
251 | RELEASE_ASSERT(!argList.hasOverflowed()); |
252 | JSValue resultV = JSC::call(exec, function, callType, callData, obj->imp, argList); |
253 | |
254 | // Convert and return the result of the function call. |
255 | convertValueToNPVariant(exec, resultV, result); |
256 | scope.clearException(); |
257 | return true; |
258 | } |
259 | |
260 | if (o->_class->invoke) |
261 | return o->_class->invoke(o, methodName, args, argCount, result); |
262 | |
263 | VOID_TO_NPVARIANT(*result); |
264 | return true; |
265 | } |
266 | |
267 | bool _NPN_Evaluate(NPP, NPObject* o, NPString* s, NPVariant* variant) |
268 | { |
269 | if (o->_class == NPScriptObjectClass) { |
270 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
271 | |
272 | RootObject* rootObject = obj->rootObject; |
273 | if (!rootObject || !rootObject->isValid()) |
274 | return false; |
275 | |
276 | auto globalObject = rootObject->globalObject(); |
277 | VM& vm = globalObject->vm(); |
278 | JSLockHolder lock(vm); |
279 | auto scope = DECLARE_CATCH_SCOPE(vm); |
280 | |
281 | ExecState* exec = globalObject->globalExec(); |
282 | String scriptString = convertNPStringToUTF16(s); |
283 | |
284 | JSValue returnValue = JSC::evaluate(exec, JSC::makeSource(scriptString, { }), JSC::JSValue()); |
285 | |
286 | convertValueToNPVariant(exec, returnValue, variant); |
287 | scope.clearException(); |
288 | return true; |
289 | } |
290 | |
291 | VOID_TO_NPVARIANT(*variant); |
292 | return false; |
293 | } |
294 | |
295 | bool _NPN_GetProperty(NPP, NPObject* o, NPIdentifier propertyName, NPVariant* variant) |
296 | { |
297 | if (o->_class == NPScriptObjectClass) { |
298 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
299 | |
300 | RootObject* rootObject = obj->rootObject; |
301 | if (!rootObject || !rootObject->isValid()) |
302 | return false; |
303 | |
304 | auto globalObject = rootObject->globalObject(); |
305 | VM& vm = globalObject->vm(); |
306 | JSLockHolder lock(vm); |
307 | auto scope = DECLARE_CATCH_SCOPE(vm); |
308 | |
309 | ExecState* exec = globalObject->globalExec(); |
310 | IdentifierRep* i = static_cast<IdentifierRep*>(propertyName); |
311 | |
312 | JSValue result; |
313 | if (i->isString()) |
314 | result = obj->imp->get(exec, identifierFromNPIdentifier(exec, i->string())); |
315 | else |
316 | result = obj->imp->get(exec, i->number()); |
317 | |
318 | convertValueToNPVariant(exec, result, variant); |
319 | scope.clearException(); |
320 | return true; |
321 | } |
322 | |
323 | if (o->_class->hasProperty && o->_class->getProperty) { |
324 | if (o->_class->hasProperty(o, propertyName)) |
325 | return o->_class->getProperty(o, propertyName, variant); |
326 | return false; |
327 | } |
328 | |
329 | VOID_TO_NPVARIANT(*variant); |
330 | return false; |
331 | } |
332 | |
333 | bool _NPN_SetProperty(NPP, NPObject* o, NPIdentifier propertyName, const NPVariant* variant) |
334 | { |
335 | if (o->_class == NPScriptObjectClass) { |
336 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
337 | |
338 | RootObject* rootObject = obj->rootObject; |
339 | if (!rootObject || !rootObject->isValid()) |
340 | return false; |
341 | |
342 | auto globalObject = rootObject->globalObject(); |
343 | VM& vm = globalObject->vm(); |
344 | JSLockHolder lock(vm); |
345 | auto scope = DECLARE_CATCH_SCOPE(vm); |
346 | |
347 | ExecState* exec = globalObject->globalExec(); |
348 | IdentifierRep* i = static_cast<IdentifierRep*>(propertyName); |
349 | |
350 | if (i->isString()) { |
351 | PutPropertySlot slot(obj->imp); |
352 | obj->imp->methodTable(vm)->put(obj->imp, exec, identifierFromNPIdentifier(exec, i->string()), convertNPVariantToValue(exec, variant, rootObject), slot); |
353 | } else |
354 | obj->imp->methodTable(vm)->putByIndex(obj->imp, exec, i->number(), convertNPVariantToValue(exec, variant, rootObject), false); |
355 | scope.clearException(); |
356 | return true; |
357 | } |
358 | |
359 | if (o->_class->setProperty) |
360 | return o->_class->setProperty(o, propertyName, variant); |
361 | |
362 | return false; |
363 | } |
364 | |
365 | bool _NPN_RemoveProperty(NPP, NPObject* o, NPIdentifier propertyName) |
366 | { |
367 | if (o->_class == NPScriptObjectClass) { |
368 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
369 | |
370 | RootObject* rootObject = obj->rootObject; |
371 | if (!rootObject || !rootObject->isValid()) |
372 | return false; |
373 | |
374 | auto globalObject = rootObject->globalObject(); |
375 | VM& vm = globalObject->vm(); |
376 | JSLockHolder lock(vm); |
377 | auto scope = DECLARE_CATCH_SCOPE(vm); |
378 | |
379 | ExecState* exec = globalObject->globalExec(); |
380 | |
381 | IdentifierRep* i = static_cast<IdentifierRep*>(propertyName); |
382 | if (i->isString()) { |
383 | if (!obj->imp->hasProperty(exec, identifierFromNPIdentifier(exec, i->string()))) { |
384 | scope.clearException(); |
385 | return false; |
386 | } |
387 | } else { |
388 | if (!obj->imp->hasProperty(exec, i->number())) { |
389 | scope.clearException(); |
390 | return false; |
391 | } |
392 | } |
393 | |
394 | if (i->isString()) |
395 | obj->imp->methodTable(vm)->deleteProperty(obj->imp, exec, identifierFromNPIdentifier(exec, i->string())); |
396 | else |
397 | obj->imp->methodTable(vm)->deletePropertyByIndex(obj->imp, exec, i->number()); |
398 | |
399 | scope.clearException(); |
400 | return true; |
401 | } |
402 | return false; |
403 | } |
404 | |
405 | bool _NPN_HasProperty(NPP, NPObject* o, NPIdentifier propertyName) |
406 | { |
407 | if (o->_class == NPScriptObjectClass) { |
408 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
409 | |
410 | RootObject* rootObject = obj->rootObject; |
411 | if (!rootObject || !rootObject->isValid()) |
412 | return false; |
413 | |
414 | auto globalObject = rootObject->globalObject(); |
415 | VM& vm = globalObject->vm(); |
416 | JSLockHolder lock(vm); |
417 | auto scope = DECLARE_CATCH_SCOPE(vm); |
418 | |
419 | ExecState* exec = globalObject->globalExec(); |
420 | IdentifierRep* i = static_cast<IdentifierRep*>(propertyName); |
421 | if (i->isString()) { |
422 | bool result = obj->imp->hasProperty(exec, identifierFromNPIdentifier(exec, i->string())); |
423 | scope.clearException(); |
424 | return result; |
425 | } |
426 | |
427 | bool result = obj->imp->hasProperty(exec, i->number()); |
428 | scope.clearException(); |
429 | return result; |
430 | } |
431 | |
432 | if (o->_class->hasProperty) |
433 | return o->_class->hasProperty(o, propertyName); |
434 | |
435 | return false; |
436 | } |
437 | |
438 | bool _NPN_HasMethod(NPP, NPObject* o, NPIdentifier methodName) |
439 | { |
440 | if (o->_class == NPScriptObjectClass) { |
441 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
442 | |
443 | IdentifierRep* i = static_cast<IdentifierRep*>(methodName); |
444 | if (!i->isString()) |
445 | return false; |
446 | |
447 | RootObject* rootObject = obj->rootObject; |
448 | if (!rootObject || !rootObject->isValid()) |
449 | return false; |
450 | |
451 | auto globalObject = rootObject->globalObject(); |
452 | VM& vm = globalObject->vm(); |
453 | JSLockHolder lock(vm); |
454 | auto scope = DECLARE_CATCH_SCOPE(vm); |
455 | |
456 | ExecState* exec = globalObject->globalExec(); |
457 | JSValue func = obj->imp->get(exec, identifierFromNPIdentifier(exec, i->string())); |
458 | scope.clearException(); |
459 | return !func.isUndefined(); |
460 | } |
461 | |
462 | if (o->_class->hasMethod) |
463 | return o->_class->hasMethod(o, methodName); |
464 | |
465 | return false; |
466 | } |
467 | |
468 | void _NPN_SetException(NPObject*, const NPUTF8* message) |
469 | { |
470 | // Ignoring the NPObject param is consistent with the Mozilla implementation. |
471 | String exception(message); |
472 | CInstance::setGlobalException(exception); |
473 | } |
474 | |
475 | bool _NPN_Enumerate(NPP, NPObject* o, NPIdentifier** identifier, uint32_t* count) |
476 | { |
477 | if (o->_class == NPScriptObjectClass) { |
478 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
479 | |
480 | RootObject* rootObject = obj->rootObject; |
481 | if (!rootObject || !rootObject->isValid()) |
482 | return false; |
483 | |
484 | auto globalObject = rootObject->globalObject(); |
485 | VM& vm = globalObject->vm(); |
486 | JSLockHolder lock(vm); |
487 | auto scope = DECLARE_CATCH_SCOPE(vm); |
488 | |
489 | ExecState* exec = globalObject->globalExec(); |
490 | PropertyNameArray propertyNames(&vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude); |
491 | |
492 | obj->imp->methodTable(vm)->getPropertyNames(obj->imp, exec, propertyNames, EnumerationMode()); |
493 | unsigned size = static_cast<unsigned>(propertyNames.size()); |
494 | // FIXME: This should really call NPN_MemAlloc but that's in WebKit |
495 | NPIdentifier* identifiers = static_cast<NPIdentifier*>(malloc(sizeof(NPIdentifier) * size)); |
496 | |
497 | for (unsigned i = 0; i < size; ++i) |
498 | identifiers[i] = _NPN_GetStringIdentifier(propertyNames[i].string().utf8().data()); |
499 | |
500 | *identifier = identifiers; |
501 | *count = size; |
502 | |
503 | scope.clearException(); |
504 | return true; |
505 | } |
506 | |
507 | if (NP_CLASS_STRUCT_VERSION_HAS_ENUM(o->_class) && o->_class->enumerate) |
508 | return o->_class->enumerate(o, identifier, count); |
509 | |
510 | return false; |
511 | } |
512 | |
513 | bool _NPN_Construct(NPP, NPObject* o, const NPVariant* args, uint32_t argCount, NPVariant* result) |
514 | { |
515 | if (o->_class == NPScriptObjectClass) { |
516 | JavaScriptObject* obj = reinterpret_cast<JavaScriptObject*>(o); |
517 | |
518 | VOID_TO_NPVARIANT(*result); |
519 | |
520 | // Lookup the constructor object. |
521 | RootObject* rootObject = obj->rootObject; |
522 | if (!rootObject || !rootObject->isValid()) |
523 | return false; |
524 | |
525 | auto globalObject = rootObject->globalObject(); |
526 | VM& vm = globalObject->vm(); |
527 | JSLockHolder lock(vm); |
528 | auto scope = DECLARE_CATCH_SCOPE(vm); |
529 | |
530 | ExecState* exec = globalObject->globalExec(); |
531 | |
532 | // Call the constructor object. |
533 | JSValue constructor = obj->imp; |
534 | ConstructData constructData; |
535 | ConstructType constructType = getConstructData(vm, constructor, constructData); |
536 | if (constructType == ConstructType::None) |
537 | return false; |
538 | |
539 | MarkedArgumentBuffer argList; |
540 | getListFromVariantArgs(exec, args, argCount, rootObject, argList); |
541 | RELEASE_ASSERT(!argList.hasOverflowed()); |
542 | JSValue resultV = JSC::construct(exec, constructor, constructType, constructData, argList); |
543 | |
544 | // Convert and return the result. |
545 | convertValueToNPVariant(exec, resultV, result); |
546 | scope.clearException(); |
547 | return true; |
548 | } |
549 | |
550 | if (NP_CLASS_STRUCT_VERSION_HAS_CTOR(o->_class) && o->_class->construct) |
551 | return o->_class->construct(o, args, argCount, result); |
552 | |
553 | return false; |
554 | } |
555 | |
556 | } // extern "C" |
557 | |
558 | } // namespace JSC |
559 | |
560 | #endif // ENABLE(NETSCAPE_PLUGIN_API) |
561 | |