Bug 1555133 - Add some heuristics about visible content in a document. r=bzbarsky

This is intended to give a reasonable number that scales with the amount of
content in a website during page load, for scheduling purposes.

This effectively counts the amount of text connected to a document that isn't
likely to be inline style or script.

Potential improvements:

 * Maybe have some more heuristics for hidden elements, like presence of the
   `hidden` attribute?

 * Maybe skip whitespace-only text? This does a pretty good job anyways because
   whitespace nodes are usually pretty small (like a couple newlines and
   spaces), so they don't add too much to the number. This could be done cheaply if
   looking at sSpaceSharedString / sTabSharedString.

 * Add some weight to some elements? Maybe images should have a fixed weight,
   for example. Though you don't want 0x0 images and such to count... Maybe we
   should add to this heuristic out of band when processing image loads or some
   such.

 * Handle shadow DOM and such better? Right now Shadow DOM and XBL are always
   assumed visible as long as they're connected. You _can_ in theory do
   something like stash a `<div>` inside a `<style>` element, attach a
   ShadowRoot and such, and append a bunch of stuff inside. But I don't think
   it's something we should be particularly worried about.

 * Probably add some check to CharacterData::AppendText as well?  Otherwise this
   undercounts when loading big amount of text arrives via the network, for
   example, but also I'm not sure we're optimizing for log files and such so it
   might be ok.

In any case, this gives us a heuristic that we can iterate on later. This does a
pretty good job at representing the amount of content in the examples over here:

 * https://faraday.basschouten.com/mozilla/executionorder/

For example for:

 * https://faraday.basschouten.com/mozilla/executionorder/allinlinedual.html

You get an output like the following if you print the heuristic after each bind
operation (and de-duplicating them):

```
0
3 // Some whitespace in <head>
4 // Some whitespace in the <body>.
5
6
7
8
9
10
65547 // Actual content injected by the first script.
65548 // Some more whitespace.
131085 // Actual content injected by the second script.
131087 // Some more whitespace.
```

I'm not a fan of what clang-format has done to my code btw :)

Differential Revision: https://phabricator.services.mozilla.com/D34397

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Emilio Cobos Álvarez 2019-06-11 10:00:46 +00:00
parent ab00497b2b
commit ac8c134eb5
4 changed files with 99 additions and 17 deletions

View File

@ -9,8 +9,10 @@
#ifndef mozilla_dom_BindContext_h__
#define mozilla_dom_BindContext_h__
#include "mozilla/Attributes.h"
#include "nsXBLBinding.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ShadowRoot.h"
@ -18,6 +20,9 @@ namespace mozilla {
namespace dom {
struct MOZ_STACK_CLASS BindContext final {
struct NestingLevel;
friend struct NestingLevel;
// The document that owns the tree we're getting bound to.
//
// This is mostly an optimization to avoid silly pointer-chases to get the
@ -49,14 +54,17 @@ struct MOZ_STACK_CLASS BindContext final {
Element* GetBindingParent() const { return mBindingParent; }
// This constructor should be used for regular appends to content.
explicit BindContext(nsINode& aParentNode)
: mDoc(*aParentNode.OwnerDoc()),
mBindingParent(aParentNode.IsContent()
? aParentNode.AsContent()->GetBindingParent()
explicit BindContext(nsINode& aParent)
: mDoc(*aParent.OwnerDoc()),
mBindingParent(aParent.IsContent()
? aParent.AsContent()->GetBindingParent()
: nullptr),
mInComposedDoc(aParentNode.IsInComposedDoc()),
mInUncomposedDoc(aParentNode.IsInUncomposedDoc()),
mSubtreeRootChanges(true) {}
mInComposedDoc(aParent.IsInComposedDoc()),
mInUncomposedDoc(aParent.IsInUncomposedDoc()),
mSubtreeRootChanges(true),
mCollectingDisplayedNodeDataDuringLoad(
ShouldCollectDisplayedNodeDataDuringLoad(mInComposedDoc, mDoc,
aParent)) {}
// When re-binding a shadow host into a tree, we re-bind all the shadow tree
// from the root. In that case, the shadow tree contents remain within the
@ -69,7 +77,10 @@ struct MOZ_STACK_CLASS BindContext final {
mBindingParent(aShadowRoot.Host()),
mInComposedDoc(aShadowRoot.IsInComposedDoc()),
mInUncomposedDoc(false),
mSubtreeRootChanges(false) {}
mSubtreeRootChanges(false),
mCollectingDisplayedNodeDataDuringLoad(
ShouldCollectDisplayedNodeDataDuringLoad(mInComposedDoc, mDoc,
aShadowRoot)) {}
// This constructor is meant to be used when inserting native-anonymous
// children into a subtree.
@ -79,7 +90,10 @@ struct MOZ_STACK_CLASS BindContext final {
mBindingParent(&aParentElement),
mInComposedDoc(aParentElement.IsInComposedDoc()),
mInUncomposedDoc(aParentElement.IsInUncomposedDoc()),
mSubtreeRootChanges(true) {
mSubtreeRootChanges(true),
mCollectingDisplayedNodeDataDuringLoad(
ShouldCollectDisplayedNodeDataDuringLoad(mInComposedDoc, mDoc,
aParentElement)) {
MOZ_ASSERT(mInComposedDoc, "Binding NAC in a disconnected subtree?");
}
@ -89,9 +103,28 @@ struct MOZ_STACK_CLASS BindContext final {
mBindingParent(aBinding.GetBoundElement()),
mInComposedDoc(aParentElement.IsInComposedDoc()),
mInUncomposedDoc(aParentElement.IsInUncomposedDoc()),
mSubtreeRootChanges(true) {}
mSubtreeRootChanges(true),
mCollectingDisplayedNodeDataDuringLoad(
ShouldCollectDisplayedNodeDataDuringLoad(mInComposedDoc, mDoc,
aParentElement)) {}
bool CollectingDisplayedNodeDataDuringLoad() const {
return mCollectingDisplayedNodeDataDuringLoad;
}
private:
static bool IsLikelyUndisplayed(const nsINode& aParent) {
return aParent.IsAnyOfHTMLElements(nsGkAtoms::style, nsGkAtoms::script);
}
static bool ShouldCollectDisplayedNodeDataDuringLoad(bool aConnected,
Document& aDoc,
nsINode& aParent) {
return aDoc.GetReadyStateEnum() == Document::READYSTATE_LOADING &&
aConnected && !IsLikelyUndisplayed(aParent);
}
Document& mDoc;
Element* const mBindingParent;
@ -102,6 +135,27 @@ struct MOZ_STACK_CLASS BindContext final {
// Whether the bind operation will change the subtree root of the content
// we're binding.
const bool mSubtreeRootChanges;
// Whether it's likely that we're in an undisplayed part of the DOM.
//
// NOTE(emilio): We don't inherit this in BindContext's for Shadow DOM or XBL
// or such. This means that if you have a shadow tree inside an undisplayed
// element it will be incorrectly counted. But given our current definition
// of undisplayed element this is not likely to matter in practice.
bool mCollectingDisplayedNodeDataDuringLoad;
};
struct MOZ_STACK_CLASS BindContext::NestingLevel {
explicit NestingLevel(BindContext& aContext, const Element& aParent)
: mRestoreCollecting(aContext.mCollectingDisplayedNodeDataDuringLoad) {
if (aContext.mCollectingDisplayedNodeDataDuringLoad) {
aContext.mCollectingDisplayedNodeDataDuringLoad =
BindContext::IsLikelyUndisplayed(aParent);
}
}
private:
AutoRestore<bool> mRestoreCollecting;
};
} // namespace dom

View File

@ -461,8 +461,13 @@ nsresult CharacterData::BindToTree(BindContext& aContext, nsINode& aParent) {
aParent.AsContent()->GetContainingShadow();
}
if (IsInComposedDoc() && mText.IsBidi()) {
aContext.OwnerDoc().SetBidiEnabled();
if (IsInComposedDoc()) {
if (mText.IsBidi()) {
aContext.OwnerDoc().SetBidiEnabled();
}
if (aContext.CollectingDisplayedNodeDataDuringLoad()) {
aContext.OwnerDoc().AddToVisibleContentHeuristic(mText.GetLength());
}
}
// Clear the lazy frame construction bits.

View File

@ -3699,6 +3699,16 @@ class Document : public nsINode,
void PropagateUseCounters(Document* aParentDocument);
void AddToVisibleContentHeuristic(size_t aNumber) {
if (MOZ_UNLIKELY(SIZE_MAX - mVisibleContentHeuristic < aNumber)) {
mVisibleContentHeuristic = SIZE_MAX;
} else {
mVisibleContentHeuristic += aNumber;
}
}
size_t GetVisibleContentHeuristic() const { return mVisibleContentHeuristic; }
// Called to track whether this document has had any interaction.
// This is used to track whether we should permit "beforeunload".
void SetUserHasInteracted();
@ -4895,6 +4905,16 @@ class Document : public nsINode,
// The CSS property use counters.
UniquePtr<StyleUseCounters> mStyleUseCounters;
// An ever-increasing heuristic number that is higher the more content is
// likely to be visible in the page.
//
// Right now it effectively measures amount of text content that has ever been
// connected to the document in some way, and is not under a <script> or
// <style>.
//
// Note that this is only measured during load.
size_t mVisibleContentHeuristic = 0;
// Whether the user has interacted with the document or not:
bool mUserHasInteracted;

View File

@ -1703,10 +1703,13 @@ nsresult Element::BindToTree(BindContext& aContext, nsINode& aParent) {
// Now recurse into our kids
nsresult rv;
for (nsIContent* child = GetFirstChild(); child;
child = child->GetNextSibling()) {
rv = child->BindToTree(aContext, *this);
NS_ENSURE_SUCCESS(rv, rv);
{
BindContext::NestingLevel level(aContext, *this);
for (nsIContent* child = GetFirstChild(); child;
child = child->GetNextSibling()) {
rv = child->BindToTree(aContext, *this);
NS_ENSURE_SUCCESS(rv, rv);
}
}
nsNodeUtils::ParentChainChanged(this);