Index: includes/ajax.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/ajax.inc,v retrieving revision 1.33 diff -r1.33 ajax.inc 216,218c216,270 < // Automatically extract any 'settings' added via drupal_add_js() and make < // them the first command. < $scripts = drupal_add_js(NULL, NULL); --- > // AJAX responses aren't rendered with html.tpl.php, so we have to call > // drupal_get_css() and drupal_get_js() here, in order to have new files added > // during this request to be loaded by the page. We only want to send back > // files that the page hasn't already loaded, so we implement simple diffing > // logic using array_diff_key(). > foreach (array('css', 'js') as $type) { > // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty, > // since the base page ought to have at least one JS file and one CSS file > // loaded. It probably indicates an error, and rather than making the page > // reload all of the files, instead we return no new files. > if (empty($_POST['ajax_page_state'][$type])) { > $items[$type] = array(); > } > else { > $function = 'drupal_add_' . $type; > $items[$type] = $function(); > drupal_alter($type, $items[$type]); > // @todo Inline CSS and JS items are indexed numerically. These can't be > // reliably diffed with array_diff_key(), since the number can change > // due to factors unrelated to the inline content, so for now, we strip > // the inline items from AJAX responses, and can add support for them > // when drupal_add_css() and drupal_add_js() are changed to using md5() > // or some other hash of the inline content. > foreach ($items[$type] as $key => $item) { > if (is_numeric($key)) { > unset($items[$type][$key]); > } > } > // Ensure that the page doesn't reload what it already has. > $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]); > } > } > > // Render the HTML to load these files, and add AJAX commands to insert this > // HTML in the page. We pass TRUE as second argument to prevent the data from > // being altered again, as we already altered it above. > $styles = drupal_get_css($items['css'], TRUE); > $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); > $scripts_header = drupal_get_js('header', $items['js'], TRUE); > > $extra_commands = array(); > if (!empty($styles)) { > $extra_commands[] = ajax_command_prepend('head', $styles); > } > if (!empty($scripts_header)) { > $extra_commands[] = ajax_command_prepend('head', $scripts_header); > } > if (!empty($scripts_footer)) { > $extra_commands[] = ajax_command_append('body', $scripts_footer); > } > if (!empty($extra_commands)) { > $commands = array_merge($extra_commands, $commands); > } > > $scripts = drupal_add_js(); 220c272,275 < array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $scripts['settings']['data']))); --- > $settings = $scripts['settings']; > // Automatically extract any settings added via drupal_add_js() and make > // them the first command. > array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE)); 309a365,389 > * Theme callback for AJAX requests. > * > * Many different pages can invoke an AJAX request to system/ajax or another > * generic AJAX path. It is almost always desired for an AJAX response to be > * rendered using the same theme as the base page, and it is therefore > * recommended for all AJAX paths to list this function for its 'theme > * callback', as is done in system_menu() for the 'system/ajax' path. > */ > function ajax_base_page_theme() { > if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) { > $theme = $_POST['ajax_page_state']['theme']; > $token = $_POST['ajax_page_state']['theme_token']; > > // Prevent a request forgery from giving a person access to a theme they > // shouldn't be otherwise allowed to see. However, since everyone is allowed > // to see the default theme, token validation isn't required for that, and > // bypassing it allows most use-cases to work even when accessed from the > // page cache. > if ($theme === variable_get('theme_default', 'bartik') || drupal_valid_token($token, $theme)) { > return $theme; > } > } > } > > /** Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1228 diff -r1.1228 common.inc 2824a2825,2828 > * @param $skip_alter > * (optional) If set to TRUE, this function skips calling drupal_alter() on > * $css, useful when the calling function passes a $css array that has already > * been altered. 2828c2832 < function drupal_get_css($css = NULL) { --- > function drupal_get_css($css = NULL, $skip_alter = FALSE) { 2834c2838,2840 < drupal_alter('css', $css); --- > if (!$skip_alter) { > drupal_alter('css', $css); > } 2857a2864,2869 > > // Provide the page with information about the individual CSS files used, > // information not otherwise available when CSS aggregation is enabled. > $setting['ajaxPageState']['css'] = array_fill_keys(array_keys($css), 1); > $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting); > 3793a3806,3809 > * @param $skip_alter > * (optional) If set to TRUE, this function skips calling drupal_alter() on > * $javascript, useful when the calling function passes a $javascript array > * that has already been altered. 3800c3816 < function drupal_get_js($scope = 'header', $javascript = NULL) { --- > function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE) { 3809c3825,3827 < drupal_alter('js', $javascript); --- > if (!$skip_alter) { > drupal_alter('js', $javascript); > } 3813c3831 < foreach ($javascript as $item) { --- > foreach ($javascript as $key => $item) { 3815c3833 < $items[] = $item; --- > $items[$key] = $item; 3847a3866,3881 > // Provide the page with information about the individual JavaScript files > // used, information not otherwise available when aggregation is enabled. > $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1); > unset($setting['ajaxPageState']['js']['settings']); > drupal_add_js($setting, 'setting'); > > // If we're outputting the header scope, then this might be the final time > // that drupal_get_js() is running, so add the setting to this output as well > // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's > // because drupal_get_js() was intentionally passed a $javascript argument > // stripped off settings, potentially in order to override how settings get > // output, so in this case, do not add the setting to this output. > if ($scope == 'header' && isset($items['settings'])) { > $items['settings']['data'][] = $setting; > } > Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.615 diff -r1.615 theme.inc 104a105,113 > > // Provide the page with information about the theme that's used, so that a > // later AJAX request can be rendered using the same theme. > // @see ajax_base_page_theme() > $setting['ajaxPageState'] = array( > 'theme' => $theme_key, > 'themeToken' => drupal_get_token($theme_key), > ); > drupal_add_js($setting, 'setting'); Index: misc/ajax.js =================================================================== RCS file: /cvs/drupal/drupal/misc/ajax.js,v retrieving revision 1.19 diff -r1.19 ajax.js 233a234,244 > // Allow Drupal to return new JavaScript and CSS files to load without > // returning the ones already loaded. > form_values.push({ name: 'ajax_page_state[theme]', value: Drupal.settings.ajaxPageState.theme }); > form_values.push({ name: 'ajax_page_state[theme_token]', value: Drupal.settings.ajaxPageState.themeToken }); > for (var key in Drupal.settings.ajaxPageState.css) { > form_values.push({ name: 'ajax_page_state[css][' + key + ']', value: 1 }); > } > for (var key in Drupal.settings.ajaxPageState.js) { > form_values.push({ name: 'ajax_page_state[js][' + key + ']', value: 1 }); > } > Index: modules/file/file.module =================================================================== RCS file: /cvs/drupal/drupal/modules/file/file.module,v retrieving revision 1.40 diff -r1.40 file.module 43a44 > 'theme callback' => 'ajax_base_page_theme', 49a51 > 'theme callback' => 'ajax_base_page_theme', Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.238 diff -r1.238 drupal_web_test_case.php 2819c2819 < --- > debug($fields); Index: modules/simpletest/tests/ajax.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/ajax.test,v retrieving revision 1.18 diff -r1.18 ajax.test 4a5,6 > protected $profile = 'testing'; > 10c12 < parent::setUp(array_unique(array_merge(array('ajax_test', 'ajax_forms_test'), $modules))); --- > parent::setUp(array_merge(array('ajax_test', 'ajax_forms_test'), $modules)); 14c16 < * Returns the passed-in commands array without the initial settings command. --- > * Search through a settings return; find/return a setting. 16,19c18,23 < * Depending on factors that may be irrelevant to a particular test, < * ajax_render() may prepend a settings command. This function allows the test < * to only have to concern itself with the commands that were passed to < * ajax_render(). --- > * @param $commands > * The AJAX commands returned by the server. > * @param $conditions > * Array of info we're looking to find in the return. > * @param $message > * An assertion message. 21,23c25,48 < protected function discardSettings($commands) { < if ($commands[0]['command'] == 'settings') { < array_shift($commands); --- > protected function assertCommand($json, $conditions, $message) { > //it could be that we really want to test on the first array of the ajax > //handling so we should not remove the settings array > if ($json[0]['command'] == 'settings' && (count($json) > 1)) { > array_shift($json); > } > // @todo Counting success is bad. We have unique expectations. > // counting success should be outside of the foreach to not > // overwrite possible successes > $count_success = 0; > foreach ($json as $item) { > // Can't anticipate ajaxPageState. > if (isset($item['settings']['ajaxPageState'])) { > unset($item['settings']['ajaxPageState']); > } > > // @todo Compare $item to $conditions (rename to $command) to make any sense of this. > foreach ($conditions as $key => $value) { > // @todo This should be a type-agnostic comparison. But let's remove > // this entire inner foreach instead. > if (isset($item[$key]) && $item[$key] == $value) { > $count_success++; > } > } 25c50 < return $commands; --- > $this->assertEqual(count($conditions), $count_success, $message); 45,49c70,88 < $result = $this->drupalGetAJAX('ajax-test/render'); < // Verify that JavaScript settings are contained (always first). < $this->assertIdentical($result[0]['command'], 'settings', t('drupal_add_js() settings are contained first.')); < // Verify that basePath is contained in JavaScript settings. < $this->assertEqual($result[0]['settings']['basePath'], base_path(), t('Base path is contained in JavaScript settings.')); --- > $commands = $this->drupalGetAJAX('ajax-test/render'); > // Verify that there is a command to load settings added with > // drupal_add_js(). > > $result = $this->assertCommand($commands, array( > 'command' => 'settings', > ), t('ajax_render() loads settings added with drupal_add_js().')); > > $result = $this->assertCommand($commands, array( > 'settings' => array('basePath' => base_path(), 'ajax' => 'test'), > ), t('The %setting setting is included.', array('%setting' => 'basePath'))); > > // Verify that our file is included in the list. > $file = file_create_url('misc/drupal.js'); > foreach ($commands as $command) { > if ($command['command'] == 'scripts') { > $this->assertEqual($command['files'][$file], $file, t('The %file file is included.', array('%file' => 'misc/drupal.js'))); > } > } 56c95 < $result = $this->discardSettings($this->drupalGetAJAX('ajax-test/render-error')); --- > $commands = $this->drupalGetAJAX('ajax-test/render-error'); 58,59c97,101 < $this->assertEqual($result[0]['command'], 'alert', t('ajax_render_error() invokes alert command.')); < $this->assertEqual($result[0]['text'], t('An error occurred while handling the request: The server received invalid input.'), t('Default error message is output.')); --- > $result = $this->assertCommand($commands, array( > 'command' => 'alert', > 'text' => t('An error occurred while handling the request: The server received invalid input.'), > ), t('ajax_render_error() invokes alert command.')); > 64,65c106,109 < $result = $this->discardSettings($this->drupalGetAJAX('ajax-test/render-error', array('query' => $edit))); < $this->assertEqual($result[0]['text'], $edit['message'], t('Custom error message is output.')); --- > $commands = $this->drupalGetAJAX('ajax-test/render-error', array('query' => $edit)); > $result = $this->assertCommand($commands, array( > 'text' => $edit['message'], > ), t('Custom error message is output.')); 87,89c131,135 < $result = $this->drupalGetAJAX('ajax-test/render', array('query' => array('commands' => $commands))); < // Verify that JavaScript settings are contained (always first). < $this->assertIdentical($result[0]['command'], 'settings', t('drupal_add_js() settings are contained first.')); --- > > $commands = $this->drupalGetAJAX('ajax-test/render', array( > 'query' => array('commands' => $commands), > )); > 91c137,140 < $this->assertEqual($result[1]['settings']['foo'], 42, t('Custom setting is output.')); --- > $result = $this->assertCommand($commands, array( > 'command' => 'settings', > 'settings' => array('foo' => 42), > ), t('Custom setting is output.')); 105,107c154,159 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'after' && $command['data'] == 'This will be placed after', "'after' AJAX command issued with correct data"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div"))); > $result = $this->assertCommand($commands, array( > 'command' => 'insert', > 'method' => 'after', > 'data' => 'This will be placed after', > ), "'after' AJAX command issued with correct data"); 110,112c162,166 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'alert' && $command['text'] == 'Alert', "'alert' AJAX Command issued with correct text"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert"))); > $result = $this->assertCommand($commands, array( > 'command' => 'alert', > 'text' => 'Alert', > ), "'alert' AJAX Command issued with correct text"); 115,117c169,174 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'append' && $command['data'] == 'Appended text', "'append' AJAX command issued with correct data"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something"))); > $result = $this->assertCommand($commands, array( > 'command' => 'insert', > 'method' => 'append', > 'data' => 'Appended text', > ), "'append' AJAX command issued with correct data"); 120,122c177,182 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'before' && $command['data'] == 'Before text', "'before' AJAX command issued with correct data"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div"))); > $result = $this->assertCommand($commands, array( > 'command' => 'insert', > 'method' => 'before', > 'data' => 'Before text', > ), "'before' AJAX command issued with correct data"); 125,127c185,189 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed.")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div', "'changed' AJAX command issued with correct selector"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed."))); > $result = $this->assertCommand($commands, array( > 'command' => 'changed', > 'selector' => '#changed_div', > ), "'changed' AJAX command issued with correct selector"); 130,132c192,197 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk.")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div' && $command['asterisk'] == '#changed_div_mark_this', "'changed' AJAX command (with asterisk) issued with correct selector"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk."))); > $result = $this->assertCommand($commands, array( > 'command' => 'changed', > 'selector' => '#changed_div', > 'asterisk' => '#changed_div_mark_this', > ), "'changed' AJAX command (with asterisk) issued with correct selector"); 135,137c200,206 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue.")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'css' && $command['selector'] == '#css_div' && $command['argument']['background-color'] == 'blue', "'css' AJAX command issued with correct selector"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue."))); > > $result = $this->assertCommand($commands, array( > 'command' => 'css', > 'selector' => '#css_div', > 'argument' => array('background-color' => 'blue'), > ), "'css' AJAX command issued with correct selector"); 140,142c209,214 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX data command: Issue command.")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'data' && $command['name'] == 'testkey' && $command['value'] == 'testvalue', "'data' AJAX command issued with correct key and value"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX data command: Issue command."))); > $result = $this->assertCommand($commands, array( > 'command' => 'data', > 'name' => 'testkey', > 'value' => 'testvalue', > ), "'data' AJAX command issued with correct key and value"); 145,147c217,222 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector.")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'html' && $command['data'] == 'replacement text', "'html' AJAX command issued with correct data"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector."))); > $result = $this->assertCommand($commands, array( > 'command' => 'insert', > 'method' => 'html', > 'data' => 'replacement text', > ), "'html' AJAX command issued with correct data"); 150,152c225,229 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX insert: Let client insert based on #ajax['method'].")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'insert' && $command['method'] == NULL && $command['data'] == 'insert replacement text', "'insert' AJAX command issued with correct data"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX insert: Let client insert based on #ajax['method']."))); > $result = $this->assertCommand($commands, array( > 'command' => 'insert', > 'data' => 'insert replacement text', > ), "'insert' AJAX command issued with correct data"); 155,157c232,237 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'prepend' && $command['data'] == 'prepended text', "'prepend' AJAX command issued with correct data"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something"))); > $result = $this->assertCommand($commands, array( > 'command' => 'insert', > 'method' => 'prepend', > 'data' => 'prepended text', > ), "'prepend' AJAX command issued with correct data"); 160,162c240,244 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'remove' && $command['selector'] == '#remove_text', "'remove' AJAX command issued with correct command and selector"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text"))); > $result = $this->assertCommand($commands, array( > 'command' => 'remove', > 'selector' => '#remove_text', > ), "'remove' AJAX command issued with correct command and selector"); 165,167c247,251 < $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'restripe' command")))); < $command = $commands[0]; < $this->assertTrue($command['command'] == 'restripe' && $command['selector'] == '#restripe_table', "'restripe' AJAX command issued with correct selector"); --- > $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'restripe' command"))); > $result = $this->assertCommand($commands, array( > 'command' => 'restripe', > 'selector' => '#restripe_table', > ), "'restripe' AJAX command issued with correct selector"); 199,201c283,286 < $commands = $this->discardSettings($this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'select')); < $data_command = $commands[1]; < $this->assertEqual($data_command['value'], $item); --- > $commands = $this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'select'); > $result = $this->assertCommand($commands, array( > 'value' => $item, > ), "verification of AJAX form values from a selectbox issued with a correct value"); 203d287 < 209,211c293,297 < $commands = $this->discardSettings($this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'checkbox')); < $data_command = $commands[1]; < $this->assertEqual((int) $data_command['value'], (int) $item); --- > $commands = $this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'checkbox'); > > $result = $this->assertCommand($commands, array( > 'value' => $item, > ), "verification of AJAX form values from a checkbox issued with a correct value"); 248a335 > 254a342 > 270a359 > 271a361 > 273a364 > //@todo When NULL is given as a value the assertFieldByXPath doesn't work 281c372 < for ($i=0; $i<2; $i++) { --- > for ($i = 0; $i < 2; $i++) { 312d402 < $web_user = $this->drupalCreateUser(); 314d403 < 322d410 < Index: modules/simpletest/tests/ajax_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/ajax_test.module,v retrieving revision 1.3 diff -r1.3 ajax_test.module 34c34,35 < * by invoking drupal_add_js() with a dummy setting. --- > * and files by invoking drupal_add_js() with a dummy setting, which causes > * drupal_add_js() to also automatically add "misc/drupal.js". Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.967 diff -r1.967 system.module 516a517 > 'theme callback' => 'ajax_base_page_theme',