summaryrefslogtreecommitdiffstats
path: root/dom/xbl/nsXBLProtoImplMethod.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xbl/nsXBLProtoImplMethod.cpp')
-rw-r--r--dom/xbl/nsXBLProtoImplMethod.cpp356
1 files changed, 356 insertions, 0 deletions
diff --git a/dom/xbl/nsXBLProtoImplMethod.cpp b/dom/xbl/nsXBLProtoImplMethod.cpp
new file mode 100644
index 000000000..31b215ab3
--- /dev/null
+++ b/dom/xbl/nsXBLProtoImplMethod.cpp
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "jsapi.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIGlobalObject.h"
+#include "nsUnicharUtils.h"
+#include "nsReadableUtils.h"
+#include "nsXBLProtoImplMethod.h"
+#include "nsJSUtils.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIXPConnect.h"
+#include "xpcpublic.h"
+#include "nsXBLPrototypeBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsXBLProtoImplMethod::nsXBLProtoImplMethod(const char16_t* aName) :
+ nsXBLProtoImplMember(aName),
+ mMethod()
+{
+ MOZ_COUNT_CTOR(nsXBLProtoImplMethod);
+}
+
+nsXBLProtoImplMethod::~nsXBLProtoImplMethod()
+{
+ MOZ_COUNT_DTOR(nsXBLProtoImplMethod);
+
+ if (!IsCompiled()) {
+ delete GetUncompiledMethod();
+ }
+}
+
+void
+nsXBLProtoImplMethod::AppendBodyText(const nsAString& aText)
+{
+ NS_PRECONDITION(!IsCompiled(),
+ "Must not be compiled when accessing uncompiled method");
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+ if (!uncompiledMethod) {
+ uncompiledMethod = new nsXBLUncompiledMethod();
+ SetUncompiledMethod(uncompiledMethod);
+ }
+
+ uncompiledMethod->AppendBodyText(aText);
+}
+
+void
+nsXBLProtoImplMethod::AddParameter(const nsAString& aText)
+{
+ NS_PRECONDITION(!IsCompiled(),
+ "Must not be compiled when accessing uncompiled method");
+
+ if (aText.IsEmpty()) {
+ NS_WARNING("Empty name attribute in xbl:parameter!");
+ return;
+ }
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+ if (!uncompiledMethod) {
+ uncompiledMethod = new nsXBLUncompiledMethod();
+ SetUncompiledMethod(uncompiledMethod);
+ }
+
+ uncompiledMethod->AddParameter(aText);
+}
+
+void
+nsXBLProtoImplMethod::SetLineNumber(uint32_t aLineNumber)
+{
+ NS_PRECONDITION(!IsCompiled(),
+ "Must not be compiled when accessing uncompiled method");
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+ if (!uncompiledMethod) {
+ uncompiledMethod = new nsXBLUncompiledMethod();
+ SetUncompiledMethod(uncompiledMethod);
+ }
+
+ uncompiledMethod->SetLineNumber(aLineNumber);
+}
+
+nsresult
+nsXBLProtoImplMethod::InstallMember(JSContext* aCx,
+ JS::Handle<JSObject*> aTargetClassObject)
+{
+ NS_PRECONDITION(IsCompiled(),
+ "Should not be installing an uncompiled method");
+ MOZ_ASSERT(js::IsObjectInContextCompartment(aTargetClassObject, aCx));
+
+#ifdef DEBUG
+ {
+ JS::Rooted<JSObject*> globalObject(aCx, JS_GetGlobalForObject(aCx, aTargetClassObject));
+ MOZ_ASSERT(xpc::IsInContentXBLScope(globalObject) ||
+ xpc::IsInAddonScope(globalObject) ||
+ globalObject == xpc::GetXBLScope(aCx, globalObject));
+ MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx) == globalObject);
+ }
+#endif
+
+ JS::Rooted<JSObject*> jsMethodObject(aCx, GetCompiledMethod());
+ if (jsMethodObject) {
+ nsDependentString name(mName);
+
+ JS::Rooted<JSObject*> method(aCx, JS::CloneFunctionObject(aCx, jsMethodObject));
+ NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!::JS_DefineUCProperty(aCx, aTargetClassObject,
+ static_cast<const char16_t*>(mName),
+ name.Length(), method,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplMethod::CompileMember(AutoJSAPI& jsapi, const nsString& aClassStr,
+ JS::Handle<JSObject*> aClassObject)
+{
+ AssertInCompilationScope();
+ NS_PRECONDITION(!IsCompiled(),
+ "Trying to compile an already-compiled method");
+ NS_PRECONDITION(aClassObject,
+ "Must have class object to compile");
+
+ nsXBLUncompiledMethod* uncompiledMethod = GetUncompiledMethod();
+
+ // No parameters or body was supplied, so don't install method.
+ if (!uncompiledMethod) {
+ // Early return after which we consider ourselves compiled.
+ SetCompiledMethod(nullptr);
+
+ return NS_OK;
+ }
+
+ // Don't install method if no name was supplied.
+ if (!mName) {
+ delete uncompiledMethod;
+
+ // Early return after which we consider ourselves compiled.
+ SetCompiledMethod(nullptr);
+
+ return NS_OK;
+ }
+
+ // We have a method.
+ // Allocate an array for our arguments.
+ int32_t paramCount = uncompiledMethod->GetParameterCount();
+ char** args = nullptr;
+ if (paramCount > 0) {
+ args = new char*[paramCount];
+
+ // Add our parameters to our args array.
+ int32_t argPos = 0;
+ for (nsXBLParameter* curr = uncompiledMethod->mParameters;
+ curr;
+ curr = curr->mNext) {
+ args[argPos] = curr->mName;
+ argPos++;
+ }
+ }
+
+ // Get the body
+ nsDependentString body;
+ char16_t *bodyText = uncompiledMethod->mBodyText.GetText();
+ if (bodyText)
+ body.Rebind(bodyText);
+
+ // Now that we have a body and args, compile the function
+ // and then define it.
+ NS_ConvertUTF16toUTF8 cname(mName);
+ NS_ConvertUTF16toUTF8 functionUri(aClassStr);
+ int32_t hash = functionUri.RFindChar('#');
+ if (hash != kNotFound) {
+ functionUri.Truncate(hash);
+ }
+
+ JSContext *cx = jsapi.cx();
+ JSAutoCompartment ac(cx, aClassObject);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine(functionUri.get(),
+ uncompiledMethod->mBodyText.GetLineNumber())
+ .setVersion(JSVERSION_LATEST);
+ JS::Rooted<JSObject*> methodObject(cx);
+ JS::AutoObjectVector emptyVector(cx);
+ nsresult rv = nsJSUtils::CompileFunction(jsapi, emptyVector, options, cname,
+ paramCount,
+ const_cast<const char**>(args),
+ body, methodObject.address());
+
+ // Destroy our uncompiled method and delete our arg list.
+ delete uncompiledMethod;
+ delete [] args;
+ if (NS_FAILED(rv)) {
+ SetUncompiledMethod(nullptr);
+ return rv;
+ }
+
+ SetCompiledMethod(methodObject);
+
+ return NS_OK;
+}
+
+void
+nsXBLProtoImplMethod::Trace(const TraceCallbacks& aCallbacks, void *aClosure)
+{
+ if (IsCompiled() && GetCompiledMethodPreserveColor()) {
+ aCallbacks.Trace(&mMethod.AsHeapObject(), "mMethod", aClosure);
+ }
+}
+
+nsresult
+nsXBLProtoImplMethod::Read(nsIObjectInputStream* aStream)
+{
+ AssertInCompilationScope();
+ MOZ_ASSERT(!IsCompiled() && !GetUncompiledMethod());
+
+ AutoJSContext cx;
+ JS::Rooted<JSObject*> methodObject(cx);
+ nsresult rv = XBL_DeserializeFunction(aStream, &methodObject);
+ if (NS_FAILED(rv)) {
+ SetUncompiledMethod(nullptr);
+ return rv;
+ }
+
+ SetCompiledMethod(methodObject);
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplMethod::Write(nsIObjectOutputStream* aStream)
+{
+ AssertInCompilationScope();
+ MOZ_ASSERT(IsCompiled());
+ if (GetCompiledMethodPreserveColor()) {
+ nsresult rv = aStream->Write8(XBLBinding_Serialize_Method);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> method(RootingCx(), GetCompiledMethod());
+ return XBL_SerializeFunction(aStream, method);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplAnonymousMethod::Execute(nsIContent* aBoundElement, JSAddonId* aAddonId)
+{
+ MOZ_ASSERT(aBoundElement->IsElement());
+ NS_PRECONDITION(IsCompiled(), "Can't execute uncompiled method");
+
+ if (!GetCompiledMethod()) {
+ // Nothing to do here
+ return NS_OK;
+ }
+
+ // Get the script context the same way
+ // nsXBLProtoImpl::InstallImplementation does.
+ nsIDocument* document = aBoundElement->OwnerDoc();
+
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(document->GetInnerWindow());
+ if (!global) {
+ return NS_OK;
+ }
+
+ nsAutoMicroTask mt;
+
+ // We are going to run script via JS::Call, so we need a script entry point,
+ // but as this is XBL related it does not appear in the HTML spec.
+ // We need an actual JSContext to do GetScopeForXBLExecution, and it needs to
+ // be in the compartment of globalObject. But we want our XBL execution scope
+ // to be our entry global.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(global)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ JS::Rooted<JSObject*> globalObject(jsapi.cx(), global->GetGlobalJSObject());
+
+ JS::Rooted<JSObject*> scopeObject(jsapi.cx(),
+ xpc::GetScopeForXBLExecution(jsapi.cx(), globalObject, aAddonId));
+ NS_ENSURE_TRUE(scopeObject, NS_ERROR_OUT_OF_MEMORY);
+
+ dom::AutoEntryScript aes(scopeObject,
+ "XBL <constructor>/<destructor> invocation",
+ true);
+ JSContext* cx = aes.cx();
+ JS::AutoObjectVector scopeChain(cx);
+ if (!nsJSUtils::GetScopeChainForElement(cx, aBoundElement->AsElement(),
+ scopeChain)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ MOZ_ASSERT(scopeChain.length() != 0);
+
+ // Clone the function object, using our scope chain (for backwards
+ // compat to the days when this was an event handler).
+ JS::Rooted<JSObject*> jsMethodObject(cx, GetCompiledMethod());
+ JS::Rooted<JSObject*> method(cx, JS::CloneFunctionObject(cx, jsMethodObject,
+ scopeChain));
+ if (!method)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Now call the method
+
+ // Check whether script is enabled.
+ bool scriptAllowed = xpc::Scriptability::Get(method).Allowed();
+
+ if (scriptAllowed) {
+ JS::Rooted<JS::Value> retval(cx);
+ JS::Rooted<JS::Value> methodVal(cx, JS::ObjectValue(*method));
+ // No need to check the return here as AutoEntryScript has taken ownership
+ // of error reporting.
+ ::JS::Call(cx, scopeChain[0], methodVal, JS::HandleValueArray::empty(), &retval);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsXBLProtoImplAnonymousMethod::Write(nsIObjectOutputStream* aStream,
+ XBLBindingSerializeDetails aType)
+{
+ AssertInCompilationScope();
+ MOZ_ASSERT(IsCompiled());
+ if (GetCompiledMethodPreserveColor()) {
+ nsresult rv = aStream->Write8(aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteWStringZ(mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> method(RootingCx(), GetCompiledMethod());
+ rv = XBL_SerializeFunction(aStream, method);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}