michael@0: /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* implements DOM interface for querying and observing media queries */ michael@0: michael@0: #include "mozilla/dom/MediaQueryList.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIMediaList.h" michael@0: #include "nsCSSParser.h" michael@0: #include "nsIDocument.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: MediaQueryList::MediaQueryList(nsPresContext *aPresContext, michael@0: const nsAString &aMediaQueryList) michael@0: : mPresContext(aPresContext), michael@0: mMediaList(new nsMediaList), michael@0: mMatchesValid(false) michael@0: { michael@0: PR_INIT_CLIST(this); michael@0: michael@0: SetIsDOMBinding(); michael@0: michael@0: nsCSSParser parser; michael@0: parser.ParseMediaList(aMediaQueryList, nullptr, 0, mMediaList, false); michael@0: } michael@0: michael@0: MediaQueryList::~MediaQueryList() michael@0: { michael@0: if (mPresContext) { michael@0: PR_REMOVE_LINK(this); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQueryList) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaQueryList) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallbacks) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaQueryList) michael@0: if (tmp->mPresContext) { michael@0: PR_REMOVE_LINK(tmp); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext) michael@0: } michael@0: tmp->RemoveAllListeners(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(MediaQueryList) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(MediaQueryList) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(MediaQueryList) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaQueryList) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaQueryList) michael@0: michael@0: void michael@0: MediaQueryList::GetMedia(nsAString &aMedia) michael@0: { michael@0: mMediaList->GetText(aMedia); michael@0: } michael@0: michael@0: bool michael@0: MediaQueryList::Matches() michael@0: { michael@0: if (!mMatchesValid) { michael@0: NS_ABORT_IF_FALSE(!HasListeners(), michael@0: "when listeners present, must keep mMatches current"); michael@0: RecomputeMatches(); michael@0: } michael@0: michael@0: return mMatches; michael@0: } michael@0: michael@0: void michael@0: MediaQueryList::AddListener(MediaQueryListListener& aListener) michael@0: { michael@0: if (!HasListeners()) { michael@0: // When we have listeners, the pres context owns a reference to michael@0: // this. This is a cyclic reference that can only be broken by michael@0: // cycle collection. michael@0: NS_ADDREF_THIS(); michael@0: } michael@0: michael@0: if (!mMatchesValid) { michael@0: NS_ABORT_IF_FALSE(!HasListeners(), michael@0: "when listeners present, must keep mMatches current"); michael@0: RecomputeMatches(); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mCallbacks.Length(); ++i) { michael@0: if (aListener == *mCallbacks[i]) { michael@0: // Already registered michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mCallbacks.AppendElement(&aListener); michael@0: if (!HasListeners()) { michael@0: // Append failed; undo the AddRef above. michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MediaQueryList::RemoveListener(MediaQueryListListener& aListener) michael@0: { michael@0: for (uint32_t i = 0; i < mCallbacks.Length(); ++i) { michael@0: if (aListener == *mCallbacks[i]) { michael@0: mCallbacks.RemoveElementAt(i); michael@0: if (!HasListeners()) { michael@0: // See NS_ADDREF_THIS() in AddListener. michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: MediaQueryList::RemoveAllListeners() michael@0: { michael@0: bool hadListeners = HasListeners(); michael@0: mCallbacks.Clear(); michael@0: if (hadListeners) { michael@0: // See NS_ADDREF_THIS() in AddListener. michael@0: NS_RELEASE_THIS(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: MediaQueryList::RecomputeMatches() michael@0: { michael@0: if (!mPresContext) { michael@0: return; michael@0: } michael@0: michael@0: mMatches = mMediaList->Matches(mPresContext, nullptr); michael@0: mMatchesValid = true; michael@0: } michael@0: michael@0: void michael@0: MediaQueryList::MediumFeaturesChanged(NotifyList &aListenersToNotify) michael@0: { michael@0: mMatchesValid = false; michael@0: michael@0: if (HasListeners()) { michael@0: bool oldMatches = mMatches; michael@0: RecomputeMatches(); michael@0: if (mMatches != oldMatches) { michael@0: for (uint32_t i = 0, i_end = mCallbacks.Length(); i != i_end; ++i) { michael@0: HandleChangeData *d = aListenersToNotify.AppendElement(); michael@0: if (d) { michael@0: d->mql = this; michael@0: d->callback = mCallbacks[i]; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsISupports* michael@0: MediaQueryList::GetParentObject() const michael@0: { michael@0: if (!mPresContext) { michael@0: return nullptr; michael@0: } michael@0: return mPresContext->Document(); michael@0: } michael@0: michael@0: JSObject* michael@0: MediaQueryList::WrapObject(JSContext* aCx) michael@0: { michael@0: return MediaQueryListBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla