summaryrefslogtreecommitdiffstats
path: root/layout/base/ServoRestyleManager.cpp
blob: 0659ab8571f7f10531d4a17f615a4ef3cd89912a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
/* -*- 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 "mozilla/ServoRestyleManager.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/dom/ChildIterator.h"
#include "nsContentUtils.h"
#include "nsPrintfCString.h"
#include "nsStyleChangeList.h"

using namespace mozilla::dom;

namespace mozilla {

ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
  : RestyleManagerBase(aPresContext)
{
}

void
ServoRestyleManager::PostRestyleEvent(Element* aElement,
                                      nsRestyleHint aRestyleHint,
                                      nsChangeHint aMinChangeHint)
{
  if (MOZ_UNLIKELY(IsDisconnected()) ||
      MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
    return;
  }

  if (aRestyleHint == 0 && !aMinChangeHint && !HasPendingRestyles()) {
    return; // Nothing to do.
  }

  // XXX This is a temporary hack to make style attribute change works.
  //     In the future, we should be able to use this hint directly.
  if (aRestyleHint & eRestyle_StyleAttribute) {
    aRestyleHint &= ~eRestyle_StyleAttribute;
    aRestyleHint |= eRestyle_Self | eRestyle_Subtree;
  }

  // Note that unlike in Servo, we don't mark elements as dirty until we process
  // the restyle hints in ProcessPendingRestyles.
  if (aRestyleHint || aMinChangeHint) {
    ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
    snapshot->AddExplicitRestyleHint(aRestyleHint);
    snapshot->AddExplicitChangeHint(aMinChangeHint);
  }

  PostRestyleEventInternal(false);
}

void
ServoRestyleManager::PostRestyleEventForLazyConstruction()
{
  PostRestyleEventInternal(true);
}

void
ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
                                         nsRestyleHint aRestyleHint)
{
  NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData not implemented");
}

void
ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                                  nsRestyleHint aRestyleHint)
{
  NS_WARNING("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
}

static void
MarkSelfAndDescendantsAsNotDirtyForServo(nsIContent* aContent)
{
  aContent->UnsetIsDirtyForServo();

  if (aContent->HasDirtyDescendantsForServo()) {
    aContent->UnsetHasDirtyDescendantsForServo();

    StyleChildrenIterator it(aContent);
    for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
      MarkSelfAndDescendantsAsNotDirtyForServo(n);
    }
  }
}

void
ServoRestyleManager::RecreateStyleContexts(nsIContent* aContent,
                                           nsStyleContext* aParentContext,
                                           ServoStyleSet* aStyleSet,
                                           nsStyleChangeList& aChangeListToProcess)
{
  MOZ_ASSERT(aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT));

  nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
  if (!primaryFrame && !aContent->IsDirtyForServo()) {
    // This happens when, for example, a display: none child of a
    // HAS_DIRTY_DESCENDANTS content is reached as part of the traversal.
    MarkSelfAndDescendantsAsNotDirtyForServo(aContent);
    return;
  }

  // Work on text before.
  if (!aContent->IsElement()) {
    if (primaryFrame) {
      RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
      RefPtr<nsStyleContext> newContext =
        aStyleSet->ResolveStyleForText(aContent, aParentContext);

      for (nsIFrame* f = primaryFrame; f;
           f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
        f->SetStyleContext(newContext);
      }
    }

    aContent->UnsetIsDirtyForServo();
    return;
  }

  Element* element = aContent->AsElement();
  if (element->IsDirtyForServo()) {
    RefPtr<ServoComputedValues> computedValues =
      Servo_ComputedValues_Get(aContent).Consume();
    MOZ_ASSERT(computedValues);

    nsChangeHint changeHint = nsChangeHint(0);

    // Add an explicit change hint if appropriate.
    ServoElementSnapshot* snapshot;
    if (mModifiedElements.Get(element, &snapshot)) {
      changeHint |= snapshot->ExplicitChangeHint();
    }

    // Add the stored change hint if there's a frame. If there isn't a frame,
    // generate a ReconstructFrame change hint if the new display value
    // (which we can get from the ComputedValues stored on the node) is not
    // none.
    if (primaryFrame) {
      changeHint |= primaryFrame->StyleContext()->ConsumeStoredChangeHint();
    } else {
      const nsStyleDisplay* currentDisplay =
        Servo_GetStyleDisplay(computedValues);
      if (currentDisplay->mDisplay != StyleDisplay::None) {
        changeHint |= nsChangeHint_ReconstructFrame;
      }
    }

    // Add the new change hint to the list of elements to process if
    // we need to do any work.
    if (changeHint) {
      aChangeListToProcess.AppendChange(primaryFrame, element, changeHint);
    }

    // The frame reconstruction step (if needed) will ask for the descendants'
    // style correctly. If not needed, we're done too.
    //
    // Note that we must leave the old style on an existing frame that is
    // about to be reframed, since some frame constructor code wants to
    // inspect the old style to work out what to do.
    if (changeHint & nsChangeHint_ReconstructFrame) {
      // Since we might still have some dirty bits set on descendants,
      // inconsistent with the clearing of HasDirtyDescendants we will do as
      // we return from these recursive RecreateStyleContexts calls, we
      // explicitly clear them here.  Otherwise we will trigger assertions
      // when we soon process the frame reconstruction.
      MarkSelfAndDescendantsAsNotDirtyForServo(element);
      return;
    }

    // If there is no frame, and we didn't generate a ReconstructFrame change
    // hint, then we don't need to do any more work.
    if (!primaryFrame) {
      aContent->UnsetIsDirtyForServo();
      return;
    }

    // Hold the old style context alive, because it could become a dangling
    // pointer during the replacement. In practice it's not a huge deal (on
    // GetNextContinuationWithSameStyle the pointer is not dereferenced, only
    // compared), but better not playing with dangling pointers if not needed.
    RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
    MOZ_ASSERT(oldStyleContext);

    RefPtr<nsStyleContext> newContext =
      aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
                            CSSPseudoElementType::NotPseudo);

    // XXX This could not always work as expected: there are kinds of content
    // with the first split and the last sharing style, but others not. We
    // should handle those properly.
    for (nsIFrame* f = primaryFrame; f;
         f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
      f->SetStyleContext(newContext);
    }

    // Update pseudo-elements state if appropriate.
    const static CSSPseudoElementType pseudosToRestyle[] = {
      CSSPseudoElementType::before,
      CSSPseudoElementType::after,
    };

    for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
      nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);

      if (nsIFrame* pseudoFrame = FrameForPseudoElement(element, pseudoTag)) {
        // TODO: we could maybe make this more performant via calling into
        // Servo just once to know which pseudo-elements we've got to restyle?
        RefPtr<nsStyleContext> pseudoContext =
          aStyleSet->ProbePseudoElementStyle(element, pseudoType, newContext);

        // If pseudoContext is null here, it means the frame is going away, so
        // our change hint computation should have already indicated we need
        // to reframe.
        MOZ_ASSERT_IF(!pseudoContext,
                      changeHint & nsChangeHint_ReconstructFrame);
        if (pseudoContext) {
          pseudoFrame->SetStyleContext(pseudoContext);

          // We only care restyling text nodes, since other type of nodes
          // (images), are still not supported. If that eventually changes, we
          // may have to write more code here... Or not, I don't think too
          // many inherited properties can affect those other frames.
          StyleChildrenIterator it(pseudoFrame->GetContent());
          for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
            if (n->IsNodeOfType(nsINode::eTEXT)) {
              RefPtr<nsStyleContext> childContext =
                aStyleSet->ResolveStyleForText(n, pseudoContext);
              MOZ_ASSERT(n->GetPrimaryFrame(),
                         "How? This node is created at FC time!");
              n->GetPrimaryFrame()->SetStyleContext(childContext);
            }
          }
        }
      }
    }

    aContent->UnsetIsDirtyForServo();
  }

  if (aContent->HasDirtyDescendantsForServo()) {
    MOZ_ASSERT(primaryFrame,
               "Frame construction should be scheduled, and it takes the "
               "correct style for the children, so no need to be here.");
    StyleChildrenIterator it(aContent);
    for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
      if (n->IsElement() || n->IsNodeOfType(nsINode::eTEXT)) {
        RecreateStyleContexts(n, primaryFrame->StyleContext(),
                              aStyleSet, aChangeListToProcess);
      }
    }
    aContent->UnsetHasDirtyDescendantsForServo();
  }
}

static void
MarkChildrenAsDirtyForServo(nsIContent* aContent)
{
  StyleChildrenIterator it(aContent);

  nsIContent* n = it.GetNextChild();
  bool hadChildren = bool(n);
  for (; n; n = it.GetNextChild()) {
    n->SetIsDirtyForServo();
  }

  if (hadChildren) {
    aContent->SetHasDirtyDescendantsForServo();
  }
}

/* static */ nsIFrame*
ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
                                           nsIAtom* aPseudoTagOrNull)
{
  MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());

  if (!aPseudoTagOrNull) {
    return aContent->GetPrimaryFrame();
  }

  if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
    return nsLayoutUtils::GetBeforeFrame(aContent);
  }

  if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
    return nsLayoutUtils::GetAfterFrame(aContent);
  }

  MOZ_CRASH("Unkown pseudo-element given to "
            "ServoRestyleManager::FrameForPseudoElement");
  return nullptr;
}

/* static */ void
ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
{
  const nsRestyleHint HANDLED_RESTYLE_HINTS = eRestyle_Self |
                                              eRestyle_Subtree |
                                              eRestyle_LaterSiblings |
                                              eRestyle_SomeDescendants;
  // NB: For Servo, at least for now, restyling and running selector-matching
  // against the subtree is necessary as part of restyling the element, so
  // processing eRestyle_Self will perform at least as much work as
  // eRestyle_Subtree.
  if (aHint & (eRestyle_Self | eRestyle_Subtree)) {
    aElement->SetIsDirtyForServo();
    aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
  // NB: Servo gives us a eRestyle_SomeDescendants when it expects us to run
  // selector matching on all the descendants. There's a bug on Servo to align
  // meanings here (#12710) to avoid this potential source of confusion.
  } else if (aHint & eRestyle_SomeDescendants) {
    MarkChildrenAsDirtyForServo(aElement);
    aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
  }

  if (aHint & eRestyle_LaterSiblings) {
    aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
    for (nsIContent* cur = aElement->GetNextSibling(); cur;
         cur = cur->GetNextSibling()) {
      cur->SetIsDirtyForServo();
    }
  }

  // TODO: Handle all other nsRestyleHint values.
  if (aHint & ~HANDLED_RESTYLE_HINTS) {
    NS_WARNING(nsPrintfCString("stylo: Unhandled restyle hint %s",
                               RestyleManagerBase::RestyleHintToString(aHint).get()).get());
  }
}

void
ServoRestyleManager::ProcessPendingRestyles()
{
  MOZ_ASSERT(PresContext()->Document(), "No document?  Pshaw!");
  MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");

  if (MOZ_UNLIKELY(!PresContext()->PresShell()->DidInitialize())) {
    // PresShell::FlushPendingNotifications doesn't early-return in the case
    // where the PreShell hasn't yet been initialized (and therefore we haven't
    // yet done the initial style traversal of the DOM tree). We should arguably
    // fix up the callers and assert against this case, but we just detect and
    // handle it for now.
    return;
  }

  if (!HasPendingRestyles()) {
    return;
  }

  ServoStyleSet* styleSet = StyleSet();
  nsIDocument* doc = PresContext()->Document();
  Element* root = doc->GetRootElement();
  if (root) {
    // ProcessPendingRestyles can generate new restyles (e.g. from the
    // frame constructor if it decides that a ReconstructFrame change must
    // apply to the parent of the element that generated that hint).  So
    // we loop while mModifiedElements still has some restyles in it, clearing
    // it after each RecreateStyleContexts call below.
    while (!mModifiedElements.IsEmpty()) {
      for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) {
        ServoElementSnapshot* snapshot = iter.UserData();
        Element* element = iter.Key();

        // The element is no longer in the document, so don't bother computing
        // a final restyle hint for it.
        //
        // XXXheycam RestyleTracker checks that the element's GetComposedDoc()
        // matches the document we're restyling.  Do we need to do that too?
        if (!element->IsInComposedDoc()) {
          continue;
        }

        // TODO: avoid the ComputeRestyleHint call if we already have the highest
        // explicit restyle hint?
        nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
        hint |= snapshot->ExplicitRestyleHint();

        if (hint) {
          NoteRestyleHint(element, hint);
        }
      }

      if (!root->IsDirtyForServo() && !root->HasDirtyDescendantsForServo()) {
        mModifiedElements.Clear();
        break;
      }

      mInStyleRefresh = true;
      styleSet->StyleDocument(/* aLeaveDirtyBits = */ true);

      // First do any queued-up frame creation. (see bugs 827239 and 997506).
      //
      // XXXEmilio I'm calling this to avoid random behavior changes, since we
      // delay frame construction after styling we should re-check once our
      // model is more stable whether we can skip this call.
      //
      // Note this has to be *after* restyling, because otherwise frame
      // construction will find unstyled nodes, and that's not funny.
      PresContext()->FrameConstructor()->CreateNeededFrames();

      nsStyleChangeList changeList;
      RecreateStyleContexts(root, nullptr, styleSet, changeList);

      mModifiedElements.Clear();
      ProcessRestyledFrames(changeList);

      mInStyleRefresh = false;
    }
  }

  MOZ_ASSERT(!doc->IsDirtyForServo());
  doc->UnsetHasDirtyDescendantsForServo();

  IncrementRestyleGeneration();
}

void
ServoRestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
                                              nsIContent* aChild)
{
  //
  // XXXbholley: We need the Gecko logic here to correctly restyle for things
  // like :empty and positional selectors (though we may not need to post
  // restyle events as agressively as the Gecko path does).
  //
  // Bug 1297899 tracks this work.
  //
}

void
ServoRestyleManager::ContentInserted(nsINode* aContainer, nsIContent* aChild)
{
  if (aContainer == aContainer->OwnerDoc()) {
    // If we're getting this notification for the insertion of a root element,
    // that means either:
    //   (a) We initialized the PresShell before the root element existed, or
    //   (b) The root element was removed and it or another root is being
    //       inserted.
    //
    // Either way the whole tree is dirty, so we should style the document.
    MOZ_ASSERT(aChild == aChild->OwnerDoc()->GetRootElement());
    MOZ_ASSERT(aChild->IsDirtyForServo());
    StyleSet()->StyleDocument(/* aLeaveDirtyBits = */ false);
    return;
  }

  if (!aContainer->HasServoData()) {
    // This can happen with display:none. Bug 1297249 tracks more investigation
    // and assertions here.
    return;
  }

  // Style the new subtree because we will most likely need it during subsequent
  // frame construction. Bug 1298281 tracks deferring this work in the lazy
  // frame construction case.
  StyleSet()->StyleNewSubtree(aChild);

  RestyleForInsertOrChange(aContainer, aChild);
}

void
ServoRestyleManager::RestyleForAppend(nsIContent* aContainer,
                                      nsIContent* aFirstNewContent)
{
  //
  // XXXbholley: We need the Gecko logic here to correctly restyle for things
  // like :empty and positional selectors (though we may not need to post
  // restyle events as agressively as the Gecko path does).
  //
  // Bug 1297899 tracks this work.
  //
}

void
ServoRestyleManager::ContentAppended(nsIContent* aContainer,
                                     nsIContent* aFirstNewContent)
{
  if (!aContainer->HasServoData()) {
    // This can happen with display:none. Bug 1297249 tracks more investigation
    // and assertions here.
    return;
  }

  // Style the new subtree because we will most likely need it during subsequent
  // frame construction. Bug 1298281 tracks deferring this work in the lazy
  // frame construction case.
  if (aFirstNewContent->GetNextSibling()) {
    aContainer->SetHasDirtyDescendantsForServo();
    StyleSet()->StyleNewChildren(aContainer);
  } else {
    StyleSet()->StyleNewSubtree(aFirstNewContent);
  }

  RestyleForAppend(aContainer, aFirstNewContent);
}

void
ServoRestyleManager::ContentRemoved(nsINode* aContainer,
                                    nsIContent* aOldChild,
                                    nsIContent* aFollowingSibling)
{
  NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented");
}

void
ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
                                         EventStates aChangedBits)
{
  if (!aContent->IsElement()) {
    return;
  }

  Element* aElement = aContent->AsElement();
  nsChangeHint changeHint;
  nsRestyleHint restyleHint;

  // NOTE: restyleHint here is effectively always 0, since that's what
  // ServoStyleSet::HasStateDependentStyle returns. Servo computes on
  // ProcessPendingRestyles using the ElementSnapshot, but in theory could
  // compute it sequentially easily.
  //
  // Determine what's the best way to do it, and how much work do we save
  // processing the restyle hint early (i.e., computing the style hint here
  // sequentially, potentially saving the snapshot), vs lazily (snapshot
  // approach).
  //
  // If we take the sequential approach we need to specialize Servo's restyle
  // hints system a bit more, and mesure whether we save something storing the
  // restyle hint in the table and deferring the dirtiness setting until
  // ProcessPendingRestyles (that's a requirement if we store snapshots though),
  // vs processing the restyle hint in-place, dirtying the nodes on
  // PostRestyleEvent.
  //
  // If we definitely take the snapshot approach, we should take rid of
  // HasStateDependentStyle, etc (though right now they're no-ops).
  ContentStateChangedInternal(aElement, aChangedBits, &changeHint,
                              &restyleHint);

  EventStates previousState = aElement->StyleState() ^ aChangedBits;
  ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
  snapshot->AddState(previousState);

  PostRestyleEvent(aElement, restyleHint, changeHint);
}

void
ServoRestyleManager::AttributeWillChange(Element* aElement,
                                         int32_t aNameSpaceID,
                                         nsIAtom* aAttribute, int32_t aModType,
                                         const nsAttrValue* aNewValue)
{
  ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
  snapshot->AddAttrs(aElement);
}

void
ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
                                      nsIAtom* aAttribute, int32_t aModType,
                                      const nsAttrValue* aOldValue)
{
  MOZ_ASSERT(SnapshotForElement(aElement)->HasAttrs());
  if (aAttribute == nsGkAtoms::style) {
    PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0));
  }
}

nsresult
ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
{
  NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
  return NS_OK;
}

ServoElementSnapshot*
ServoRestyleManager::SnapshotForElement(Element* aElement)
{
  // NB: aElement is the argument for the construction of the snapshot in the
  // not found case.
  return mModifiedElements.LookupOrAdd(aElement, aElement);
}

} // namespace mozilla