Bug 771043. Move MediaQueryList tracking from the prescontext to the document, so they will correctly outlive prescontext changes. r=dbaron

This commit is contained in:
Boris Zbarsky 2014-10-03 14:15:25 -04:00
parent 6106ddd104
commit fc75ac6acd
9 changed files with 171 additions and 88 deletions

View File

@ -25,6 +25,7 @@
#include "Units.h"
#include "nsExpirationTracker.h"
#include "nsClassHashtable.h"
#include "prclist.h"
class imgIRequest;
class nsAString;
@ -113,6 +114,7 @@ class OverfillCallback;
class HTMLBodyElement;
struct LifecycleCallbackArgs;
class Link;
class MediaQueryList;
class GlobalObject;
class NodeFilter;
class NodeIterator;
@ -1504,6 +1506,17 @@ public:
GetBoxObjectFor(mozilla::dom::Element* aElement,
mozilla::ErrorResult& aRv) = 0;
/**
* Support for window.matchMedia()
*/
already_AddRefed<mozilla::dom::MediaQueryList>
MatchMedia(const nsAString& aMediaQueryList);
const PRCList* MediaQueryLists() const {
return &mDOMMediaQueryLists;
}
/**
* Get the compatibility mode for this document
*/
@ -2738,6 +2751,9 @@ protected:
uint32_t mBlockDOMContentLoaded;
bool mDidFireDOMContentLoaded:1;
// Our live MediaQueryLists
PRCList mDOMMediaQueryLists;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)

View File

@ -193,6 +193,7 @@
#include "mozilla/dom/Event.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/MediaQueryList.h"
#include "mozilla/dom/NodeFilterBinding.h"
#include "mozilla/dom/OwningNonNull.h"
#include "mozilla/dom/TabChild.h"
@ -1560,6 +1561,8 @@ nsIDocument::nsIDocument()
mDidFireDOMContentLoaded(true)
{
SetInDocument();
PR_INIT_CLIST(&mDOMMediaQueryLists);
}
// NOTE! nsDocument::operator new() zeroes out all members, so don't
@ -1605,6 +1608,9 @@ ClearAllBoxObjects(nsIContent* aKey, nsPIBoxObject* aBoxObject, void* aUserArg)
nsIDocument::~nsIDocument()
{
NS_ABORT_IF_FALSE(PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists),
"must not have media query lists left");
if (mNodeInfoManager) {
mNodeInfoManager->DropDocumentReference();
}
@ -2019,6 +2025,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
for (uint32_t i = 0; i < tmp->mHostObjectURIs.Length(); ++i) {
nsHostObjectProtocolHandler::Traverse(tmp->mHostObjectURIs[i], cb);
}
// We own only the items in mDOMMediaQueryLists that have listeners;
// this reference is managed by their AddListener and RemoveListener
// methods.
for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
l != &tmp->mDOMMediaQueryLists; l = PR_NEXT_LINK(l)) {
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
if (mql->HasListeners()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
cb.NoteXPCOMChild(mql);
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument)
@ -2121,6 +2139,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
nsHostObjectProtocolHandler::RemoveDataEntry(tmp->mHostObjectURIs[i]);
}
// We own only the items in mDOMMediaQueryLists that have listeners;
// this reference is managed by their AddListener and RemoveListener
// methods.
for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
l != &tmp->mDOMMediaQueryLists; ) {
PRCList *next = PR_NEXT_LINK(l);
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
mql->RemoveAllListeners();
l = next;
}
tmp->mInUnlinkOrDeletion = false;
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -6987,6 +7016,17 @@ nsDocument::ClearBoxObjectFor(nsIContent* aContent)
}
}
already_AddRefed<MediaQueryList>
nsIDocument::MatchMedia(const nsAString& aMediaQueryList)
{
nsRefPtr<MediaQueryList> result = new MediaQueryList(this, aMediaQueryList);
// Insert the new item at the end of the linked list.
PR_INSERT_BEFORE(result, &mDOMMediaQueryLists);
return result.forget();
}
void
nsDocument::FlushSkinBindings()
{

View File

@ -5342,26 +5342,11 @@ nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList,
FORWARD_TO_OUTER_OR_THROW(MatchMedia, (aMediaQueryList, aError), aError,
nullptr);
// We need this now to ensure that we have a non-null |presContext|
// when we ought to.
// This is similar to EnsureSizeUpToDate, but only flushes frames.
nsGlobalWindow *parent = static_cast<nsGlobalWindow*>(GetPrivateParent());
if (parent) {
parent->FlushPendingNotifications(Flush_Frames);
}
if (!mDocShell) {
if (!mDoc) {
return nullptr;
}
nsRefPtr<nsPresContext> presContext;
mDocShell->GetPresContext(getter_AddRefs(presContext));
if (!presContext) {
return nullptr;
}
return presContext->MatchMedia(aMediaQueryList);
return mDoc->MatchMedia(aMediaQueryList);
}
NS_IMETHODIMP

View File

@ -246,8 +246,6 @@ nsPresContext::nsPresContext(nsIDocument* aDocument, nsPresContextType aType)
if (log && log->level >= PR_LOG_WARNING) {
mTextPerf = new gfxTextPerfMetrics();
}
PR_INIT_CLIST(&mDOMMediaQueryLists);
}
nsPresContext::~nsPresContext()
@ -255,9 +253,6 @@ nsPresContext::~nsPresContext()
NS_PRECONDITION(!mShell, "Presshell forgot to clear our mShell pointer");
SetShell(nullptr);
NS_ABORT_IF_FALSE(PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists),
"must not have media query lists left");
// Disconnect the refresh driver *after* the transition manager, which
// needs it.
if (mRefreshDriver && mRefreshDriver->PresContext() == this) {
@ -345,18 +340,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsPresContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventManager);
// NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mLanguage); // an atom
// We own only the items in mDOMMediaQueryLists that have listeners;
// this reference is managed by their AddListener and RemoveListener
// methods.
for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
l != &tmp->mDOMMediaQueryLists; l = PR_NEXT_LINK(l)) {
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
if (mql->HasListeners()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
cb.NoteXPCOMChild(mql);
}
}
// NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTheme); // a service
// NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLangService); // a service
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrintSettings);
@ -373,17 +356,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsPresContext)
tmp->mEventManager = nullptr;
}
// We own only the items in mDOMMediaQueryLists that have listeners;
// this reference is managed by their AddListener and RemoveListener
// methods.
for (PRCList *l = PR_LIST_HEAD(&tmp->mDOMMediaQueryLists);
l != &tmp->mDOMMediaQueryLists; ) {
PRCList *next = PR_NEXT_LINK(l);
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
mql->RemoveAllListeners();
l = next;
}
// NS_RELEASE(tmp->mLanguage); // an atom
// NS_IMPL_CYCLE_COLLECTION_UNLINK(mTheme); // a service
@ -1899,7 +1871,7 @@ nsPresContext::MediaFeatureValuesChanged(StyleRebuildType aShouldRebuild,
mPendingViewportChange = false;
if (mDocument->IsBeingUsedAsImage()) {
MOZ_ASSERT(PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists));
MOZ_ASSERT(PR_CLIST_IS_EMPTY(mDocument->MediaQueryLists()));
return;
}
@ -1912,7 +1884,7 @@ nsPresContext::MediaFeatureValuesChanged(StyleRebuildType aShouldRebuild,
// Note that we do this after the new style from media queries in
// style sheets has been computed.
if (!PR_CLIST_IS_EMPTY(&mDOMMediaQueryLists)) {
if (!PR_CLIST_IS_EMPTY(mDocument->MediaQueryLists())) {
// We build a list of all the notifications we're going to send
// before we send any of them. (The spec says the notifications
// should be a queued task, so any removals that happen during the
@ -1926,8 +1898,8 @@ nsPresContext::MediaFeatureValuesChanged(StyleRebuildType aShouldRebuild,
// list in the order they were created and, for each list, to the
// listeners in the order added.
MediaQueryList::NotifyList notifyList;
for (PRCList *l = PR_LIST_HEAD(&mDOMMediaQueryLists);
l != &mDOMMediaQueryLists; l = PR_NEXT_LINK(l)) {
for (PRCList *l = PR_LIST_HEAD(mDocument->MediaQueryLists());
l != mDocument->MediaQueryLists(); l = PR_NEXT_LINK(l)) {
MediaQueryList *mql = static_cast<MediaQueryList*>(l);
mql->MediumFeaturesChanged(notifyList);
}
@ -1971,17 +1943,6 @@ nsPresContext::HandleMediaFeatureValuesChangedEvent()
}
}
already_AddRefed<MediaQueryList>
nsPresContext::MatchMedia(const nsAString& aMediaQueryList)
{
nsRefPtr<MediaQueryList> result = new MediaQueryList(this, aMediaQueryList);
// Insert the new item at the end of the linked list.
PR_INSERT_BEFORE(result, &mDOMMediaQueryLists);
return result.forget();
}
nsCompatibility
nsPresContext::CompatibilityMode() const
{

View File

@ -72,7 +72,6 @@ class RestyleManager;
class CounterStyleManager;
namespace dom {
class FontFaceSet;
class MediaQueryList;
}
namespace layers {
class ContainerLayer;
@ -272,12 +271,6 @@ public:
MediaFeatureValuesChanged(eRebuildStyleIfNeeded);
}
/**
* Support for window.matchMedia()
*/
already_AddRefed<mozilla::dom::MediaQueryList>
MatchMedia(const nsAString& aMediaQueryList);
/**
* Access compatibility mode for this context. This is the same as
* our document's compatibility mode.
@ -1217,8 +1210,6 @@ protected:
mozilla::WeakPtr<nsDocShell> mContainer;
PRCList mDOMMediaQueryLists;
// Base minimum font size, independent of the language-specific global preference. Defaults to 0
int32_t mBaseMinFontSize;
float mTextZoom; // Text zoom, defaults to 1.0

View File

@ -1,3 +1,4 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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
@ -14,9 +15,9 @@
namespace mozilla {
namespace dom {
MediaQueryList::MediaQueryList(nsPresContext *aPresContext,
MediaQueryList::MediaQueryList(nsIDocument *aDocument,
const nsAString &aMediaQueryList)
: mPresContext(aPresContext),
: mDocument(aDocument),
mMediaList(new nsMediaList),
mMatchesValid(false)
{
@ -30,7 +31,7 @@ MediaQueryList::MediaQueryList(nsPresContext *aPresContext,
MediaQueryList::~MediaQueryList()
{
if (mPresContext) {
if (mDocument) {
PR_REMOVE_LINK(this);
}
}
@ -38,15 +39,15 @@ MediaQueryList::~MediaQueryList()
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaQueryList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaQueryList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallbacks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaQueryList)
if (tmp->mPresContext) {
if (tmp->mDocument) {
PR_REMOVE_LINK(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
}
tmp->RemoveAllListeners();
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
@ -140,11 +141,33 @@ MediaQueryList::RemoveAllListeners()
void
MediaQueryList::RecomputeMatches()
{
if (!mPresContext) {
if (!mDocument) {
return;
}
mMatches = mMediaList->Matches(mPresContext, nullptr);
if (mDocument->GetParentDocument()) {
// Flush frames on the parent so our prescontext will get
// recreated as needed.
mDocument->GetParentDocument()->FlushPendingNotifications(Flush_Frames);
// That might have killed our document, so recheck that.
if (!mDocument) {
return;
}
}
nsIPresShell* shell = mDocument->GetShell();
if (!shell) {
// XXXbz What's the right behavior here? Spec doesn't say.
return;
}
nsPresContext* presContext = shell->GetPresContext();
if (!presContext) {
// XXXbz What's the right behavior here? Spec doesn't say.
return;
}
mMatches = mMediaList->Matches(presContext, nullptr);
mMatchesValid = true;
}
@ -171,10 +194,7 @@ MediaQueryList::MediumFeaturesChanged(NotifyList &aListenersToNotify)
nsISupports*
MediaQueryList::GetParentObject() const
{
if (!mPresContext) {
return nullptr;
}
return mPresContext->Document();
return mDocument;
}
JSObject*

View File

@ -1,3 +1,4 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* 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
@ -11,13 +12,14 @@
#include "nsISupports.h"
#include "nsCycleCollectionParticipant.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsTArray.h"
#include "prclist.h"
#include "mozilla/Attributes.h"
#include "nsWrapperCache.h"
#include "mozilla/dom/MediaQueryListBinding.h"
class nsPresContext;
class nsIDocument;
class nsMediaList;
namespace mozilla {
@ -30,7 +32,7 @@ class MediaQueryList MOZ_FINAL : public nsISupports,
public:
// The caller who constructs is responsible for calling Evaluate
// before calling any other methods.
MediaQueryList(nsPresContext *aPresContext,
MediaQueryList(nsIDocument *aDocument,
const nsAString &aMediaQueryList);
private:
~MediaQueryList();
@ -67,7 +69,7 @@ public:
private:
void RecomputeMatches();
// We only need a pointer to the pres context to support lazy
// We only need a pointer to the document to support lazy
// reevaluation following dynamic changes. However, this lazy
// reevaluation is perhaps somewhat important, since some usage
// patterns may involve the creation of large numbers of
@ -77,11 +79,11 @@ private:
// This pointer does make us a little more dependent on cycle
// collection.
//
// We have a non-null mPresContext for our entire lifetime except
// after cycle collection unlinking. Having a non-null mPresContext
// is equivalent to being in that pres context's mDOMMediaQueryLists
// We have a non-null mDocument for our entire lifetime except
// after cycle collection unlinking. Having a non-null mDocument
// is equivalent to being in that document's mDOMMediaQueryLists
// linked list.
nsRefPtr<nsPresContext> mPresContext;
nsCOMPtr<nsIDocument> mDocument;
nsRefPtr<nsMediaList> mMediaList;
bool mMatches;

View File

@ -95,6 +95,7 @@ support-files = bug517224.sjs
support-files = file_bug645998-1.css file_bug645998-2.css
[test_bug716226.html]
[test_bug765590.html]
[test_bug771043.html]
[test_bug798567.html]
[test_bug798843_pref.html]
[test_bug829816.html]

View File

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=771043
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 771043</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug 771043 **/
var expectedValue;
var callCount = 0;
var storedHeight;
function callback(arg) {
++callCount;
is(arg.matches, expectedValue,
"Should have the right value on call #" + callCount + " to the callback");
}
function flushLayout() {
storedHeight = document.querySelector("iframe").offsetHeight;
}
function setHeight(height, reason) {
var oldCount = callCount;
var ifr = document.querySelector("iframe");
ifr.style.height = height + "px";
flushLayout();
is(callCount, oldCount+1, "Should have called our callback when " + reason);
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
var mql = frames[0].matchMedia("(orientation: landscape)");
mql.addListener(callback);
expectedValue = true;
setHeight(50, "going landscape");
expectedValue = false;
setHeight(200, "going portrait");
var ifr = document.querySelector("iframe");
ifr.style.display = "none";
flushLayout();
ifr.style.display = "";
expectedValue = true;
setHeight(50, "going landscape from display:none");
expectedValue = false;
setHeight(200, "going portrait after display:none");
SimpleTest.finish();
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=771043">Mozilla Bug 771043</a>
<!-- Important: the iframe needs to be displayed -->
<p id="display"><iframe style="width: 100px; height: 200px"</p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>