esc, loading stops猬嗭笍 this is how regular pages work, but pjax doesn't listen to esc
IssueHunt Summary
stop-pjax-loading-with-esc featureSounds good.
@issuehunt has funded $40.00 to this issue.
pjax doesn't seem to offer a Cancel/Abort action.
To implement this you'd have to come up with some hack. Hope someone can think of something.
It looks like the pjax on GitHub doesn't return the xhr with pjax:beforeSend.
$(document).on("pjax:beforeSend",function(){console.log(this, arguments);});
$("#js-repo-pjax-container").on("pjax:beforeSend",function(){console.log(this, arguments);});
document.getElementById("js-repo-pjax-container").addEventListener("pjax:beforeSend",function(){console.log(this, arguments);});
document.addEventListener("pjax:beforeSend",function(){console.log(this, arguments);});
I'm not sure if https://github.com/defunkt/jquery-pjax is the one GitHub is using, because most of the events are missing from GitHub's JS files.
So, I came up with a hack using AbortController, that involves replacing fetch at global level.
const actualFetch = self.fetch; // Saving original fetch for later use
const controller = new AbortController();
// Custom fetch, replacing `AbortSignal` on request object
self.fetch = async (request) => {
console.log(request);
const newRequest = new Request(request, {signal: controller.signal}); // Create new request, with our own signal
try {
return actualFetch(newRequest);
} catch (error) {
console.error(error);
}
};
// Handler to abort fetch
window.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
console.log('Aborting...');
controller.abort();
}
});
One catch with the above snippet is that after the fetch is aborted, the user is directly redirected to the target page, which is no the intended behavior, we need the user to stay on the current page.
I think we can manage this using as very clever combination of mousedown, click, and mouseup event handlers and using stopPropagation() if the click actually caused that aborted fetch (haven't tested that part though).
replacing
fetchat global level
Make sure that this actually works because last time I tried to do that in the content script it failed in Firefox, but now I'm testing in the console and it appears to work:

When testing, keep in mind that this replacing code has to be injected _unsafely_ in the main page like resolve-conflicts because content scripts are in a different realm.
Additionally, I think that GitHub will actually try to reload the page when the fetch fails, so esc would have to be pressed twice: once to stop the fetch and again to stop the reload.
Hey @fregante is this still an active issue?
It鈥檚 still active, but I don鈥檛 think it鈥檚 solvable. Do you want to try?
Has anyone sent feedback to GitHub? If not I'm going to make one 馃榿
I can try, but in my firefox and chrome, I'm able to ESC out of loading issues, but only issues, is this the same for you guys?
I'm able to ESC out of loading issues, but only issues
I think that's the regular ESC feature of the browser because they are not ajaxed pages. That's what this issue is about: ajaxed pages don't support ESC
Has anyone sent feedback to GitHub? If not I'm going to make one 馃榿
Please do!
@fregante
One hack coming right up, we can do this with webextensions like the following:
const ajaxRequests = new Set<string>();
const cancelRequests = new Set<string>()
browser.commands.onCommand.addListener((command) => {
if (command === 'stop-request') {
cancelRequests.clear();
Array.from(ajaxRequests).forEach((ajaxUrl) => {
const requestedUrl = ajaxUrl.match(/^(.*)[?&]_pjax=.*$/i)[1];
if (requestedUrl) {
cancelRequests.add(ajaxUrl);
cancelRequests.add(requestedUrl);
}
})
}
})
browser.webRequest.onBeforeRequest.addListener((details) => {
ajaxRequests.add(details.url);
}, { urls: ['https://github.com/*?*_pjax=*'], types: ['xmlhttprequest'] });
browser.webRequest.onHeadersReceived.addListener((details) => {
ajaxRequests.delete(details.url);
}, { urls: ['https://github.com/*?*_pjax=*'], types: ['xmlhttprequest'] });
browser.webRequest.onHeadersReceived.addListener((details) => {
if (cancelRequests.has(details.url)) {
cancelRequests.delete(details.url);
return { cancel: true };
}
}, { urls: ['https://github.com/*'], types: ['main_frame', 'xmlhttprequest'] }, ['blocking']);
How this works is such that all ajax requests are tracked between the request being made and the headers being recieved.
When an interrupt is called, all requests that are tracked will be canceled.
This particular example is a background script only example, to cancel on esc involves making a content script that communicates with this background script as background scripts are limited in what keys they can intercept.
The gotcha here is that pjax becomes confused and some html content seems to lose their formatting. I've tried a redirect on ajax requests instead and this works (code is below), but content is rerendered (even through the page does not switch).
browser.webRequest.onHeadersReceived.addListener((details) => {
if (cancelRequests.has(details.url)) {
cancelRequests.delete(details.url);
if (details.type === 'main_frame') {
return { cancel: true };
}
else if (details.type === 'xmlhttprequest') {
return { redirectUrl: details.originUrl };
}
}
}, { urls: ['https://github.com/*'], types: ['main_frame', 'xmlhttprequest'] }, ['blocking']);
I also tried out @notlmn 's solution, which seems to have weird behaviour, but it triggers the same rendering bug as mine. Additionally, I think if I fuse our solutions we could have something that doesn't need to track ajax requests if you prefer it that way.
I don't believe we have access to browser.webRequest, if I'm not wrong isn't it a new permission we need to ask the user for?
Yeah, there are two permissions needed here webRequest and webRequestBlocking
Yeah, there are two permissions needed here
webRequestandwebRequestBlocking
Yeah, we wouldn't really want to add more permissions if we could do it any other way.
Also how is cancelling requests using browser.webRequest different from signalling using AbortController?
It's not different in cancellation, but the other method of the self redirect is possible through webRequests only.
The main advantage is not in cancelling the ajax request, as you've already shown that to be possible, but in cancelling the page load afterwards that pjax falls back on.
Was that the reason why you didn't continue with your fix? Or was there some other complication I'm not aware of?
I'm confused as to why this might be unsolvable given there are solutions to cancel requests, it's just whether or not they fit the scope of this plugin.
Yeah, there are two permissions needed here
webRequestandwebRequestBlocking
We can't add those 2 permissions by default, which means we'd have to have a UI for this specific feature to request them (browser.permission.request). For example it could append a message like
ESC won't cancel ajaxed loads, but `xyz-feature` can if you add extra permissions: [Add]
If your solution works, feel free to send a PR, keeping in mind that 馃憞
some html content seems to lose their formatting
content is rerendered
馃憜 these are probably deal-breakers. The point of this feature would be to avoid changing page; For example if you're not down leaving a review comment, a rerendering would still lose the comment.
That makes sense, then the cancellation method is more in line with what you want.
With that method, what I've seen is two issues:
If these two issues are not acceptable then I guess it really can't be approached from outside of github code, since both my method and the fetch replacement produce these errors
- The profile page's username is not rendered properly after cancelling load on the profile page
This might be due to some _unload_ handler that we cannot prevent. We can consider these separately, like if it breaks profiles, we can disable it there, as long as it doesn't break _almost everywhere_
- DevTools no longer shows HTML after cancelling load
Not a problem, GitHub users generally don't need to open the devtools.
Perhaps you can send a minimal PR to test it out
Wow, that pull request by @artusm is an elegant solution. I think that is your best bet.
To think that pjax event hooks were right under our noses 馃槅
From "impossible" to "done"
Thanks @artusm!
@sindresorhus has rewarded $36.00 to @artusm. See it on IssueHunt
Most helpful comment
I'm not sure if https://github.com/defunkt/jquery-pjax is the one GitHub is using, because most of the events are missing from GitHub's JS files.
So, I came up with a hack using
AbortController, that involves replacingfetchat global level.One catch with the above snippet is that after the fetch is aborted, the user is directly redirected to the target page, which is no the intended behavior, we need the user to stay on the current page.
I think we can manage this using as very clever combination of
mousedown,click, andmouseupevent handlers and usingstopPropagation()if the click actually caused that aborted fetch (haven't tested that part though).