Skip to content
Snippets Groups Projects
Verified Commit b586e902 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 bd53cc0b
No related branches found
No related tags found
17 merge requests!10663Issue #3495778: Update phpdoc in FileSaveHtaccessLoggingTest,!10451Issue #3472458 by watergate, smustgrave: CKEditor 5 show blocks label is not translated,!103032838547 Fix punctuation rules for inline label suffix colon with CSS only,!10150Issue #3467294 by quietone, nod_, smustgrave, catch, longwave: Change string...,!10130Resolve #3480321 "Second level menu",!9936Issue #3483087: Check the module:// prefix in the translation server path and replace it with the actual module path,!9933Issue #3394728 by ankondrat4: Undefined array key "#prefix" and deprecated function: explode() in Drupal\file\Element\ManagedFile::uploadAjaxCallback(),!9914Issue #3451136 by quietone, gapple, ghost of drupal past: Improve...,!9882Draft: Issue #3481777 In bulk_form ensure the triggering element is the bulk_form button,!9839Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9815Issue #3480025: There is no way to remove entity cache items,!9757Issue #3478869 Add "All" or overview links to parent links,!9752Issue #3439910 by pooja_sharma, vensires: Fix Toolbar tests that rely on UID1's super user behavior,!9749Issue #3439910 by pooja_sharma, vensires: Fix Toolbar tests that rely on UID1's super user behavior,!9678Issue #3465132 by catch, Spokje, nod_: Show test run time by class in run-tests.sh output,!9578Issue #3304746 by scott_euser, casey, smustgrave: BigPipe cannot handle (GET)...,!9449Issue #3344041: Allow textarea widgets to be used for text (formatted) fields
Pipeline #274158 canceled
Pipeline: drupal

#274160

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