diff options
Diffstat (limited to 'dom/bindings/WebIDLGlobalNameHash.cpp')
-rw-r--r-- | dom/bindings/WebIDLGlobalNameHash.cpp | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/dom/bindings/WebIDLGlobalNameHash.cpp b/dom/bindings/WebIDLGlobalNameHash.cpp new file mode 100644 index 000000000..7477b52e7 --- /dev/null +++ b/dom/bindings/WebIDLGlobalNameHash.cpp @@ -0,0 +1,324 @@ +/* -*- 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 "WebIDLGlobalNameHash.h" +#include "js/GCAPI.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/DOMJSProxyHandler.h" +#include "mozilla/dom/RegisterBindings.h" +#include "nsIMemoryReporter.h" +#include "nsTHashtable.h" + +namespace mozilla { +namespace dom { + +struct MOZ_STACK_CLASS WebIDLNameTableKey +{ + explicit WebIDLNameTableKey(JSFlatString* aJSString) + : mLength(js::GetFlatStringLength(aJSString)) + { + mNogc.emplace(); + JSLinearString* jsString = js::FlatStringToLinearString(aJSString); + if (js::LinearStringHasLatin1Chars(jsString)) { + mLatin1String = reinterpret_cast<const char*>( + js::GetLatin1LinearStringChars(*mNogc, jsString)); + mTwoBytesString = nullptr; + mHash = mLatin1String ? HashString(mLatin1String, mLength) : 0; + } else { + mLatin1String = nullptr; + mTwoBytesString = js::GetTwoByteLinearStringChars(*mNogc, jsString); + mHash = mTwoBytesString ? HashString(mTwoBytesString, mLength) : 0; + } + } + explicit WebIDLNameTableKey(const char* aString, size_t aLength) + : mLatin1String(aString), + mTwoBytesString(nullptr), + mLength(aLength), + mHash(HashString(aString, aLength)) + { + MOZ_ASSERT(aString[aLength] == '\0'); + } + + Maybe<JS::AutoCheckCannotGC> mNogc; + const char* mLatin1String; + const char16_t* mTwoBytesString; + size_t mLength; + uint32_t mHash; +}; + +struct WebIDLNameTableEntry : public PLDHashEntryHdr +{ + typedef const WebIDLNameTableKey& KeyType; + typedef const WebIDLNameTableKey* KeyTypePointer; + + explicit WebIDLNameTableEntry(KeyTypePointer aKey) + : mNameOffset(0), + mNameLength(0), + mDefine(nullptr), + mEnabled(nullptr) + {} + WebIDLNameTableEntry(WebIDLNameTableEntry&& aEntry) + : mNameOffset(aEntry.mNameOffset), + mNameLength(aEntry.mNameLength), + mDefine(aEntry.mDefine), + mEnabled(aEntry.mEnabled) + {} + ~WebIDLNameTableEntry() + {} + + bool KeyEquals(KeyTypePointer aKey) const + { + if (mNameLength != aKey->mLength) { + return false; + } + + const char* name = WebIDLGlobalNameHash::sNames + mNameOffset; + + if (aKey->mLatin1String) { + return PodEqual(aKey->mLatin1String, name, aKey->mLength); + } + + return nsCharTraits<char16_t>::compareASCII(aKey->mTwoBytesString, name, + aKey->mLength) == 0; + } + + static KeyTypePointer KeyToPointer(KeyType aKey) + { + return &aKey; + } + + static PLDHashNumber HashKey(KeyTypePointer aKey) + { + return aKey->mHash; + } + + enum { ALLOW_MEMMOVE = true }; + + uint16_t mNameOffset; + uint16_t mNameLength; + WebIDLGlobalNameHash::DefineGlobalName mDefine; + // May be null if enabled unconditionally + WebIDLGlobalNameHash::ConstructorEnabled* mEnabled; +}; + +static nsTHashtable<WebIDLNameTableEntry>* sWebIDLGlobalNames; + +class WebIDLGlobalNamesHashReporter final : public nsIMemoryReporter +{ + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + ~WebIDLGlobalNamesHashReporter() {} + +public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + int64_t amount = + sWebIDLGlobalNames ? + sWebIDLGlobalNames->ShallowSizeOfIncludingThis(MallocSizeOf) : 0; + + MOZ_COLLECT_REPORT( + "explicit/dom/webidl-globalnames", KIND_HEAP, UNITS_BYTES, amount, + "Memory used by the hash table for WebIDL's global names."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(WebIDLGlobalNamesHashReporter, nsIMemoryReporter) + +/* static */ +void +WebIDLGlobalNameHash::Init() +{ + sWebIDLGlobalNames = new nsTHashtable<WebIDLNameTableEntry>(sCount); + RegisterWebIDLGlobalNames(); + + RegisterStrongMemoryReporter(new WebIDLGlobalNamesHashReporter()); +} + +/* static */ +void +WebIDLGlobalNameHash::Shutdown() +{ + delete sWebIDLGlobalNames; +} + +/* static */ +void +WebIDLGlobalNameHash::Register(uint16_t aNameOffset, uint16_t aNameLength, + DefineGlobalName aDefine, + ConstructorEnabled* aEnabled) +{ + const char* name = sNames + aNameOffset; + WebIDLNameTableKey key(name, aNameLength); + WebIDLNameTableEntry* entry = sWebIDLGlobalNames->PutEntry(key); + entry->mNameOffset = aNameOffset; + entry->mNameLength = aNameLength; + entry->mDefine = aDefine; + entry->mEnabled = aEnabled; +} + +/* static */ +void +WebIDLGlobalNameHash::Remove(const char* aName, uint32_t aLength) +{ + WebIDLNameTableKey key(aName, aLength); + sWebIDLGlobalNames->RemoveEntry(key); +} + +/* static */ +bool +WebIDLGlobalNameHash::DefineIfEnabled(JSContext* aCx, + JS::Handle<JSObject*> aObj, + JS::Handle<jsid> aId, + JS::MutableHandle<JS::PropertyDescriptor> aDesc, + bool* aFound) +{ + MOZ_ASSERT(JSID_IS_STRING(aId), "Check for string id before calling this!"); + + const WebIDLNameTableEntry* entry; + { + WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId)); + // Rooting analysis thinks nsTHashtable<...>::GetEntry may GC because it + // ends up calling through PLDHashTableOps' matchEntry function pointer, but + // we know WebIDLNameTableEntry::KeyEquals can't cause a GC. + JS::AutoSuppressGCAnalysis suppress; + entry = sWebIDLGlobalNames->GetEntry(key); + } + + if (!entry) { + *aFound = false; + return true; + } + + *aFound = true; + + ConstructorEnabled* checkEnabledForScope = entry->mEnabled; + // We do the enabled check on the current compartment of aCx, but for the + // actual object we pass in the underlying object in the Xray case. That + // way the callee can decide whether to allow access based on the caller + // or the window being touched. + JS::Rooted<JSObject*> global(aCx, + js::CheckedUnwrap(aObj, /* stopAtWindowProxy = */ false)); + if (!global) { + return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR); + } + + { + // It's safe to pass "&global" here, because we've already unwrapped it, but + // for general sanity better to not have debug code even having the + // appearance of mutating things that opt code uses. +#ifdef DEBUG + JS::Rooted<JSObject*> temp(aCx, global); + DebugOnly<nsGlobalWindow*> win; + MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, &temp, win))); +#endif + } + + if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) { + return true; + } + + // The DOM constructor resolve machinery interacts with Xrays in tricky + // ways, and there are some asymmetries that are important to understand. + // + // In the regular (non-Xray) case, we only want to resolve constructors + // once (so that if they're deleted, they don't reappear). We do this by + // stashing the constructor in a slot on the global, such that we can see + // during resolve whether we've created it already. This is rather + // memory-intensive, so we don't try to maintain these semantics when + // manipulating a global over Xray (so the properties just re-resolve if + // they've been deleted). + // + // Unfortunately, there's a bit of an impedance-mismatch between the Xray + // and non-Xray machinery. The Xray machinery wants an API that returns a + // JS::PropertyDescriptor, so that the resolve hook doesn't have to get + // snared up with trying to define a property on the Xray holder. At the + // same time, the DefineInterface callbacks are set up to define things + // directly on the global. And re-jiggering them to return property + // descriptors is tricky, because some DefineInterface callbacks define + // multiple things (like the Image() alias for HTMLImageElement). + // + // So the setup is as-follows: + // + // * The resolve function takes a JS::PropertyDescriptor, but in the + // non-Xray case, callees may define things directly on the global, and + // set the value on the property descriptor to |undefined| to indicate + // that there's nothing more for the caller to do. We assert against + // this behavior in the Xray case. + // + // * We make sure that we do a non-Xray resolve first, so that all the + // slots are set up. In the Xray case, this means unwrapping and doing + // a non-Xray resolve before doing the Xray resolve. + // + // This all could use some grand refactoring, but for now we just limp + // along. + if (xpc::WrapperFactory::IsXrayWrapper(aObj)) { + JS::Rooted<JSObject*> interfaceObject(aCx); + { + JSAutoCompartment ac(aCx, global); + interfaceObject = entry->mDefine(aCx, global, aId, false); + } + if (NS_WARN_IF(!interfaceObject)) { + return Throw(aCx, NS_ERROR_FAILURE); + } + if (!JS_WrapObject(aCx, &interfaceObject)) { + return Throw(aCx, NS_ERROR_FAILURE); + } + + FillPropertyDescriptor(aDesc, aObj, 0, JS::ObjectValue(*interfaceObject)); + return true; + } + + JS::Rooted<JSObject*> interfaceObject(aCx, + entry->mDefine(aCx, aObj, aId, true)); + if (NS_WARN_IF(!interfaceObject)) { + return Throw(aCx, NS_ERROR_FAILURE); + } + + // We've already defined the property. We indicate this to the caller + // by filling a property descriptor with JS::UndefinedValue() as the + // value. We still have to fill in a property descriptor, though, so + // that the caller knows the property is in fact on this object. It + // doesn't matter what we pass for the "readonly" argument here. + FillPropertyDescriptor(aDesc, aObj, JS::UndefinedValue(), false); + + return true; +} + +/* static */ +bool +WebIDLGlobalNameHash::MayResolve(jsid aId) +{ + WebIDLNameTableKey key(JSID_TO_FLAT_STRING(aId)); + // Rooting analysis thinks nsTHashtable<...>::Contains may GC because it ends + // up calling through PLDHashTableOps' matchEntry function pointer, but we + // know WebIDLNameTableEntry::KeyEquals can't cause a GC. + JS::AutoSuppressGCAnalysis suppress; + return sWebIDLGlobalNames->Contains(key); +} + +/* static */ +void +WebIDLGlobalNameHash::GetNames(JSContext* aCx, JS::Handle<JSObject*> aObj, + nsTArray<nsString>& aNames) +{ + for (auto iter = sWebIDLGlobalNames->Iter(); !iter.Done(); iter.Next()) { + const WebIDLNameTableEntry* entry = iter.Get(); + if (!entry->mEnabled || entry->mEnabled(aCx, aObj)) { + AppendASCIItoUTF16(nsDependentCString(sNames + entry->mNameOffset, + entry->mNameLength), + *aNames.AppendElement()); + } + } +} + +} // namespace dom +} // namespace mozilla |