Problem/Motivation

When a page contains a form and a fragment with special characters is in the url, such as http://mysite.com/#badfragment=broken, a javascript error will be thrown: Uncaught Error: Syntax error, unrecognized expression: .error, #badfragment=broken.

This issue happens in form.js near line 300:

    const hash = url.hash.substring(1);
    if (hash) {
      const $target = $(`#${hash}`);

Notice that the anchor is being directly pulled from the URL and used in the selector. However, `#${hash}` will not yield a valid selector, resulting in an error being thrown (breaking all JS on the page).

Steps to reproduce

Add this link to a page <a href="#edit-contact/broken">This is an anchor link with a broken fragment</a> then click it, and the error will appear in the console.

Alternatively visit https://local.site/user/register#badfragment=broken on a site.

Proposed resolution

Provide a helper that converts a fragment into a safe ID that can be used for a jquery or other selector.

Remaining tasks

Need help writing tests that assert no console errors visiting form urls with complex fragments?
Review

User interface changes

n/a

API changes

n/a

Data model changes

n/a

Release notes snippet

n/a

Issue fork drupal-2395065

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

rooby’s picture

A work around that might work for your case is this:

Instead of

if ($fieldset.find('.error' + anchor).length) {
  $fieldset.removeClass('collapsed');
}

Use this

      if (anchor.indexOf('?') == -1) {
        // Default behavior.
        if ($fieldset.find('.error' + anchor).length) {
          $fieldset.removeClass('collapsed');
        }
      }
      else {
        // Exclude the anchor if it contains a query string because otherwise
        // jQuery errrors out in IE.
        if ($fieldset.find('.error').length) {
          $fieldset.removeClass('collapsed');
        }
      }
samhassell’s picture

This conflict comes up when using AngularJS's $location service within D7.

Sample URL:

http://mysite.com/map#?station=8005&soil=3&groundCover=1
pol’s picture

Same here too.

pol’s picture

In misc/collapse.js, replace:

if ($fieldset.find('.error' + anchor).length) {

With:

      if ($fieldset.find('.error' + anchor.replace(/[=%;,\/]/g, "_")).length) {

If I get good feedback, I'll propose a patch for D7 core.

pol’s picture

Status: Active » Needs review
StatusFileSize
new594 bytes

Here's the patch.

tim.plunkett’s picture

Version: 7.x-dev » 8.0.x-dev
Status: Needs review » Needs work
Issue tags: +Needs backport to D7

This code is unchanged in D8, meaning that it should probably be fixed there first.

pol’s picture

Status: Needs work » Needs review
StatusFileSize
new607 bytes

Here's the patch for D8.

longwave’s picture

Is [=%;,\/] the full set of invalid characters, or could there be others? If a replacement takes place, will it ever match anything useful, or match something else by mistake?

Maybe this is more readable/useful:

if (anchor.search(/[=%;,\/]/) == -1 && ...) {

or

if (anchor.search(/^[valid characters]*$/) != -1 && ...) {

markhalliwell’s picture

Component: forms system » javascript
Status: Needs review » Needs work
Issue tags: +JavaScript

Is [=%;,\/]the full set of invalid characters, or could there be others?

There could be others.

I would much rather the route of checking for expected/valid characters as is done in
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Util...

That being said, I would imagine that we could abstract this and just add it to drupal.js as a utilitarian method:

Drupal.cleanId = function (input) {
  if (typeof input !== 'string') return input;
  // Replace certain characters with hyphens and lowercase.
  var output = input.replace('/[\s_\[]', '-').toLowerCase();
  // Strip all non-alphanumeric characters.
  output = output.replace(/[^A-Za-z0-9\-_]/, '');
  // Removing multiple consecutive hyphens.
  return output.replace(/\-+/, '-');
};

Then we could just do something like:

var anchor = Drupal.cleanId(location.hash && location.hash !== '#' ? ', ' + location.hash : '');

sgdev’s picture

Thank you for everyone's work on this issue. A more generic approach is definitely the way to go. We ran into this problem with AddThis. AddThis adds anchor tags on URLs when recommended content links are clicked. The rendered URL displays something like the following:

http://example.com/node/25#at_pco=smlwd-1.0&at_si=5622dc8ca8da4ad0&at_ac=per-2&at_pos=0&at_tot=1

The #5 patch appropriately handles the equal character ("="), but does not replace the ampersand ("&"). The result is a jQuery error:

Uncaught Error: Syntax error, unrecognized expression: .error_ #at_pco=smlwd-1.0&at_si=5622dc8ca8da4ad0&at_ac=per-2&at_pos=0&at_tot=1 (jquery.min.js:2)

If we add "&" to the possible characters in Patch #5 it works correctly. Therefore a patch that handles any possible special symbol would be the best method.

David_Rothstein’s picture

I just marked #2651230: collapse.js breaks when the hash contains jQuery BBQ-like query string as duplicate. There is a patch in that issue also.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

markhalliwell’s picture

Version: 8.4.x-dev » 8.5.x-dev

Bump, re: #9

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

geek-merlin’s picture

Title: Certain URL fragments cause javascript error when collapsible fieldset is present on page » Certain URL fragments cause javascript error

This issue always arises when hashes are not escaped. I have been facing it when blazy_photoswipe generated URLs like "foo#&gid=1&pid=2", and form.js's handleFragmentLinkClickOrHashChange() (see #2531700: Fragment links to children elements in closed grouping elements don't work) coughed on that:

jquery.min.js?v=3.2.1:2 Uncaught Error: Syntax error, unrecognized expression: #&gid=5&pid=1
    at Function.ga.error (jquery.min.js?v=3.2.1:2)
    at ga.tokenize (jquery.min.js?v=3.2.1:2)
    at ga.select (jquery.min.js?v=3.2.1:2)
    at Function.ga [as find] (jquery.min.js?v=3.2.1:2)
    at r.fn.init.find (jquery.min.js?v=3.2.1:2)
    at new r.fn.init (jquery.min.js?v=3.2.1:2)
    at r (jquery.min.js?v=3.2.1:2)
    at handleFragmentLinkClickOrHashChange (form.js?v=8.5.3:137)
    at debounce.js?v=8.5.3:27
    at dispatch (jquery.min.js?v=3.2.1:3)

the offending line in form.js:137 is

      var $target = $('#' + hash);

and replacing that (like in the patch here) with

      var $target = $('#' + hash.replace(/[^0-9a-zA-Z]/g, '\\$&'));

fixes the issue for me.

For a thorough fix we should implement a utility method as suggested in #9 and use it in all such instances.

For the correct utility method see:
* http://api.jquery.com/category/selectors/ ("meta characters...must be escaped")
* https://stackoverflow.com/a/32872718/606859
* https://github.com/mathiasbynens/CSS.escape/blob/master/css.escape.js#L21

The last one contains a MIT licensed function that polyfills CSSCOM's CSS.escape which is what we want here.

batkor’s picture

#19 +.
I'm get error in form.js
Axel.rutz Ty

batkor’s picture

StatusFileSize
new857 bytes

patch for form.js

andypost’s picture

Status: Needs work » Needs review

As bug it needs test but let's see current coverage

batkor’s picture

i think #9 true.
And use for form.js, collapse.js

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

aklump’s picture

Thank you, batkor, #21 patch worked for me. 8.6.10.

bmunslow’s picture

Patch #21 worked for me, thanks @baktor.

markhalliwell’s picture

Status: Needs review » Needs work

See #9

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

neclimdul’s picture

Status: Needs work » Needs review
Issue tags: -JavaScript +JavaScript
StatusFileSize
new3.65 KB
new3.96 KB

Ugh, ran into this randomly breaking a embedded Vue SPA.

#!/my/route<code> blows this up real good.

<code>
+++ b/core/misc/form.js
@@ -134,7 +134,7 @@
-      var $target = $('#' + hash);
+      var $target = $('#' + hash.replace(/[^0-9a-zA-Z]/g, '\\$&'));
       $('body').trigger('formFragmentLinkClickOrHashChange', [$target]);

Similar I think to longwave's comment about earlier patch I think but it doesn't make sense that this triggers when there is no match. It wouldn't really be actionable by any listeners without a target would it? That's a bigger change though and just and optimization.

I took a pass at implementing Mark's helper since that makes sense with 2 locations. With 2 there's probably a third. His sample looked correct so I didn't really change anything other than some small cleanups:
1. chaining it
2. replaced the first regex with a native regex
3. added global flag to regex because they where only matching the first instance.

I don't have a lot of experience writing core's JS tests so those aren't included but as a first pass lets see if this breaks anything.

Had to reroll because of babel changes(+ to .concat) and that's not included in interdiff. Also didn't interdiff against the previous collapse patch since it was not on the es6.js file and that meant more work but changes should be obvious.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

neclimdul’s picture

StatusFileSize
new652 bytes
new3.65 KB

Reviewing some old patches I noticed some doc/spacing problems in the last patch. Quick fix.

neclimdul’s picture

StatusFileSize
new1.91 KB
new3.68 KB

eslint

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

callen321’s picture

StatusFileSize
new3.84 KB

removed

callen321’s picture

StatusFileSize
new0 bytes

removed

callen321’s picture

StatusFileSize
new3.84 KB

Rerolled for 9.5

callen321’s picture

neclimdul’s picture

can't rtbc what's basically my own patch but we've been using this in production for _ages_ so it should be good to go.

geek-merlin’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

We fix a bug, so needs a red test that triggers the bug and is fixed by the patch.

quietone’s picture

Issue summary: View changes
Issue tags: +Needs issue summary update

This also needs an issue summary update. The proposed resolution should explain what solution is being implemented.

longwave’s picture

Issue tags: +Needs reroll

Also needs a reroll for 10.x. Bug fixes are usually eligible for backport but in this case we are adding API so this is likely to land in the development branch only.

_utsavsharma’s picture

StatusFileSize
new1.04 KB
new1.04 KB

Patch for 10.1.x.

sahil.goyal’s picture

Status: Needs work » Needs review
longwave’s picture

Status: Needs review » Needs work
Issue tags: -Needs reroll

Thanks for rerolling. This still needs a test to exercise the broken behaviour and prove that we have fixed it with the patch.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

neclimdul’s picture

conflicts with #3419763: Replace deprecated String.prototype.substr() with String.prototype.substring(). rerolled in a merge request. not sure what a test would look like so just the reroll atm.

adinancenci’s picture

StatusFileSize
new1.04 KB

Patch 46 adapted for Drupal 10.2.4.

jmagunia’s picture

The patch from #52 applied cleanly to Drupal 10.2.5.

yash.rode made their first commit to this issue’s fork.

yash.rode’s picture

Hi, I was trying to reproduce this issue in Drupal Core, where I can click on any button which takes me to the link with a fragment?
I am trying to write the test, but not sure what should be the Drupal page where I can get this error which I can convert to test.

neclimdul’s picture

Not sure of a common real case but I was able recreate this just now like this.

1. Install Drupal site.
2. Ensure registration is allowed.
3. Log out and visit the registration url with a complex url fragment. e.g. https://d10.lndo.site/en/user/register/#badfragment/broken

yash.rode’s picture

Hi I tried the steps above but the issue only occurs when we try to change the URL and not when we visit the URL for the first time?
If that is the expected behaviour how can we write a test for that scenario?

bnjmnm’s picture

Testing / reproducing is fortunately pretty simple. The callback where the problem is occuring is triggered by, among other things, clicking on an anchor link inside a form. Get this link into a form <a href="#edit-contact/broken">This is an anchor link with a broken fragment</a> then click it, and the error will appear in the console.

And hopefully the issue summary can get updated soon as it is presents the issue as being specific to a file that has not been in Drupal for several years.

neclimdul’s picture

Issue summary: View changes

Thanks, yeah adding that link would be a simple way someone could see it.

Anyone could update issue summaries.

utkarsh_33’s picture

I tried reproducing this based on #58.
I added

$form['anchor_link'] = [
      '#type' => 'markup',
      '#markup' => '<a href="/#edit-contact/broken">This is an anchor link with a broken fragment</a>',
    ];

in FormTestUrlForm form and clicked on the anchor link inside the form.
This does not lead to any error on 11.x.Also it tried to edit the url and hit the url as mentioned in #57 by @yash.rode but then also there is no error in the console.
Also when following the same steps as mentioned by @yash.rode in #57 I was able to reproduce the same error as yash was able to get.

The question here is am I missing something is steps to reproduce?

utkarsh_33’s picture

Status: Needs work » Needs review
neclimdul’s picture

Status: Needs review » Needs work

I think this is documented and reviewed to death at this point. Its actually needs work because it needs tests as the tag suggests.

bnjmnm’s picture

Re #60

The question here is am I missing something is steps to reproduce?

Yes. The file where the error is happening needs to be loaded in order to reproduce the problem.

The error is occurring in form.js, but the form loaded by FormTestUrlForm does not include that file.

See core.libraries.yml for the library that includes this file and the libraries that depend on it.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

budda’s picture

11 years ... wow, and still not resolved. 🙄

Is there any reason why jQuery.escapeSelector() wasn't used insteadof the cleanId function @Utkarsh_33 ?

https://api.jquery.com/jQuery.escapeSelector/