Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
80.39% covered (warning)
80.39%
41 / 51
51.28% covered (warning)
51.28%
20 / 39
16.67% covered (danger)
16.67%
5 / 30
60.00% covered (warning)
60.00%
9 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
DisplayBuilderEventsSubscriber
80.39% covered (warning)
80.39%
41 / 51
51.28% covered (warning)
51.28%
20 / 39
16.67% covered (danger)
16.67%
5 / 30
60.00% covered (warning)
60.00%
9 / 15
417.20
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSubscribedEvents
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onActive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onAttachToRoot
100.00% covered (success)
100.00%
1 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onAttachToSlot
100.00% covered (success)
100.00%
1 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onDelete
100.00% covered (success)
100.00%
1 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onHistoryChange
100.00% covered (success)
100.00%
1 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onRestore
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onRevert
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onMove
100.00% covered (success)
100.00%
1 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onUpdate
100.00% covered (success)
100.00%
1 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onPublish
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onPresetSave
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 dispatchToIslands
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
14 / 14
10.00% covered (danger)
10.00%
1 / 10
100.00% covered (success)
100.00%
1 / 1
42.72
 shouldDefer
50.00% covered (danger)
50.00%
5 / 10
33.33% covered (danger)
33.33%
4 / 12
28.57% covered (danger)
28.57%
2 / 7
0.00% covered (danger)
0.00%
0 / 1
19.12
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Event;
6
7use Drupal\display_builder\Island\IslandInterface;
8use Drupal\display_builder\Island\IslandPluginManagerInterface;
9use Drupal\display_builder\Island\IslandType;
10use Symfony\Component\EventDispatcher\EventSubscriberInterface;
11
12/**
13 * The event subscriber for Display Builder islands.
14 */
15class DisplayBuilderEventsSubscriber implements EventSubscriberInterface {
16
17  /**
18   * Constructs a new DisplayBuilderEventsSubscriber object.
19   */
20  public function __construct(
21    protected IslandPluginManagerInterface $islandManager,
22  ) {}
23
24  /**
25   * {@inheritdoc}
26   */
27  public static function getSubscribedEvents(): array {
28    return [
29      DisplayBuilderEvents::ON_ACTIVE => 'onActive',
30      DisplayBuilderEvents::ON_ATTACH_TO_ROOT => 'onAttachToRoot',
31      DisplayBuilderEvents::ON_ATTACH_TO_SLOT => 'onAttachToSlot',
32      DisplayBuilderEvents::ON_DELETE => 'onDelete',
33      DisplayBuilderEvents::ON_HISTORY_CHANGE => 'onHistoryChange',
34      DisplayBuilderEvents::ON_RESTORE => 'onRestore',
35      DisplayBuilderEvents::ON_REVERT => 'onRevert',
36      DisplayBuilderEvents::ON_MOVE => 'onMove',
37      DisplayBuilderEvents::ON_UPDATE => 'onUpdate',
38      DisplayBuilderEvents::ON_PUBLISH => 'onPublish',
39      DisplayBuilderEvents::ON_PRESET_SAVE => 'onPresetSave',
40    ];
41  }
42
43  /**
44   * Event handler for when a block becomes active.
45   *
46   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
47   *   The event object.
48   */
49  public function onActive(DisplayBuilderEvent $event): void {
50    $this->dispatchToIslands($event, __FUNCTION__, [$event->getData()]);
51  }
52
53  /**
54   * Event handler for when a block is attached to the root.
55   *
56   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
57   *   The event object.
58   */
59  public function onAttachToRoot(DisplayBuilderEvent $event): void {
60    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId()]);
61  }
62
63  /**
64   * Event handler for when a block is attached to a slot.
65   *
66   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
67   *   The event object.
68   */
69  public function onAttachToSlot(DisplayBuilderEvent $event): void {
70    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId(), $event->getParentId()]);
71  }
72
73  /**
74   * Event handler for when a block is deleted.
75   *
76   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
77   *   The event object.
78   */
79  public function onDelete(DisplayBuilderEvent $event): void {
80    $this->dispatchToIslands($event, __FUNCTION__, [$event->getParentId()]);
81  }
82
83  /**
84   * Event handler for when the history changes.
85   *
86   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
87   *   The event object.
88   */
89  public function onHistoryChange(DisplayBuilderEvent $event): void {
90    $this->dispatchToIslands($event, __FUNCTION__);
91  }
92
93  /**
94   * Event handler for when the builder is restored to its last saved state.
95   *
96   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
97   *   The event object.
98   */
99  public function onRestore(DisplayBuilderEvent $event): void {
100    $this->dispatchToIslands($event, __FUNCTION__);
101  }
102
103  /**
104   * Event handler for when an override is reverted to its default state.
105   *
106   * Reuses the history-change island callbacks since the UI refresh is
107   * identical: all islands must re-render the updated state and history.
108   *
109   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
110   *   The event object.
111   */
112  public function onRevert(DisplayBuilderEvent $event): void {
113    $this->dispatchToIslands($event, __FUNCTION__);
114  }
115
116  /**
117   * Event handler for when a block is moved.
118   *
119   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
120   *   The event object.
121   */
122  public function onMove(DisplayBuilderEvent $event): void {
123    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId()]);
124  }
125
126  /**
127   * Event handler for when a block is updated.
128   *
129   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
130   *   The event object.
131   */
132  public function onUpdate(DisplayBuilderEvent $event): void {
133    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId()]);
134  }
135
136  /**
137   * Event handler for when a display is saved.
138   *
139   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
140   *   The event object.
141   */
142  public function onPublish(DisplayBuilderEvent $event): void {
143    $this->dispatchToIslands($event, __FUNCTION__, [$event->getData()]);
144  }
145
146  /**
147   * Event handler for when a preset is saved.
148   *
149   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
150   *   The event object.
151   */
152  public function onPresetSave(DisplayBuilderEvent $event): void {
153    $this->dispatchToIslands($event, __FUNCTION__);
154  }
155
156  /**
157   * Dispatch the event with a generic code.
158   *
159   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
160   *   The event object.
161   * @param string $method
162   *   The method to dispatch.
163   * @param array $parameters
164   *   (Optional) The parameters to the method.
165   */
166  private function dispatchToIslands(DisplayBuilderEvent $event, string $method, array $parameters = []): void {
167    \array_unshift($parameters, $event->getInstance());
168
169    $configuration = $event->getIslandConfiguration();
170    $contexts = $event->getInstance()->getAvailableContexts();
171    $islands = $this->islandManager->createInstances($this->islandManager->getDefinitions(), $contexts, $configuration);
172
173    $island_enabled = $event->getEnabledIslands();
174    $visible_islands = $event->getVisibleIslands();
175
176    foreach ($islands as $island_id => $island) {
177      if (!isset($island_enabled[$island_id])) {
178        continue;
179      }
180
181      // Skip the island triggering the HTMX event. Useful to avoid swapping
182      // the content of an island which is already in the expected state.
183      // For examples, if we move an instance in Builder, Wireframe or Tree
184      // panels, if we change the settings in InstanceForm.
185      // @see Drupal\display_builder\Controller\ApiControllerBase::islandId
186      if ($island_id === $event->getCurrentIslandId()) {
187        continue;
188      }
189
190      // Skip panels the client told us are off screen. They are rebuilt on
191      // demand when the user brings them back into view.
192      if ($visible_islands !== NULL && $this->shouldDefer($island, $visible_islands)) {
193        continue;
194      }
195
196      $result = $island->{$method}(...$parameters);
197
198      if ($result !== NULL) {
199        $event->appendResult($island_id, $result);
200      }
201    }
202  }
203
204  /**
205   * Determines whether an island's rebuild can be deferred until it is shown.
206   *
207   * Only panels the user cannot currently see are worth deferring, and only
208   * where the client is able to notice they went stale and ask for them again.
209   * That is true of View panels in the tabbed main area and in the start
210   * sidebar drawer - one visible at a time in each - and of the Floating
211   * controls which are shown and hidden along with the panel they attach to.
212   *
213   * Everything else (toolbar buttons, contextual menu entries, Library and
214   * Contextual tabs) is always rendered: it is either permanently on screen or
215   * cheap enough that deferring it would cost more than it saves.
216   *
217   * Which islands are eligible at all is the island's own answer, so a panel
218   * that cannot survive a standalone reload can decline. This method only
219   * decides whether an eligible island is currently off screen.
220   *
221   * The region is deliberately not consulted: IslandType::regions() offers a
222   * View island only 'main' and 'sidebar', both of which show one panel at a
223   * time, so every View island qualifies. Testing the region would also be
224   * wrong, since it is only stored on the profile when explicitly configured
225   * (@see \Drupal\display_builder\Entity\Profile::setIslandConfiguration()) -
226   * an island left at its default would fall through and never be deferred.
227   *
228   * @param \Drupal\display_builder\Island\IslandInterface $island
229   *   The island to test.
230   * @param array $visible_islands
231   *   The island plugin IDs the client reports as currently visible.
232   *
233   * @return bool
234   *   TRUE if this island's rebuild should be skipped for this request.
235   *
236   * @see \Drupal\display_builder\Island\IslandInterface::isDeferrable()
237   * @see \Drupal\display_builder\Controller\ApiController::reloadIsland()
238   */
239  private function shouldDefer(IslandInterface $island, array $visible_islands): bool {
240    if (!$island->isDeferrable()) {
241      return FALSE;
242    }
243
244    $type = $island->getTypeId();
245
246    if ($type === IslandType::View->value) {
247      return !\in_array($island->getPluginId(), $visible_islands, TRUE);
248    }
249
250    // A Floating island rides along with the panel(s) it is attached to, so it
251    // is off screen exactly when all of them are.
252    // @see \Drupal\display_builder\ProfileViewBuilder::buildFloatingControlsRegion()
253    if ($type === IslandType::Floating->value) {
254      $definition = $island->getPluginDefinition();
255      $attach_to = \is_array($definition) ? ($definition['attach_to'] ?? []) : [];
256
257      return $attach_to !== [] && \array_intersect($attach_to, $visible_islands) === [];
258    }
259
260    return FALSE;
261  }
262
263}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

DisplayBuilderEventsSubscriber->__construct
21    protected IslandPluginManagerInterface $islandManager,
22  ) {}
DisplayBuilderEventsSubscriber->dispatchToIslands
166  private function dispatchToIslands(DisplayBuilderEvent $event, string $method, array $parameters = []): void {
167    \array_unshift($parameters, $event->getInstance());
168
169    $configuration = $event->getIslandConfiguration();
170    $contexts = $event->getInstance()->getAvailableContexts();
171    $islands = $this->islandManager->createInstances($this->islandManager->getDefinitions(), $contexts, $configuration);
172
173    $island_enabled = $event->getEnabledIslands();
174    $visible_islands = $event->getVisibleIslands();
175
176    foreach ($islands as $island_id => $island) {
176    foreach ($islands as $island_id => $island) {
176    foreach ($islands as $island_id => $island) {
177      if (!isset($island_enabled[$island_id])) {
178        continue;
186      if ($island_id === $event->getCurrentIslandId()) {
187        continue;
192      if ($visible_islands !== NULL && $this->shouldDefer($island, $visible_islands)) {
192      if ($visible_islands !== NULL && $this->shouldDefer($island, $visible_islands)) {
192      if ($visible_islands !== NULL && $this->shouldDefer($island, $visible_islands)) {
193        continue;
196      $result = $island->{$method}(...$parameters);
197
198      if ($result !== NULL) {
176    foreach ($islands as $island_id => $island) {
177      if (!isset($island_enabled[$island_id])) {
178        continue;
179      }
180
181      // Skip the island triggering the HTMX event. Useful to avoid swapping
182      // the content of an island which is already in the expected state.
183      // For examples, if we move an instance in Builder, Wireframe or Tree
184      // panels, if we change the settings in InstanceForm.
185      // @see Drupal\display_builder\Controller\ApiControllerBase::islandId
186      if ($island_id === $event->getCurrentIslandId()) {
187        continue;
188      }
189
190      // Skip panels the client told us are off screen. They are rebuilt on
191      // demand when the user brings them back into view.
192      if ($visible_islands !== NULL && $this->shouldDefer($island, $visible_islands)) {
193        continue;
194      }
195
196      $result = $island->{$method}(...$parameters);
197
198      if ($result !== NULL) {
199        $event->appendResult($island_id, $result);
176    foreach ($islands as $island_id => $island) {
176    foreach ($islands as $island_id => $island) {
177      if (!isset($island_enabled[$island_id])) {
178        continue;
179      }
180
181      // Skip the island triggering the HTMX event. Useful to avoid swapping
182      // the content of an island which is already in the expected state.
183      // For examples, if we move an instance in Builder, Wireframe or Tree
184      // panels, if we change the settings in InstanceForm.
185      // @see Drupal\display_builder\Controller\ApiControllerBase::islandId
186      if ($island_id === $event->getCurrentIslandId()) {
187        continue;
188      }
189
190      // Skip panels the client told us are off screen. They are rebuilt on
191      // demand when the user brings them back into view.
192      if ($visible_islands !== NULL && $this->shouldDefer($island, $visible_islands)) {
193        continue;
194      }
195
196      $result = $island->{$method}(...$parameters);
197
198      if ($result !== NULL) {
199        $event->appendResult($island_id, $result);
200      }
201    }
202  }
DisplayBuilderEventsSubscriber->getSubscribedEvents
29      DisplayBuilderEvents::ON_ACTIVE => 'onActive',
30      DisplayBuilderEvents::ON_ATTACH_TO_ROOT => 'onAttachToRoot',
31      DisplayBuilderEvents::ON_ATTACH_TO_SLOT => 'onAttachToSlot',
32      DisplayBuilderEvents::ON_DELETE => 'onDelete',
33      DisplayBuilderEvents::ON_HISTORY_CHANGE => 'onHistoryChange',
34      DisplayBuilderEvents::ON_RESTORE => 'onRestore',
35      DisplayBuilderEvents::ON_REVERT => 'onRevert',
36      DisplayBuilderEvents::ON_MOVE => 'onMove',
37      DisplayBuilderEvents::ON_UPDATE => 'onUpdate',
38      DisplayBuilderEvents::ON_PUBLISH => 'onPublish',
39      DisplayBuilderEvents::ON_PRESET_SAVE => 'onPresetSave',
40    ];
41  }
DisplayBuilderEventsSubscriber->onActive
49  public function onActive(DisplayBuilderEvent $event): void {
50    $this->dispatchToIslands($event, __FUNCTION__, [$event->getData()]);
51  }
DisplayBuilderEventsSubscriber->onAttachToRoot
59  public function onAttachToRoot(DisplayBuilderEvent $event): void {
60    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId()]);
61  }
DisplayBuilderEventsSubscriber->onAttachToSlot
69  public function onAttachToSlot(DisplayBuilderEvent $event): void {
70    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId(), $event->getParentId()]);
71  }
DisplayBuilderEventsSubscriber->onDelete
79  public function onDelete(DisplayBuilderEvent $event): void {
80    $this->dispatchToIslands($event, __FUNCTION__, [$event->getParentId()]);
81  }
DisplayBuilderEventsSubscriber->onHistoryChange
89  public function onHistoryChange(DisplayBuilderEvent $event): void {
90    $this->dispatchToIslands($event, __FUNCTION__);
91  }
DisplayBuilderEventsSubscriber->onMove
122  public function onMove(DisplayBuilderEvent $event): void {
123    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId()]);
124  }
DisplayBuilderEventsSubscriber->onPresetSave
152  public function onPresetSave(DisplayBuilderEvent $event): void {
153    $this->dispatchToIslands($event, __FUNCTION__);
154  }
DisplayBuilderEventsSubscriber->onPublish
142  public function onPublish(DisplayBuilderEvent $event): void {
143    $this->dispatchToIslands($event, __FUNCTION__, [$event->getData()]);
144  }
DisplayBuilderEventsSubscriber->onRestore
99  public function onRestore(DisplayBuilderEvent $event): void {
100    $this->dispatchToIslands($event, __FUNCTION__);
101  }
DisplayBuilderEventsSubscriber->onRevert
112  public function onRevert(DisplayBuilderEvent $event): void {
113    $this->dispatchToIslands($event, __FUNCTION__);
114  }
DisplayBuilderEventsSubscriber->onUpdate
132  public function onUpdate(DisplayBuilderEvent $event): void {
133    $this->dispatchToIslands($event, __FUNCTION__, [$event->getNodeId()]);
134  }
DisplayBuilderEventsSubscriber->shouldDefer
239  private function shouldDefer(IslandInterface $island, array $visible_islands): bool {
240    if (!$island->isDeferrable()) {
241      return FALSE;
244    $type = $island->getTypeId();
245
246    if ($type === IslandType::View->value) {
247      return !\in_array($island->getPluginId(), $visible_islands, TRUE);
253    if ($type === IslandType::Floating->value) {
254      $definition = $island->getPluginDefinition();
255      $attach_to = \is_array($definition) ? ($definition['attach_to'] ?? []) : [];
255      $attach_to = \is_array($definition) ? ($definition['attach_to'] ?? []) : [];
255      $attach_to = \is_array($definition) ? ($definition['attach_to'] ?? []) : [];
255      $attach_to = \is_array($definition) ? ($definition['attach_to'] ?? []) : [];
256
257      return $attach_to !== [] && \array_intersect($attach_to, $visible_islands) === [];
257      return $attach_to !== [] && \array_intersect($attach_to, $visible_islands) === [];
257      return $attach_to !== [] && \array_intersect($attach_to, $visible_islands) === [];
260    return FALSE;
261  }