onhashchange not working within in-app browsers (Facebook, Twiiter) - javascript

I'm using the onhashchange window event to detect the url hash change for my single page webapp. This enables me to fire AJAX, while retaining the browser history.
User clicks anchor with href="#hashlink".
onhashchange detects the URL updating.
#hashlink is extracted and passed in as AJAX url (/partials/hashlink.php).
I have discovered an issue. You may already be aware, but Facebook and Twitter have started launching external links within an in-app browser. It seems to prevent the default action of page anchors href, which has killed my hash change detection. Thus my webapp is pretty much useless :-(
The in-app browser for Facebook and Twitter were only released very recently, so finding a solution is proving to be difficult.
Thanks in advance!

I know this is an old question, but I ran into the same problem yesterday. Was not able to find a solution that allowed me to keep my use of hash links and the onhashchange event. I rewrote my code to use the History API, which is widely supported (caniuse says it works for every browser except Opera Mini, but when I tested, it worked for me there, too).
Step-by-step:
turn all hash links into buttons, or some other accessible format (div with a role="link" attribute, etc). (don't forget to include an aria-label attribute if the buttons don't contain text clearly stating their function).
<button class="nav__list-item" data-id="${p.websafeHandle}" aria-label="Read our Q & A with ${p.name}">
<div class="nav__list-item__img" style="background-image: url(${p.thumbnail})"></div>
<span class="nav__list-item__handle">${p.handle}</span>
</button>
add click event listener
const qaLinks = document.querySelectorAll('.nav__list-item')
qaLinks.forEach(link => {
link.addEventListener('click', (e) => this.loadQA(e.currentTarget.dataset.id, true))
})
click event handler to update page content and browser history
loadQA(person, updateHistory) {
// I'm condensing/summarizing the parts of my code that deal with fetching data and
// outputting it on the page, because it's not particularly relevant to this question.
const data = this.data[person]
this.container.innerHTML = template(Templates.qaPage, data.qa)
// the loadQA function will also be called if somebody comes to this page
// with a hash link like the one below. in that case (on an initial visit,
// instead of navigation within my single-page app), I wouldn't want to add
// the page to the history again, thereby creating a double entry,
// which is why I have the updateHistory param
if (updateHistory) {
// update our page title and our URL and add it to the browser history
const title = `MTI - ${data.info.name}`
const hash = `#/qa/${data.info.websafeHandle}`
history.pushState(null, title, hash)
}
}
event handler that will fire whenever someone uses their browser's forward/back buttons
window.addEventListener('popstate', () => this.loadPage())
in the popstate event handler (which is also run on initial page load), get the URL hash and load the page accordingly
loadPage() {
if (!window.location.hash) {
// the param here is for updateHistory
this.showLanding(false)
} else {
const person = this.getHashParam()
person ? this.loadQA(person, false) : this.showLanding(true)
}
}
Side note: I found this app to be really helpful for testing my local code in Facebook's IAB. You give it a link (e.g. to your dev site), it generates a link (you have to use the xip.io one to be able to open it in FB), gives you a QR code to scan with your phone, which connects your phone to its dev tools and opens that link in your browser. then you can copy the link, post it in a private FB post only you can see, and voila! you can visit your local server in the Facebook browser, and Ghostlab gives you access to all the dev tools you'd normally have in Chrome.

Related

Prevent initial webRequest prior to redirecting new tab/window only when opened by user clicking link

I am working on a Chrome extension. What I would like to accomplish is redirecting hyperlinks that are opened in a new window or new tab. I've experimented with the code below and while this does redirect the tab it does not prevent the original page request from being submitted which is something I would also like to accomplish.
chrome.webNavigation.onCreatedNavigationTarget.addListener(function(details) {
chrome.tabs.update(details.tabId, {
url: 'http://www.google.com/'
});
});
I only want to redirect if the user opens a hyperlink in a new window (e.g. shift/ctrl+click, middle click, context menu, etc.). I do not want to redirect if the window, or tab, is being opened for a reason other than those.
Redirect new tabs/windows opened by only links, but not directly
Unfortunately, without a content script in every page, you can not differentiate between a user clicking a link and other reasons for opening a new tab or window, prior to the webRequest being transmitted.
The type of webNavigation that is causing the tab or window to be opened is clearly indicated by the value of the transitionType property, in the details supplied to a webNavigation.onCommitted listener. If it was from the user clicking a link, the transitionType property will have the value of link. If the request is from a link is not information that is normally available to the background page prior to the webNavigation.onCommitted event.
Unfortunately, for what you desire, the webNavigation.onCommitted event fires after the webRequest for the page's URL is complete. Thus, without some way to know earlier that the transition is the result of a user clicking a link (e.g. using a content script), you can't know that the current transition is the result of the user clicking a link in time to choose to redirect the webRequest for the main URL of the page.
What you can do is always redirect the initial request to about:blank. Then, once you get the webNavigation.onCommitted event, you can make the choice, based on the value of the transitionType property, to change the tab's URL to the redirect URL you ultimately have in mind, or change it back to the URL which was the original intended page. This process will result in the loss of the Referer header representing the page on which the link was clicked.
Obviously, you could use your ultimate destination instead of about:blank. This may be better, but will result in a webRequest to that URL even if the tab ultimately ends up being put back to the original destination URL.
Here is code that will do what is described above:
background.js
var tabsBlockedOnce = new Set();
var tabsRedirected = new Map();
chrome.webRequest.onBeforeRequest.addListener(function(details){
if(!tabsBlockedOnce.has(details.tabId)){
tabsBlockedOnce.add(details.tabId);
tabsRedirected.set(details.tabId,details.url);
//Redirect
return {redirectUrl:'about:blank'};
//Block
//return {cancel:true};
}
},{urls:['<all_urls>'],types:['main_frame']},['blocking']);
chrome.webNavigation.onCommitted.addListener(function(details){
if(tabsRedirected.has(details.tabId)){
//Default is to not redirect
let url = tabsRedirected.get(details.tabId);
tabsRedirected.delete(details.tabId);
if(details.transitionType === 'link'){
//It was a link, go to where we want to redirect.
url = 'http://www.google.com/';
}
//Send the tab where it is supposed to go
chrome.tabs.update(details.tabId,{url:url});
}
});
//Don't block the first request in any tab that already exists.
// This is of primary benefit when the extension is first installed/reloaded.
chrome.tabs.query({},function(tabs){
tabs.forEach(function(tab){
tabsBlockedOnce.add(tab.id);
});
});
manifest.json (partial):
"permissions": [
"webNavigation",
"webRequest",
"webRequestBlocking"
],
"background": {
"scripts": ["background.js"]
}
With a content script in every page, you could do it directly
In order to perform this operation directly (i.e. not interfere with the original webRequest if the page is not ultimately going to be redirected), you have to know that the reason for the webRequest is that a link was clicked prior to the webRequest.onBeforeRequest event fires. To get this information to your background script in time, you would have to inject a content script in every page and runtime.sendMessage() a message to your background script that a link is in the process of being clicked.
Based on testing, such a message will get to the background script prior to the webRequest.onBeforeRequest firing. Being able to do this with a content script depends on the exact timing of the asynchronous communication between the content script sending a message with runtime.sendMessage() and when the runtime.onMessage event fires vs. when the webRequest.onBeforeRequest event fires. Testing indicates that the mousedown, mouseup and click events (click is not always fired for all mouse buttons) can send a message that the background script receives prior to the webRequest.onBeforeRequest event fires. This timing is not guaranteed, but appears to work.
From a User Experience point of view this is often a bad idea
What you desire to do usurps the user's choice to use a UI interaction to specifically open a link in a new tab or window. Unless I was specifically looking for this functionality, I would find this very annoying and, almost certainly, immediately uninstall the extension. Usurping the user's agency to control their machine is something that should be done only under very limited circumstances. Unless you are in a specialized environment, what you are proposing will be contrary to most user's expectations. In addition, it may break interactions with some websites.
I don't know this for sure, but wouldn't you have to cancel the native event in favor of what you want to do, similar to event.preventDeafult()?

Enabling the back button without allowing manual changes through the URL

I have a jQuery Mobile application I'm developing. jQuery Mobile uses pushState by default to allow the browser's back button to work dynamically.
Now, my application is meant to change its pages dynamically, and the user should always arrive at the front page when loading the application.
The problem is, jQuery Mobile updates the page's hash in the URL whenever I go to a page in the application. Now, if the user enters the same hash in the application, jQuery Mobile will automatically take them to that page (when I'd want them to be handled by my code). Also, if they refresh the page, I'd like my code to take them back to where they should be, not directly moved to the hash the URL had.
To prevent this, I tried to add the following code in the mobileinit event:
$.mobile.hashListeningEnabled = false;
This works, but it also disables the pushState updates, which in turn breaks the back button, which I don't want to happen.
What would be the best way to allow users to use the back button while still not allowing manual movement between pages?
I don't have so much element to describe a possible and accurate solution for your problem, but an easy one should be this:
on every link on your page that take to another one attach a function like this:
$(DOMElem).on("click",function(){
sessionStorage["urlChangedByLink"] = "true";
});
On the same page you can try if there are no problem with this:
$( window ).on( "navigate", function( event, data ) {
if(sessionStorage["urlChangedByLink"] == "true")
$.mobile.hashListeningEnabled = true;
else
$.mobile.hashListeningEnabled = false;
});
Or this, on the other page you check if this storage variable exsist and than make your operation:
if(sessionStorage["urlChangedByLink"] == "true")
continue navigation...
else
window.history.back();
one option here is to set the data-url for each of your pages you simply add the attribute to your page div and set it equal to your home page that way the url for the page shown in the history doesnt have the hash values (or you could include your own values). the documentation on this is better explained in the jquery mobile documentation

Triggering multiple individual JavaScript functions via URL without a page refresh

I'm building a simple webapp using NFC(near field communication), which involved certain tags being programmed with the URL of my website + a hash that will trigger a specific JavaScript function.
For example, "www.website.com/index.html#hide/one" will hide the element labeled "one" on the webpage. Simple enough, right? I thought it would be.
I've since learned that when you tap an NFC tag, it opens the URL in a new webpage/tab. I think this could be averted if my webpage checked to see if there are any other open pages and closes them, though.
If there's a better way to do this(trigger JavaScript functions on a webpage via a URL to that webpage), please let me know. It's important to note that there are 8 tags(elements) in total, and they all have to be triggered for the game/app to end, which requires it all to be done on the same page, preferably without refreshes(although I could probably rig something up using localstorage so it could be refreshed).
Thanks in advance, I'm just not sure how I would proceed here.
-Mitchyl
EDIT - I should mention that I already am using backbone.js for my routing needs. It's perfect for my situation at the moment,
What you need is a hash tag routing libary. http://projects.jga.me/routie/
This will run when your app page loads and read the hash, diverting the logic of your code to do something based on the hash tag, thus you make your items remove on the page in your code logic. No need for lots of pages.
But!
If the url launcher on the device launches new windows each time an item is detected, that is a problem since you can't close those windows, other than from the window itself.
Solution
The app has a main window for the game, each item is stored in local storage, You can use the local storage event system to detect if another page changes an item, and update the UI in real-time.
addEvent(window, 'storage', function (event) {
if (event.key == 'item1') {
item1.innerHTML = event.newValue;
}
});
When NFC launches a new window, display user feedback that states they have completed a task of the game then close it using a timeout.
Below that window will be the main page window with the update displayed.
Done properly it will work brilliantly. You can also add a nice x close button on the pop windows as tasks are completed.

Getting direct URL while clicking javascript button on specific website

I am displaying an online internal website.
Upon clicking on a button "A" it processes a task, and goes to another HTML page. However, this direct address is like "hidden" (hard to explain).
For example, for each page I am accessing by simple button click, it's always the same URL (like http://host.com for every page I display from them).
I am using Firefox, and I need to know how to get the exact HTML address (or direct URL) used for displaying these full new pages. I managed to do it few months ago, but not anymore.
It will help me to automate some tasks and bashing programs. I am openned to any linux browser in case you find a way to help me. Thanks a lot.
it sounds like domain masking is used. you could check the source and see if a frame is being used on the page. the source should indicate the src of the frame, revealing the location of the page.
<frame src="page.html">
If the button uses window.open to navigate to the url, you could override that method and intercept the url there:
var oldOpen = window.open;
window.open = function(){
console.log(arguments[0]);
oldOpen.apply(window, arguments);
};

Android Browser after hash change no apple-touch-icon-precomposed

I have encountered a weird thing (bug?) on Android Stock Browser and Mobile Chrome, both installed on Android ICS 4.0.3/4 (I've tested both). From reliable sources I have heard the same issue appears on Android 2.3.
What I want to achieve:
I recently added the apple-touch-icon-precomposed icon link, which can be used to make my mobile website look like an app if saved to the Home screen. I also added a bookmark bubble to inform users that they can now save my mobile website as an "app". Of course, I don't want to show the bubble to anyone who opened up my website from their app-like bookmark on their Home screen, so I add a special hash on load for anyone who doesn't already have the hash. That way, when they save my website, they'll save it with the hash and thus I can check whether they opened up my website through the normal link my.website.com/ or through their bookmark my.website.com/#specialhash. I simplified this whole process by using an awesome library for it: https://github.com/okamototk/jqm-mobile-bookmark-bubble
The issue:
However, on Android, whenever I through JS change the hash, the browser/the OS won't recognize the apple-touch-icon-precomposed icon links and only save the regular favicon.ico (which looks horrible and not at all like an app).
Is their anything I can do?
PS. In Mobile Chrome the proper apple-touch-icon-precomposed gets saved into the bookmark library, but not whenever I try to save it to the Home screen.
This is an issue with Mobile Chrome. See this bug. Experimenting, it appears that using apple-touch-icon saves it in the bookmarks library and renders it as a small icon on a page when added to the homescreen, but using apple-touch-icon-precomposed doesn't render it on the homescreen at all - I get a globe on a page icon. There's not really anything you can do right now, except to add the bookmark through the stock browser, and use the stock bookmark widget to add it to the homescreen. The Chrome bookmark and widget is broken for these icons.
This solution is for the Android default browser.
It seems that after a hash change, the android default browser gets confused about the bookmark link, maybe because it doesn't recognize the url anymore. I found that if the bookmark image link tag is inserted into the DOM right after the hash change, via JavaScript, and in this case, jQuery, the problem is fixed. This is the workaround I used to address this problem:
// create the bookmark link
MyObject.prototype.getBookmarkLink = function(imageName)
{
var html = '';
if (imageName) {
html = '<link rel="apple-touch-icon-precomposed" href="' + PathToImages + '/' + imageName + '" />';
}
return html;
};
MyObject.prototype.insertBookmarkImage = function(imageName)
{
if (imageName) {
// try to get the link that may already be there
var existingBookmark = jQuery("link[rel='applfe-touch-icon-precomposed']");
// if able to find it...
if (existingBookmark.length) {
// remove it...
existingBookmark.remove();
// ... then, put it back
jQuery('head').append(existingBookmark);
} else {
// we were not able to find it, so add a new one
jQuery('head').append(this.getBookmarkLink(imageName));
}
}
};

Categories