Bug 1382172 - Compute names for all JS-implemented XPCOM objects (r=mrbkap)

MozReview-Commit-ID: 4kPbqOpGYnq
This commit is contained in:
Bill McCloskey 2017-08-07 16:52:42 -07:00
parent cdb7204303
commit 96944ff355
4 changed files with 146 additions and 1 deletions

View File

@ -10,10 +10,12 @@
#include "jsprf.h"
#include "nsArrayEnumerator.h"
#include "nsContentUtils.h"
#include "nsINamed.h"
#include "nsIScriptError.h"
#include "nsWrapperCache.h"
#include "AccessCheck.h"
#include "nsJSUtils.h"
#include "nsPrintfCString.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMException.h"
@ -449,6 +451,97 @@ NS_DEFINE_STATIC_IID_ACCESSOR(WrappedJSIdentity,
/***************************************************************************/
namespace {
class WrappedJSNamed final : public nsINamed
{
nsCString mName;
~WrappedJSNamed() {}
public:
NS_DECL_ISUPPORTS
WrappedJSNamed(const nsACString& aName) : mName(aName) {}
NS_IMETHOD GetName(nsACString& aName) override
{
aName = mName;
aName.AppendLiteral(":JS");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(WrappedJSNamed, nsINamed)
nsCString
GetFunctionName(JSContext* cx, HandleObject obj)
{
RootedObject inner(cx, js::UncheckedUnwrap(obj));
JSAutoCompartment ac(cx, inner);
RootedFunction fun(cx, JS_GetObjectFunction(inner));
if (!fun) {
// If the object isn't a function, it's likely that it has a single
// function property (for things like nsITimerCallback). In this case,
// return the name of that function property.
Rooted<IdVector> idArray(cx, IdVector(cx));
if (!JS_Enumerate(cx, inner, &idArray)) {
JS_ClearPendingException(cx);
return nsCString("error");
}
if (idArray.length() != 1)
return nsCString("nonfunction");
RootedId id(cx, idArray[0]);
RootedValue v(cx);
if (!JS_GetPropertyById(cx, inner, id, &v)) {
JS_ClearPendingException(cx);
return nsCString("nonfunction");
}
if (!v.isObject())
return nsCString("nonfunction");
RootedObject vobj(cx, &v.toObject());
return GetFunctionName(cx, vobj);
}
RootedString funName(cx, JS_GetFunctionDisplayId(fun));
RootedScript script(cx, JS_GetFunctionScript(cx, fun));
const char* filename = script ? JS_GetScriptFilename(script) : "anonymous";
const char* filenameSuffix = strrchr(filename, '/');
if (filenameSuffix) {
filenameSuffix++;
} else {
filenameSuffix = filename;
}
nsCString displayName("anonymous");
if (funName) {
nsCString* displayNamePtr = &displayName;
RootedValue funNameVal(cx, StringValue(funName));
if (!XPCConvert::JSData2Native(&displayNamePtr, funNameVal, nsXPTType::T_UTF8STRING,
nullptr, nullptr))
{
JS_ClearPendingException(cx);
return nsCString("anonymous");
}
}
displayName.Append('[');
displayName.Append(filenameSuffix, strlen(filenameSuffix));
displayName.Append(']');
return displayName;
}
} // anonymous namespace
/***************************************************************************/
// static
bool
nsXPCWrappedJSClass::IsWrappedJS(nsISupports* aPtr)
@ -590,6 +683,16 @@ nsXPCWrappedJSClass::DelegatedQueryInterface(nsXPCWrappedJS* self,
}
}
// If we're asked to QI to nsINamed, we pretend that this is possible. We'll
// try to return a name that makes sense for the wrapped JS value.
if (aIID.Equals(NS_GET_IID(nsINamed))) {
RootedObject obj(RootingCx(), self->GetJSObject());
nsCString name = GetFunctionName(ccx, obj);
RefPtr<WrappedJSNamed> named = new WrappedJSNamed(name);
*aInstancePtr = named.forget().take();
return NS_OK;
}
// else...
// no can do
*aInstancePtr = nullptr;

View File

@ -0,0 +1,41 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var Cu = Components.utils;
var Cc = Components.classes;
var Ci = Components.interfaces;
function callback() {}
let sandbox = Cu.Sandbox(this);
let callbackWrapped = Cu.evalInSandbox("(function wrapped() {})", sandbox);
function run_test() {
let functions = [
[{ notify: callback }, "callback[test_function_names.js]:JS"],
[{ notify: { notify: callback } }, "callback[test_function_names.js]:JS"],
[callback, "callback[test_function_names.js]:JS"],
[function() {}, "run_test/functions<[test_function_names.js]:JS"],
[function foobar() {}, "foobar[test_function_names.js]:JS"],
[function Δ() {}, "Δ[test_function_names.js]:JS"],
[{ notify1: callback, notify2: callback }, "nonfunction:JS"],
[{ notify: 10 }, "nonfunction:JS"],
[{}, "nonfunction:JS"],
[{ notify: callbackWrapped }, "wrapped[test_function_names.js]:JS"],
];
// Use the observer service so we can get double-wrapped functions.
var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
function observer(subject, topic, data)
{
let named = subject.QueryInterface(Ci.nsINamed);
do_check_eq(named.name, data);
dump(`name: ${named.name}\n`);
}
obs.addObserver(observer, "test-obs-fun", false);
for (let [f, requiredName] of functions) {
obs.notifyObservers(f, "test-obs-fun", requiredName);
}
}

View File

@ -136,3 +136,4 @@ head = head_watchdog.js
[test_xray_regexp.js]
[test_resolve_dead_promise.js]
[test_asyncLoadSubScriptError.js]
[test_function_names.js]

View File

@ -20,5 +20,5 @@ interface nsINamed : nsISupports
* WARNING: This attribute will be included in telemetry, so it should
* never contain privacy sensitive information.
*/
readonly attribute ACString name;
readonly attribute AUTF8String name;
};