| // Copyright 2016 PDFium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com | 
 |  | 
 | #include "fxjs/xfa/cfxjse_context.h" | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "fxjs/cfxjs_engine.h" | 
 | #include "fxjs/xfa/cfxjse_class.h" | 
 | #include "fxjs/xfa/cfxjse_isolatetracker.h" | 
 | #include "fxjs/xfa/cfxjse_runtimedata.h" | 
 | #include "fxjs/xfa/cfxjse_value.h" | 
 | #include "third_party/base/ptr_util.h" | 
 |  | 
 | namespace { | 
 |  | 
 | const char szCompatibleModeScript[] = | 
 |     "(function(global, list) {\n" | 
 |     "  'use strict';\n" | 
 |     "  var objname;\n" | 
 |     "  for (objname in list) {\n" | 
 |     "    var globalobj = global[objname];\n" | 
 |     "    if (globalobj) {\n" | 
 |     "      list[objname].forEach(function(name) {\n" | 
 |     "        if (!globalobj[name]) {\n" | 
 |     "          Object.defineProperty(globalobj, name, {\n" | 
 |     "            writable: true,\n" | 
 |     "            enumerable: false,\n" | 
 |     "            value: (function(obj) {\n" | 
 |     "              if (arguments.length === 0) {\n" | 
 |     "                throw new TypeError('missing argument 0 when calling " | 
 |     "                    function ' + objname + '.' + name);\n" | 
 |     "              }\n" | 
 |     "              return globalobj.prototype[name].apply(obj, " | 
 |     "                  Array.prototype.slice.call(arguments, 1));\n" | 
 |     "            })\n" | 
 |     "          });\n" | 
 |     "        }\n" | 
 |     "      });\n" | 
 |     "    }\n" | 
 |     "  }\n" | 
 |     "}(this, {String: ['substr', 'toUpperCase']}));"; | 
 |  | 
 | const char szConsoleScript[] = | 
 |     "console.show = function() {};\n" | 
 |     "\n" | 
 |     "console.println = function(...args) {\n" | 
 |     "  this.log(...args);\n" | 
 |     "};"; | 
 |  | 
 | // Only address matters, values are for humans debuging here. | 
 | char g_FXJSEHostObjectTag[] = "FXJSE Host Object"; | 
 | char g_FXJSEProxyObjectTag[] = "FXJSE Proxy Object"; | 
 |  | 
 | v8::Local<v8::Object> CreateReturnValue(v8::Isolate* pIsolate, | 
 |                                         v8::TryCatch* trycatch) { | 
 |   v8::Local<v8::Object> hReturnValue = v8::Object::New(pIsolate); | 
 |   if (!trycatch->HasCaught()) | 
 |     return hReturnValue; | 
 |  | 
 |   v8::Local<v8::Message> hMessage = trycatch->Message(); | 
 |   if (hMessage.IsEmpty()) | 
 |     return hReturnValue; | 
 |  | 
 |   v8::Local<v8::Context> context = pIsolate->GetCurrentContext(); | 
 |   v8::Local<v8::Value> hException = trycatch->Exception(); | 
 |   if (hException->IsObject()) { | 
 |     v8::Local<v8::String> hNameStr = | 
 |         v8::String::NewFromUtf8(pIsolate, "name", v8::NewStringType::kNormal) | 
 |             .ToLocalChecked(); | 
 |     v8::Local<v8::Value> hValue = | 
 |         hException.As<v8::Object>()->Get(context, hNameStr).ToLocalChecked(); | 
 |     if (hValue->IsString() || hValue->IsStringObject()) { | 
 |       hReturnValue->Set(context, 0, hValue).FromJust(); | 
 |     } else { | 
 |       v8::Local<v8::String> hErrorStr = | 
 |           v8::String::NewFromUtf8(pIsolate, "Error", v8::NewStringType::kNormal) | 
 |               .ToLocalChecked(); | 
 |       hReturnValue->Set(context, 0, hErrorStr).FromJust(); | 
 |     } | 
 |  | 
 |     v8::Local<v8::String> hMessageStr = | 
 |         v8::String::NewFromUtf8(pIsolate, "message", v8::NewStringType::kNormal) | 
 |             .ToLocalChecked(); | 
 |     hValue = | 
 |         hException.As<v8::Object>()->Get(context, hMessageStr).ToLocalChecked(); | 
 |     if (hValue->IsString() || hValue->IsStringObject()) | 
 |       hReturnValue->Set(context, 1, hValue).FromJust(); | 
 |     else | 
 |       hReturnValue->Set(context, 1, hMessage->Get()).FromJust(); | 
 |   } else { | 
 |     v8::Local<v8::String> hErrorStr = | 
 |         v8::String::NewFromUtf8(pIsolate, "Error", v8::NewStringType::kNormal) | 
 |             .ToLocalChecked(); | 
 |     hReturnValue->Set(context, 0, hErrorStr).FromJust(); | 
 |     hReturnValue->Set(context, 1, hMessage->Get()).FromJust(); | 
 |   } | 
 |   hReturnValue->Set(context, 2, hException).FromJust(); | 
 |   int line = hMessage->GetLineNumber(context).FromMaybe(0); | 
 |   hReturnValue->Set(context, 3, v8::Integer::New(pIsolate, line)).FromJust(); | 
 |   v8::Local<v8::String> source = | 
 |       hMessage->GetSourceLine(context).FromMaybe(v8::Local<v8::String>()); | 
 |   hReturnValue->Set(context, 4, source).FromJust(); | 
 |   int column = hMessage->GetStartColumn(context).FromMaybe(0); | 
 |   hReturnValue->Set(context, 5, v8::Integer::New(pIsolate, column)).FromJust(); | 
 |   column = hMessage->GetEndColumn(context).FromMaybe(0); | 
 |   hReturnValue->Set(context, 6, v8::Integer::New(pIsolate, column)).FromJust(); | 
 |   return hReturnValue; | 
 | } | 
 |  | 
 | class CFXJSE_ScopeUtil_IsolateHandleContext { | 
 |  public: | 
 |   explicit CFXJSE_ScopeUtil_IsolateHandleContext(CFXJSE_Context* pContext) | 
 |       : m_parent(pContext->GetIsolate()), m_cscope(pContext->GetContext()) {} | 
 |  | 
 |  private: | 
 |   CFXJSE_ScopeUtil_IsolateHandleContext( | 
 |       const CFXJSE_ScopeUtil_IsolateHandleContext&) = delete; | 
 |   void operator=(const CFXJSE_ScopeUtil_IsolateHandleContext&) = delete; | 
 |   void* operator new(size_t size) = delete; | 
 |   void operator delete(void*, size_t) = delete; | 
 |  | 
 |   CFXJSE_ScopeUtil_IsolateHandle m_parent; | 
 |   v8::Context::Scope m_cscope; | 
 | }; | 
 |  | 
 | void FXJSE_UpdateProxyBinding(v8::Local<v8::Object> hObject) { | 
 |   ASSERT(!hObject.IsEmpty()); | 
 |   ASSERT(hObject->InternalFieldCount() == 2); | 
 |   hObject->SetAlignedPointerInInternalField(0, g_FXJSEProxyObjectTag); | 
 |   hObject->SetAlignedPointerInInternalField(1, nullptr); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | void FXJSE_UpdateObjectBinding(v8::Local<v8::Object> hObject, | 
 |                                CFXJSE_HostObject* lpNewBinding) { | 
 |   ASSERT(!hObject.IsEmpty()); | 
 |   ASSERT(hObject->InternalFieldCount() == 2); | 
 |   hObject->SetAlignedPointerInInternalField(0, g_FXJSEHostObjectTag); | 
 |   hObject->SetAlignedPointerInInternalField(1, lpNewBinding); | 
 | } | 
 |  | 
 | void FXJSE_ClearObjectBinding(v8::Local<v8::Object> hObject) { | 
 |   ASSERT(!hObject.IsEmpty()); | 
 |   ASSERT(hObject->InternalFieldCount() == 2); | 
 |   hObject->SetAlignedPointerInInternalField(0, nullptr); | 
 |   hObject->SetAlignedPointerInInternalField(1, nullptr); | 
 | } | 
 |  | 
 | CFXJSE_HostObject* FXJSE_RetrieveObjectBinding( | 
 |     v8::Local<v8::Object> hJSObject) { | 
 |   ASSERT(!hJSObject.IsEmpty()); | 
 |   if (!hJSObject->IsObject()) | 
 |     return nullptr; | 
 |  | 
 |   v8::Local<v8::Object> hObject = hJSObject; | 
 |   if (hObject->InternalFieldCount() != 2 || | 
 |       hObject->GetAlignedPointerFromInternalField(0) == g_FXJSEProxyObjectTag) { | 
 |     v8::Local<v8::Value> hProtoObject = hObject->GetPrototype(); | 
 |     if (hProtoObject.IsEmpty() || !hProtoObject->IsObject()) | 
 |       return nullptr; | 
 |  | 
 |     hObject = hProtoObject.As<v8::Object>(); | 
 |     if (hObject->InternalFieldCount() != 2) | 
 |       return nullptr; | 
 |   } | 
 |   if (hObject->GetAlignedPointerFromInternalField(0) != g_FXJSEHostObjectTag) | 
 |     return nullptr; | 
 |  | 
 |   return static_cast<CFXJSE_HostObject*>( | 
 |       hObject->GetAlignedPointerFromInternalField(1)); | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<CFXJSE_Context> CFXJSE_Context::Create( | 
 |     v8::Isolate* pIsolate, | 
 |     const FXJSE_CLASS_DESCRIPTOR* pGlobalClass, | 
 |     CFXJSE_HostObject* pGlobalObject) { | 
 |   CFXJSE_ScopeUtil_IsolateHandle scope(pIsolate); | 
 |   auto pContext = pdfium::MakeUnique<CFXJSE_Context>(pIsolate); | 
 |  | 
 |   v8::Local<v8::ObjectTemplate> hObjectTemplate; | 
 |   if (pGlobalClass) { | 
 |     CFXJSE_Class* pGlobalClassObj = | 
 |         CFXJSE_Class::Create(pContext.get(), pGlobalClass, true); | 
 |     ASSERT(pGlobalClassObj); | 
 |     v8::Local<v8::FunctionTemplate> hFunctionTemplate = | 
 |         v8::Local<v8::FunctionTemplate>::New(pIsolate, | 
 |                                              pGlobalClassObj->m_hTemplate); | 
 |     hObjectTemplate = hFunctionTemplate->InstanceTemplate(); | 
 |   } else { | 
 |     hObjectTemplate = v8::ObjectTemplate::New(pIsolate); | 
 |     hObjectTemplate->SetInternalFieldCount(2); | 
 |   } | 
 |   hObjectTemplate->Set( | 
 |       v8::Symbol::GetToStringTag(pIsolate), | 
 |       v8::String::NewFromUtf8(pIsolate, "global", v8::NewStringType::kNormal) | 
 |           .ToLocalChecked()); | 
 |  | 
 |   v8::Local<v8::Context> hNewContext = | 
 |       v8::Context::New(pIsolate, nullptr, hObjectTemplate); | 
 |  | 
 |   v8::Local<v8::Object> pThisProxy = hNewContext->Global(); | 
 |   FXJSE_UpdateProxyBinding(pThisProxy); | 
 |  | 
 |   v8::Local<v8::Object> pThis = pThisProxy->GetPrototype().As<v8::Object>(); | 
 |   FXJSE_UpdateObjectBinding(pThis, pGlobalObject); | 
 |  | 
 |   v8::Local<v8::Context> hRootContext = v8::Local<v8::Context>::New( | 
 |       pIsolate, CFXJSE_RuntimeData::Get(pIsolate)->m_hRootContext); | 
 |   hNewContext->SetSecurityToken(hRootContext->GetSecurityToken()); | 
 |   pContext->m_hContext.Reset(pIsolate, hNewContext); | 
 |   return pContext; | 
 | } | 
 |  | 
 | CFXJSE_Context::CFXJSE_Context(v8::Isolate* pIsolate) : m_pIsolate(pIsolate) {} | 
 |  | 
 | CFXJSE_Context::~CFXJSE_Context() {} | 
 |  | 
 | std::unique_ptr<CFXJSE_Value> CFXJSE_Context::GetGlobalObject() { | 
 |   auto pValue = pdfium::MakeUnique<CFXJSE_Value>(GetIsolate()); | 
 |   CFXJSE_ScopeUtil_IsolateHandleContext scope(this); | 
 |   v8::Local<v8::Context> hContext = | 
 |       v8::Local<v8::Context>::New(GetIsolate(), m_hContext); | 
 |   v8::Local<v8::Object> hGlobalObject = | 
 |       hContext->Global()->GetPrototype().As<v8::Object>(); | 
 |   pValue->ForceSetValue(hGlobalObject); | 
 |   return pValue; | 
 | } | 
 |  | 
 | v8::Local<v8::Context> CFXJSE_Context::GetContext() { | 
 |   return v8::Local<v8::Context>::New(GetIsolate(), m_hContext); | 
 | } | 
 |  | 
 | void CFXJSE_Context::AddClass(std::unique_ptr<CFXJSE_Class> pClass) { | 
 |   m_rgClasses.push_back(std::move(pClass)); | 
 | } | 
 |  | 
 | CFXJSE_Class* CFXJSE_Context::GetClassByName(ByteStringView szName) const { | 
 |   auto pClass = | 
 |       std::find_if(m_rgClasses.begin(), m_rgClasses.end(), | 
 |                    [szName](const std::unique_ptr<CFXJSE_Class>& item) { | 
 |                      return szName == item->m_szClassName; | 
 |                    }); | 
 |   return pClass != m_rgClasses.end() ? pClass->get() : nullptr; | 
 | } | 
 |  | 
 | void CFXJSE_Context::EnableCompatibleMode() { | 
 |   ExecuteScript(szCompatibleModeScript, nullptr, nullptr); | 
 |   ExecuteScript(szConsoleScript, nullptr, nullptr); | 
 | } | 
 |  | 
 | bool CFXJSE_Context::ExecuteScript(const char* szScript, | 
 |                                    CFXJSE_Value* lpRetValue, | 
 |                                    CFXJSE_Value* lpNewThisObject) { | 
 |   CFXJSE_ScopeUtil_IsolateHandleContext scope(this); | 
 |   v8::Local<v8::Context> hContext = GetIsolate()->GetCurrentContext(); | 
 |   v8::TryCatch trycatch(GetIsolate()); | 
 |   v8::Local<v8::String> hScriptString = | 
 |       v8::String::NewFromUtf8(GetIsolate(), szScript, | 
 |                               v8::NewStringType::kNormal) | 
 |           .ToLocalChecked(); | 
 |   if (!lpNewThisObject) { | 
 |     v8::Local<v8::Script> hScript; | 
 |     if (v8::Script::Compile(hContext, hScriptString).ToLocal(&hScript)) { | 
 |       ASSERT(!trycatch.HasCaught()); | 
 |       v8::Local<v8::Value> hValue; | 
 |       if (hScript->Run(hContext).ToLocal(&hValue)) { | 
 |         ASSERT(!trycatch.HasCaught()); | 
 |         if (lpRetValue) | 
 |           lpRetValue->ForceSetValue(hValue); | 
 |         return true; | 
 |       } | 
 |     } | 
 |     if (lpRetValue) | 
 |       lpRetValue->ForceSetValue(CreateReturnValue(GetIsolate(), &trycatch)); | 
 |     return false; | 
 |   } | 
 |  | 
 |   v8::Local<v8::Value> hNewThis = v8::Local<v8::Value>::New( | 
 |       GetIsolate(), lpNewThisObject->DirectGetValue()); | 
 |   ASSERT(!hNewThis.IsEmpty()); | 
 |   v8::Local<v8::String> hEval = | 
 |       v8::String::NewFromUtf8(GetIsolate(), | 
 |                               "(function () { return eval(arguments[0]); })", | 
 |                               v8::NewStringType::kNormal) | 
 |           .ToLocalChecked(); | 
 |   v8::Local<v8::Script> hWrapper = | 
 |       v8::Script::Compile(hContext, hEval).ToLocalChecked(); | 
 |   v8::Local<v8::Value> hWrapperValue; | 
 |   if (hWrapper->Run(hContext).ToLocal(&hWrapperValue)) { | 
 |     ASSERT(!trycatch.HasCaught()); | 
 |     v8::Local<v8::Function> hWrapperFn = hWrapperValue.As<v8::Function>(); | 
 |     v8::Local<v8::Value> rgArgs[] = {hScriptString}; | 
 |     v8::Local<v8::Value> hValue; | 
 |     if (hWrapperFn->Call(hContext, hNewThis.As<v8::Object>(), 1, rgArgs) | 
 |             .ToLocal(&hValue)) { | 
 |       ASSERT(!trycatch.HasCaught()); | 
 |       if (lpRetValue) | 
 |         lpRetValue->ForceSetValue(hValue); | 
 |       return true; | 
 |     } | 
 |   } | 
 |  | 
 | #ifndef NDEBUG | 
 |   v8::String::Utf8Value error(GetIsolate(), trycatch.Exception()); | 
 |   fprintf(stderr, "JS Error: %s\n", *error); | 
 |  | 
 |   v8::Local<v8::Message> message = trycatch.Message(); | 
 |   if (!message.IsEmpty()) { | 
 |     v8::Local<v8::Context> context(GetIsolate()->GetCurrentContext()); | 
 |     int linenum = message->GetLineNumber(context).FromJust(); | 
 |     v8::String::Utf8Value sourceline( | 
 |         GetIsolate(), message->GetSourceLine(context).ToLocalChecked()); | 
 |     fprintf(stderr, "Line %d: %s\n", linenum, *sourceline); | 
 |   } | 
 | #endif  // NDEBUG | 
 |  | 
 |   if (lpRetValue) | 
 |     lpRetValue->ForceSetValue(CreateReturnValue(GetIsolate(), &trycatch)); | 
 |   return false; | 
 | } |