Skip to content
Snippets Groups Projects
Verified Commit 3d9ddf67 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 7486417d
No related branches found
No related tags found
10 merge requests!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #273268 passed with warnings
Pipeline: drupal

#273301

    Pipeline: drupal

    #273290

      Pipeline: drupal

      #273284

        +1
        ......@@ -20,3 +20,10 @@
        padding-bottom: 0.5em;
        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 {
        }
        /**
        * Tests the dummy checkboxes added to the permissions page.
        * Tests the fake checkboxes added to the permissions page.
        */
        public function testPermissionCheckboxes(): void {
        $this->drupalLogin($this->adminUser);
        ......@@ -58,24 +58,25 @@ public function testPermissionCheckboxes(): void {
        $page = $this->getSession()->getPage();
        $wrapper = $page->find('css', '.form-item-' . $this->rid . '-administer-modules');
        $real_checkbox = $wrapper->find('css', '.real-checkbox');
        $dummy_checkbox = $wrapper->find('css', '.dummy-checkbox');
        $fake_checkbox = $wrapper->find('css', '.fake-checkbox');
        // The real per-role checkbox is visible and unchecked, the dummy copy is
        // invisible.
        $this->assertTrue($real_checkbox->isVisible());
        $this->assertFalse($real_checkbox->isChecked());
        $this->assertFalse($dummy_checkbox->isVisible());
        // The real per-role checkbox is visible and unchecked, the fake copy does
        // not exist yet.
        $this->assertNull($fake_checkbox);
        // Enable the permission for all authenticated users.
        $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.
        $this->assertFalse($real_checkbox->isVisible());
        $this->assertTrue($dummy_checkbox->isVisible());
        $this->assertTrue($dummy_checkbox->isChecked());
        $this->assertTrue($dummy_checkbox->hasAttribute('disabled'));
        $this->assertTrue($fake_checkbox->isVisible());
        $this->assertTrue($fake_checkbox->isChecked());
        $this->assertTrue($fake_checkbox->hasAttribute('disabled'));
        }
        }
        ......@@ -13,80 +13,81 @@
        * Attaches functionality to the permissions table.
        */
        Drupal.behaviors.permissions = {
        attach(context) {
        once('permissions', 'table#permissions').forEach((table) => {
        // On a site with many roles and permissions, this behavior initially
        // has to perform thousands of DOM manipulations to inject checkboxes
        // 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.
        const $table = $(table);
        let $ancestor;
        let method;
        if ($table.prev().length) {
        $ancestor = $table.prev();
        method = 'after';
        } else {
        $ancestor = $table.parent();
        method = 'append';
        }
        $table.detach();
        // 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
        // permission table would be polluted with redundant entries. This is
        // deliberate, but desirable when we automatically check them.
        const $dummy = $(Drupal.theme('checkbox'))
        .removeClass('form-checkbox')
        .addClass('dummy-checkbox js-dummy-checkbox')
        .attr('disabled', 'disabled')
        .attr('checked', 'checked')
        .attr(
        'title',
        Drupal.t(
        'This permission is inherited from the authenticated user role.',
        ),
        )
        .hide();
        $table
        .find('input[type="checkbox"]')
        .not('.js-rid-anonymous, .js-rid-authenticated')
        .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);
        });
        },
        attach() {
        const [table] = once('permissions', 'table#permissions');
        if (!table) {
        return;
        }
        // Create fake checkboxes. We use fake 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
        // permission table would be polluted with redundant entries. This is
        // deliberate, but desirable when we automatically check them.
        const $fakeCheckbox = $(Drupal.theme('checkbox'))
        .removeClass('form-checkbox')
        .addClass('fake-checkbox js-fake-checkbox')
        .attr({
        disabled: 'disabled',
        checked: 'checked',
        title: Drupal.t(
        'This permission is inherited from the authenticated user role.',
        ),
        });
        const $wrapper = $('<div></div>').append($fakeCheckbox);
        const fakeCheckboxHtml = $wrapper.html();
        /**
        * Process each table row to create fake checkboxes.
        *
        * @param {object} object
        * @param {HTMLElement} object.target
        */
        function tableRowProcessing({ target }) {
        once('permission-checkbox', target).forEach((checkbox) => {
        checkbox
        .closest('tr')
        .querySelectorAll(
        'input[type="checkbox"]:not(.js-rid-anonymous, .js-rid-authenticated)',
        )
        .forEach((check) => {
        check.classList.add('real-checkbox', 'js-real-checkbox');
        check.insertAdjacentHTML('beforebegin', fakeCheckboxHtml);
        });
        });
        }
        /**
        * Toggles all dummy checkboxes based on the checkboxes' state.
        *
        * If the "authenticated user" checkbox is checked, the checked and disabled
        * checkboxes are shown, the real checkboxes otherwise.
        */
        toggle() {
        const authCheckbox = this;
        const $row = $(this).closest('tr');
        // jQuery performs too many layout calculations for .hide() and .show(),
        // 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' : '';
        });
        $row.find('.js-dummy-checkbox').each(function () {
        this.style.display = authCheckbox.checked ? '' : 'none';
        });
        // An IntersectionObserver object is associated with each of the table
        // rows to activate checkboxes interactively as users scroll the page
        // up or down. This prevents processing all checkboxes on page load.
        const checkedCheckboxObserver = new IntersectionObserver(
        (entries, thisObserver) => {
        entries
        .filter((entry) => entry.isIntersecting)
        .forEach((entry) => {
        tableRowProcessing(entry);
        thisObserver.unobserve(entry.target);
        });
        },
        {
        rootMargin: '50%',
        },
        );
        // 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.
        Finish editing this message first!
        Please register or to comment