Skip to content
Snippets Groups Projects
Verified Commit 04d9ff13 authored by Théodore Biadala's avatar Théodore Biadala
Browse files

Issue #3464530 by nod_, mabho, nicxvan, joaopauloc.dev, cassioalmeida, catch,...

Issue #3464530 by nod_, mabho, nicxvan, joaopauloc.dev, cassioalmeida, catch, sun, quietone, droplet, aaronbauman, geerlingguy: Improve performance of the user.permissions.js script running in /admin/people/permissions

(cherry picked from commit b80bfda1)
parent 44b62751
Branches
Tags
4 merge requests!11958Issue #3490507 by alexpott, smustgrave: Fix bogus mocking in...,!11769Issue #3517987: Add option to contextual filters to encode slashes in query parameter.,!11185Issue #3477324 by andypost, alexpott: Fix usage of str_getcsv() and fgetcsv() for PHP 8.4,!9944Issue #3483353: Consider making the createCopy config action optionally fail...
Pipeline #273269 passed with warnings
Pipeline: drupal

#273302

    Pipeline: drupal

    #273293

      Pipeline: drupal

      #273287

        +1
        ...@@ -20,3 +20,10 @@ ...@@ -20,3 +20,10 @@
        padding-bottom: 0.5em; padding-bottom: 0.5em;
        font-size: 0.85em; font-size: 0.85em;
        } }
        .permissions tbody tr:has(input[type="checkbox"].js-rid-authenticated:not(:checked)) .js-fake-checkbox {
        display: none;
        }
        .permissions tbody tr:has(input[type="checkbox"].js-rid-authenticated:checked) .js-real-checkbox {
        display: none;
        }
        ...@@ -50,7 +50,7 @@ protected function setUp(): void { ...@@ -50,7 +50,7 @@ protected function setUp(): void {
        } }
        /** /**
        * Tests the dummy checkboxes added to the permissions page. * Tests the fake checkboxes added to the permissions page.
        */ */
        public function testPermissionCheckboxes(): void { public function testPermissionCheckboxes(): void {
        $this->drupalLogin($this->adminUser); $this->drupalLogin($this->adminUser);
        ...@@ -58,24 +58,25 @@ public function testPermissionCheckboxes(): void { ...@@ -58,24 +58,25 @@ public function testPermissionCheckboxes(): void {
        $page = $this->getSession()->getPage(); $page = $this->getSession()->getPage();
        $wrapper = $page->find('css', '.form-item-' . $this->rid . '-administer-modules'); $wrapper = $page->find('css', '.form-item-' . $this->rid . '-administer-modules');
        $real_checkbox = $wrapper->find('css', '.real-checkbox'); $fake_checkbox = $wrapper->find('css', '.fake-checkbox');
        $dummy_checkbox = $wrapper->find('css', '.dummy-checkbox');
        // The real per-role checkbox is visible and unchecked, the dummy copy is // The real per-role checkbox is visible and unchecked, the fake copy does
        // invisible. // not exist yet.
        $this->assertTrue($real_checkbox->isVisible()); $this->assertNull($fake_checkbox);
        $this->assertFalse($real_checkbox->isChecked());
        $this->assertFalse($dummy_checkbox->isVisible());
        // Enable the permission for all authenticated users. // Enable the permission for all authenticated users.
        $page->findField('authenticated[administer modules]')->click(); $page->findField('authenticated[administer modules]')->click();
        // The real and dummy checkboxes switch visibility and the dummy is now both // The checkboxes have been initialized.
        $real_checkbox = $wrapper->find('css', '.real-checkbox');
        $fake_checkbox = $wrapper->find('css', '.fake-checkbox');
        // The real and fake checkboxes switch visibility and the fake is now both
        // checked and disabled. // checked and disabled.
        $this->assertFalse($real_checkbox->isVisible()); $this->assertFalse($real_checkbox->isVisible());
        $this->assertTrue($dummy_checkbox->isVisible()); $this->assertTrue($fake_checkbox->isVisible());
        $this->assertTrue($dummy_checkbox->isChecked()); $this->assertTrue($fake_checkbox->isChecked());
        $this->assertTrue($dummy_checkbox->hasAttribute('disabled')); $this->assertTrue($fake_checkbox->hasAttribute('disabled'));
        } }
        } }
        ...@@ -13,80 +13,81 @@ ...@@ -13,80 +13,81 @@
        * Attaches functionality to the permissions table. * Attaches functionality to the permissions table.
        */ */
        Drupal.behaviors.permissions = { Drupal.behaviors.permissions = {
        attach(context) { attach() {
        once('permissions', 'table#permissions').forEach((table) => { const [table] = once('permissions', 'table#permissions');
        // On a site with many roles and permissions, this behavior initially if (!table) {
        // has to perform thousands of DOM manipulations to inject checkboxes return;
        // and hide them. By detaching the table from the DOM, all operations }
        // can be performed without triggering internal layout and re-rendering
        // processes in the browser. // Create fake checkboxes. We use fake checkboxes instead of reusing
        const $table = $(table); // the existing checkboxes here because new checkboxes don't alter the
        let $ancestor; // submitted form. If we'd automatically check existing checkboxes, the
        let method; // permission table would be polluted with redundant entries. This is
        if ($table.prev().length) { // deliberate, but desirable when we automatically check them.
        $ancestor = $table.prev(); const $fakeCheckbox = $(Drupal.theme('checkbox'))
        method = 'after'; .removeClass('form-checkbox')
        } else { .addClass('fake-checkbox js-fake-checkbox')
        $ancestor = $table.parent(); .attr({
        method = 'append'; disabled: 'disabled',
        } checked: 'checked',
        $table.detach(); title: Drupal.t(
        'This permission is inherited from the authenticated user role.',
        // Create dummy checkboxes. We use dummy checkboxes instead of reusing ),
        // the existing checkboxes here because new checkboxes don't alter the });
        // submitted form. If we'd automatically check existing checkboxes, the const $wrapper = $('<div></div>').append($fakeCheckbox);
        // permission table would be polluted with redundant entries. This is const fakeCheckboxHtml = $wrapper.html();
        // deliberate, but desirable when we automatically check them.
        const $dummy = $(Drupal.theme('checkbox')) /**
        .removeClass('form-checkbox') * Process each table row to create fake checkboxes.
        .addClass('dummy-checkbox js-dummy-checkbox') *
        .attr('disabled', 'disabled') * @param {object} object
        .attr('checked', 'checked') * @param {HTMLElement} object.target
        .attr( */
        'title', function tableRowProcessing({ target }) {
        Drupal.t( once('permission-checkbox', target).forEach((checkbox) => {
        'This permission is inherited from the authenticated user role.', checkbox
        ), .closest('tr')
        ) .querySelectorAll(
        .hide(); 'input[type="checkbox"]:not(.js-rid-anonymous, .js-rid-authenticated)',
        )
        $table .forEach((check) => {
        .find('input[type="checkbox"]') check.classList.add('real-checkbox', 'js-real-checkbox');
        .not('.js-rid-anonymous, .js-rid-authenticated') check.insertAdjacentHTML('beforebegin', fakeCheckboxHtml);
        .addClass('real-checkbox js-real-checkbox') });
        .after($dummy); });
        }
        // Initialize the authenticated user checkbox.
        $table
        .find('input[type=checkbox].js-rid-authenticated')
        .on('click.permissions', this.toggle)
        // .triggerHandler() cannot be used here, as it only affects the first
        // element.
        .each(this.toggle);
        // Re-insert the table into the DOM.
        $ancestor[method]($table);
        });
        },
        /** // An IntersectionObserver object is associated with each of the table
        * Toggles all dummy checkboxes based on the checkboxes' state. // rows to activate checkboxes interactively as users scroll the page
        * // up or down. This prevents processing all checkboxes on page load.
        * If the "authenticated user" checkbox is checked, the checked and disabled const checkedCheckboxObserver = new IntersectionObserver(
        * checkboxes are shown, the real checkboxes otherwise. (entries, thisObserver) => {
        */ entries
        toggle() { .filter((entry) => entry.isIntersecting)
        const authCheckbox = this; .forEach((entry) => {
        const $row = $(this).closest('tr'); tableRowProcessing(entry);
        // jQuery performs too many layout calculations for .hide() and .show(), thisObserver.unobserve(entry.target);
        // leading to a major page rendering lag on sites with many roles and });
        // permissions. Therefore, we toggle visibility directly. },
        $row.find('.js-real-checkbox').each(function () { {
        this.style.display = authCheckbox.checked ? 'none' : ''; rootMargin: '50%',
        }); },
        $row.find('.js-dummy-checkbox').each(function () { );
        this.style.display = authCheckbox.checked ? '' : 'none';
        }); // Select rows with checked authenticated role and attach an observer
        // to each.
        table
        .querySelectorAll(
        'tbody tr input[type="checkbox"].js-rid-authenticated:checked',
        )
        .forEach((checkbox) => checkedCheckboxObserver.observe(checkbox));
        // Create checkboxes only when necessary on click.
        $(table).on(
        'click.permissions',
        'input[type="checkbox"].js-rid-authenticated',
        tableRowProcessing,
        );
        }, },
        }; };
        ......
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Please register or to comment