Commit f3da649b authored by catch's avatar catch
Browse files

Issue #967166 by effulgentsia, Cottser: Fixed Content rendered via AJAX does...

Issue #967166 by effulgentsia, Cottser: Fixed Content rendered via AJAX does not respect stylesheets removed in .info files.
parent ef7f375d
......@@ -2966,6 +2966,18 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) {
// Sort CSS items, so that they appear in the correct order.
uasort($css, 'drupal_sort_css_js');
// Provide the page with information about the individual CSS files used,
// information not otherwise available when CSS aggregation is enabled. The
// setting is attached later in this function, but is set here, so that CSS
// files removed below are still considered "used" and prevented from being
// added in a later AJAX request.
// Skip if no files were added to the page or jQuery.extend() will overwrite
// the Drupal.settings.ajaxPageState.css object with an empty array.
if (!empty($css)) {
// Cast the array to an object to be on the safe side even if not empty.
$setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
}
// Remove the overridden CSS files. Later CSS files override former ones.
$previous_item = array();
foreach ($css as $key => $item) {
......@@ -2985,14 +2997,7 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) {
'#type' => 'styles',
'#items' => $css,
);
// Provide the page with information about the individual CSS files used,
// information not otherwise available when CSS aggregation is enabled.
// Skip if no files were added to the page or jQuery.extend() will overwrite
// the Drupal.settings.ajaxPageState.css object with an empty array.
// Cast the array to an object to be on the safe side even if not empty.
if (!empty($css)) {
$setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
if (!empty($setting)) {
$styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
}
......
......@@ -2083,6 +2083,8 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
// them.
$dom = new DOMDocument();
@$dom->loadHTML($content);
// XPath allows for finding wrapper nodes better than DOM does.
$xpath = new DOMXPath($dom);
foreach ($return as $command) {
switch ($command['command']) {
case 'settings':
......@@ -2090,52 +2092,52 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
break;
case 'insert':
// @todo ajax.js can process commands that include a 'selector', but
// these are hard to emulate with DOMDocument. For now, we only
// implement 'insert' commands that use $ajax_settings['wrapper'].
$wrapperNode = NULL;
// When a command doesn't specify a selector, use the
// #ajax['wrapper'] which is always an HTML ID.
if (!isset($command['selector'])) {
// $dom->getElementById() doesn't work when drupalPostAJAX() is
// invoked multiple times for a page, so use XPath instead. This
// also sets us up for adding support for $command['selector'] in
// the future, once we figure out how to transform a jQuery
// selector to XPath.
$xpath = new DOMXPath($dom);
$wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0);
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
$newDom = new DOMDocument();
$newDom->loadHTML('<div>' . $command['data'] . '</div>');
$newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate
// each one using PHP's DOMNode API.
switch ($method) {
case 'replaceWith':
$wrapperNode->parentNode->replaceChild($newNode, $wrapperNode);
break;
case 'append':
$wrapperNode->appendChild($newNode);
break;
case 'prepend':
// If no firstChild, insertBefore() falls back to
// appendChild().
$wrapperNode->insertBefore($newNode, $wrapperNode->firstChild);
break;
case 'before':
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode);
break;
case 'after':
// If no nextSibling, insertBefore() falls back to
// appendChild().
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling);
break;
case 'html':
foreach ($wrapperNode->childNodes as $childNode) {
$wrapperNode->removeChild($childNode);
}
$wrapperNode->appendChild($newNode);
break;
}
}
// @todo Ajax commands can target any jQuery selector, but these are
// hard to fully emulate with XPath. For now, just handle 'head'
// and 'body', since these are used by ajax_render().
elseif (in_array($command['selector'], array('head', 'body'))) {
$wrapperNode = $xpath->query('//' . $command['selector'])->item(0);
}
if ($wrapperNode) {
// ajax.js adds an enclosing DIV to work around a Safari bug.
$newDom = new DOMDocument();
$newDom->loadHTML('<div>' . $command['data'] . '</div>');
$newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
$method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
// The "method" is a jQuery DOM manipulation function. Emulate
// each one using PHP's DOMNode API.
switch ($method) {
case 'replaceWith':
$wrapperNode->parentNode->replaceChild($newNode, $wrapperNode);
break;
case 'append':
$wrapperNode->appendChild($newNode);
break;
case 'prepend':
// If no firstChild, insertBefore() falls back to
// appendChild().
$wrapperNode->insertBefore($newNode, $wrapperNode->firstChild);
break;
case 'before':
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode);
break;
case 'after':
// If no nextSibling, insertBefore() falls back to
// appendChild().
$wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling);
break;
case 'html':
foreach ($wrapperNode->childNodes as $childNode) {
$wrapperNode->removeChild($childNode);
}
$wrapperNode->appendChild($newNode);
break;
}
}
break;
......
......@@ -195,6 +195,27 @@ class AJAXFrameworkTestCase extends AJAXTestCase {
$this->assertEqual($new_js, $original_js + array($expected['js'] => 1), t('Page state now has the %js file.', array('%js' => $expected['js'])));
$this->assertCommand($commands, array('data' => $expected_js_html), t('Page now has the %js file.', array('%js' => $expected['js'])));
}
/**
* Tests that overridden CSS files are not added during lazy load.
*/
function testLazyLoadOverriddenCSS() {
// The test theme overrides system.base.css without an implementation,
// thereby removing it.
theme_enable(array('test_theme'));
variable_set('theme_default', 'test_theme');
// This gets the form, and emulates an Ajax submission on it, including
// adding markup to the HEAD and BODY for any lazy loaded JS/CSS files.
$this->drupalPostAJAX('ajax_forms_test_lazy_load_form', array('add_files' => TRUE), array('op' => t('Submit')));
// Verify that the resulting HTML does not load the overridden CSS file.
// We add a "?" to the assertion, because Drupal.settings may include
// information about the file; we only really care about whether it appears
// in a LINK or STYLE tag, for which Drupal always adds a query string for
// cache control.
$this->assertNoText('system.base.css?', 'Ajax lazy loading does not add overridden CSS files.');
}
}
/**
......
......@@ -31,6 +31,14 @@ function ajax_test_menu() {
return $items;
}
/**
* Implements hook_system_theme_info().
*/
function ajax_test_system_theme_info() {
$themes['test_theme'] = drupal_get_path('module', 'ajax_test') . '/themes/test_theme/test_theme.info';
return $themes;
}
/**
* Menu callback; Return an element suitable for use by ajax_deliver().
*
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment