Skip to content
Snippets Groups Projects
Verified Commit b80bfda1 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
parent 377f078c
No related branches found
No related tags found
20 merge requests!11131[10.4.x-only-DO-NOT-MERGE]: Issue ##2842525 Ajax attached to Views exposed filter form does not trigger callbacks,!9470[10.3.x-only-DO-NOT-MERGE]: #3331771 Fix file_get_contents(): Passing null to parameter,!8736Update the Documention As per the Function uses.,!8513Issue #3453786: DefaultSelection should document why values for target_bundles NULL and [] behave as they do,!3878Removed unused condition head title for views,!3818Issue #2140179: $entity->original gets stale between updates,!3742Issue #3328429: Create item list field formatter for displaying ordered and unordered lists,!3731Claro: role=button on status report items,!3154Fixes #2987987 - CSRF token validation broken on routes with optional parameters.,!3133core/modules/system/css/components/hidden.module.css,!2964Issue #2865710 : Dependencies from only one instance of a widget are used in display modes,!2812Issue #3312049: [Followup] Fix Drupal.Commenting.FunctionComment.MissingReturnType returns for NULL,!2378Issue #2875033: Optimize joins and table selection in SQL entity query implementation,!2062Issue #3246454: Add weekly granularity to views date sort,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!877Issue #2708101: Default value for link text is not saved,!617Issue #3043725: Provide a Entity Handler for user cancelation,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493
Pipeline #273271 passed with warnings
Pipeline: drupal

#273291

    Pipeline: drupal

    #273285

      Pipeline: drupal

      #273274

        ......@@ -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