Whitelist / Blocking with chrome.webRequest - javascript

so I've been trying to add whitelist ability to chrome extension, where that list can be updated via or whitelist can be totally disabled.
After checking the documentation and all possible answers here, I am still facing one weird issue, any light on it would be appreciated.
So here is the code snippet
//this list can be updated by user or other trigger
var allowed = ["example.com", "cnn.com", "domain.com"];
//this is main callback, so it can be removed from listener when needed
whiteMode = function (details) {
//checking url to array
var even = function(element) {
return details.url.indexOf(element) == -1;
}
if (allowed.some(even) == true) {
return {cancel: true }
} else {
return {cancel: false}
}
}
//setup listener
chrome.webRequest.onBeforeRequest.addListener(
whiteMode,
{urls: ["<all_urls>"]},
["blocking"]
);
so that works fine itself, if user wants to disable the mode, I just call
chrome.webRequest.onBeforeRequest.removeListener(whiteMode);
Then, if I would like to update the allowed list, I first use removeListener, then relaunch it again with new values, it does launches, however the "whiteMode" function keeps triggering twice now. By checking with console.log, I see that my new url is missing in the array on first try, then immidiately listener works again, and there is correct new array of allowed, however as it was already blocked by first trigger, it's just doing nothing.
The question, why the listener keeps doing it twice or more (if I add more items let's say), even if it was removed before added back.
Is there any way to clear up all listeners? (nothing about that in docs), been struggling with this for quite some time...
Also, tried with onHandlerBehaviorChanged, but its not helping.

Related

Chrome Bug - onDOMContentLoaded firing before any DOM has loaded

I have set up an event listener in my background.js file:
chrome.webNavigation.onDOMContentLoaded.addListener(function (info) {
// Checks for the URL bar navigation and not any child frames
if (info.frameId === 0){
var url = info.url;
console.log('visit: ' + url);
}
});
I would expect the a log entry showing the url of the current page when the dom has loaded. The problem arises when typing in a search in the omnibox. Say I visit www.twitter.com often enough to have it cached when I enter a search that starts with "t" in the omnibox.
If I enter the search term "testing123" in the omnibox, I'll see a log entry saying "visit: http://www.twitter.com/". The same thing will happen if I enter the search term "callbacks": I'll see a log entry saying "visit: http://www.cnn.com" These get logged even before I hit enter on the search. I'm assuming this is because Chrome is doing some sort of pre-fetching on commonly visited URLs, but I still think this is unexpected behavior.
Can someone confirm? Thanks.
Edit:
I tried some other webNavigation methods but I'm still seeing some inconsistencies:
onComitted - Fired almost all the time.
onHistoryStateUpdated - Fired some of the time
onTabReplaced - Was not firing as no pre-rendering was done.
Edit 2:
The following code helped me sort out events that were being fired on inactive tabs. (See comments 5/6 for more explanation.)
chrome.webNavigation.onCommitted.addListener(
function (info) {
// Checks for the URL bar navigation and not any child frames
if (info.frameId === 0){
chrome.tabs.query({active: true, currentWindow: true}, function (tab) {
// Ensures that the listener is only attached to the active tab
if (tab[0].id === info.tabId){
// Everything's good! Do your work here
}
});
}
}
);

Is there a method within Omniture's s.code to see whether a pageview has fired?

I'm looking to develop a Omniture "trouble" tag that will fire within Google Tag Manager. This tag should check to see whether the Omniture s.code on-page has fired a pageview. If it hasn't, send an Event to our GA account.
Ideally, it'd look like this (pseudocode follows):
<script>
window.onload=function(){if(OmniturePageViewHasFired == false){
ga('send','event','SCodeMissing','Page',window.location.href);
}}
</script>
Just checking to see whether the s.code is on page at all is a much easier task, but won't be as useful since it's possible for the code to be on page and not have fired. Any ideas? Also note that I do NOT have access to the s.code itself, so I can't set a variable with it that's then picked up by this script.
This is how I'd do it, based on how Adobe's DigitalPulse Debugger looks for it:
function OmniturePageViewHasFired() {
var i=document.images;
for (var c=0,l=i.length;c<l;c++) {
if ( (i[c].src.indexOf('/b/ss/')>=0)
&& (!i[c].src.match(/[&?]pe=/))
) return true;
}
for (var o in window) {
if ( (o.substring(0,4)=='s_i_')
&& (window[o].src)
&& (window[o].src.indexOf('/b/ss/')>=0)
&& (!window[o].src.match(/[&?]pe=/))
) return true;
}
return false;
}
//example:
if (OmniturePageViewHasFired() == false){
// no omn request detected
} else {
// found at least 1
}
Note 1: This will only return true if a page view (s.t) request is made. It will not return true for click requests (s.tl). If you want it to return true for any request, then remove the last &&.. in the 2 conditions.
Note 2: Officially Adobe thinks it is good enough to just look for /b/ss/ in the src. Admittedly, in all my years of QA'ing Adobe Analytics (btw that's what it's called now, not Omniture), I've only seen a false positive from this like one or two times.
If this worries you, you can make the condition more specific by evaluating the domain of the src for your implementation. This is unique to your implementation, which is why Adobe doesn't look for something more specific. Just look at a request on your page to get it.
You can add your own code to track whether or not Omniture has fired.
There is a plugin feature, and Omniture uses to fire plugins and other code on a tracking event. This function is fired everytime a pageview or on-click event, or any other type of Omniture event is fired.
You'll want to make sure that this is not used elsewhere to enable Omniture plugins, if it is, you'll have to be able to update that code. Basically you could do something like the following, as long as you make sure that the following bit of code loads and exectues (after) the s_code.js file loads and executes:
var om_fired = false;
s.usePlugins=true;
function s_doPlugins(s) {
om_fired = true;
}
s.doPlugins=s_doPlugins;
Then in your google code just check to see if om_fired is true/false
Another way to do it (adding it here since it was too long for a comment).
Before s.code loads, add some script that reads the cookies that are set by Omniture, I don't remember the exact cookie names, but multiple cookies are set. Your code that reads the cookie will need to be in the header so it fires as the page loads( and before site cat tracking code), and before page load completes. Read the omniture cookie, and check the values, there is one in there for Time Since Last hit, I believe the cookie variable is something like s.tlh. Store that value away, then after page load fire another call that reads the cookie again and check to see if the tlh value changed. If it did then your tracking event occured. If it did not change, then your event did not fire. Your challenge will be to make sure the code fires in the right order, i.e. 1. Read cookie/store value, 2. Site Cat Fires, 3. Read cookie again and compare w/ cookie in #1.

How do I make a webRequest Event Page that only listens for events when a flag is set?

I am making an extension that can be ON or OFF, which I can check via a flag stored in local data.
When ON, I want to listen for all webRequests and redirect them like so:
chrome.webRequest.onBeforeRequest.addListener(
// callback
function(info) {
console.log("Got request: " + info.url + "\n Going to redirect");
return {redirectUrl: chrome.extension.getURL("redirect.html")};
},
// filters
{
urls: [
"<all_urls>"
]
},
// extraInfoSpec
["blocking"]);
But when OFF I do not want the event to fire at all. Ideally when OFF I wouldn't even be listening to events (if that would cut down on performance overhead in any significant way). One option I see is checking the flag in my callback function and simply not redirecting if OFF, but that still has the event being handled. As I understand it, the event will not be handled if the RequestFilter does not pass. Can I modify the RequestFilter to also check my ON/OFF boolean flag before trying to handle the event? Or is the RequestFilter only meant for checking URLs, headers, etc.?
Also my main reasoning for wanting to only handle events when ON is that it seems like a needless performance hit to try to handle EVERY webRequest - even if briefly. Would an immediate flag checking in the callback function not make any noticeable impact on performance anyway?
I am new to Chrome Extension dev and webdev in general, so if there is a much cleaner/easier way of doing this then please let me know.
Thanks!
The chrome.webRequest API cannot be used on event pages.
If you implement ExpertSystem's answer, then your extension won't add overhead to requests, but it will still waste memory (because using the webRequest API implies that you're using background pages. These pages always remain active even when the extension appears to do nothing).
The chrome.declarativeWebRequest is similar to the webRequest API, except that its API is declarative, allowing it to be used on event pages as well. The only downside of the API is that it is currently only enabled on the beta or dev channel. It will eventually be available on the stable channel though, probably within a few releases.
The following example shows how to redirect any URL whose host contains "google" ("google.com", "www.google.nl", but NOT "notgoogle.com") to a page within your extension:
var rules = [{
id: 'redirect-to-my-extension',
conditions: [
new chrome.declarativeWebRequest.RequestMatcher({
url: {
hostContains: '.google.'
}
})
],
actions: [
new chrome.declarativeWebRequest.RedirectRequest({
redirectUrl: chrome.runtime.getURL('redirect.html')
})
]
}];
// Whenever you're ready...
chrome.declarativeWebRequest.onRequest.addRules(rules);
// To disable the rules, simply remove the rules (by the previously specified id)
var ruleIds = rules.map(function(rule) { return rule.id; });
chrome.declarativeWebRequest.onRequest.removeRules(ruleIds);
This is merely an example. The declarativeWebRequest API has lots of other ways to construct conditions or actions, just take a look at the reference documentation and URL filters.
RequestFilters do not allow you to specify arbitrary conditions (such as if a flag is set). You can un-register the listener using removeListener() whenever the flag is set to OFF and register it back when the flag is set to ON. E.g.:
function myListener(...) {...}
function setEnabled(enabled) {
localStorage.enabled = enabled ? 'ON' : 'OFF';
if (enabled) {
chrome.webRequest.onBeforeRequest.addListener(myListener);
} else {
chrome.webRequest.onBeforeRequest.removeListener(myListener);
}
}
function isEnabled() {
return (localStorage.enabled !== 'OFF'); // <-- 'ON' by default
}
setEnabled(isEnabled());

window.onbeforeunload in Chrome: what is the most recent fix?

Obviously, window.onbeforeunload has encountered its fair share of problems with Chrome as I've seen from all the problems I've encountered. What's the most recent work around?
The only thing I've got even close to working is this:
window.onbeforeunload = function () { return "alert" };
However, if I substitute return "alert" with something like alert("blah"), I get nothing from Chrome.
I saw in this question that Google purposefully blocks this. Good for them... but what if I want to make an AJAX call when someone closes the window? In my case, I want to know when someone has left the chatroom on my website, signalled by the window closing.
I want to know if there's a way to either
(a): fix the window.onbeforeunload call so that I can put AJAX in there
or
(b): get some other way of determining that a window has closed in Chrome
Answer:
$(window).on('beforeunload', function() {
var x =logout();
return x;
});
function logout(){
jQuery.ajax({
});
return 1+3;
}
A little mix and match, but it worked for me. The 1+3 makes sure that the logout function is being called (you'll see 4 if it's successful on the popup when you try to leave).
As of Chrome 98.0.4758.109 and Edge 100.0.1185.29, Chromium has not met the standard. There is a bug report filed, but the review is abandoned.
Test with StackBlitz!
Chrome requires returnValue to be a non-null value whether set as the return value from the handler or by reference on the event object.
The standard states that prompting can be controlled by canceling the event or setting the return value to a non-null value.
The standard states that authors should use Event.preventDefault() instead of returnValue.
The standard states that the message shown to the user is not customizable.
window.addEventListener('beforeunload', function (e) {
// Cancel the event as stated by the standard.
e.preventDefault();
// Chrome requires returnValue to be set.
e.returnValue = '';
});
window.location = 'about:blank';
Here's a more straightforward approach.
$(window).on('beforeunload', function() {
return "You should keep this page open.";
});
The returned message can be anything you want, including the empty string if you have nothing to add to the message that Chrome already shows. The result looks like this:
According to MDN,
The function should assign a string value to the returnValue property
of the Event object and return the same string.
This is the following
window.addEventListener( 'beforeunload', function(ev) {
return ev.returnValue = 'My reason';
})
This solved my problem why it wasn't working in my app:
Note that the user must interact with the page somehow (clicking somewhere) before closing its window, otherwise beforeunload is ignored in order not prevent abuse.

Detect failure to load contents of an iframe

I can detect when the content of an iframe has loaded using the load event. Unfortunately, for my purposes, there are two problems with this:
If there is an error loading the page (404/500, etc), the load event is never fired.
If some images or other dependencies failed to load, the load event is fired as usual.
Is there some way I can reliably determine if either of the above errors occurred?
I'm writing a semi-web semi-desktop application based on Mozilla/XULRunner, so solutions that only work in Mozilla are welcome.
If you have control over the iframe page (and the pages are on the same domain name), a strategy could be as follows:
In the parent document, initialize a variable var iFrameLoaded = false;
When the iframe document is loaded, set this variable in the parent to true calling from the iframe document a parent's function (setIFrameLoaded(); for example).
check the iFrameLoaded flag using the timer object (set the timer to your preferred timeout limit) - if the flag is still false you can tell that the iframe was not regularly loaded.
I hope this helps.
This is a very late answer, but I will leave it to someone who needs it.
Task: load iframe cross-origin content, emit onLoaded on success and onError on load error.
This is the most cross browsers origin independent solution I could develop. But first of all I will briefly tell about other approaches I had and why they are bad.
1. iframe That was a little shock for me, that iframe only has onload event and it is called on load and on error, no way to know it is error or not.
2. performance.getEntriesByType('resource'). This method returns loaded resources. Sounds like what we need. But what a shame, firefox always adds Resource in resources array no matter it is loaded or failed. No way to know by Resource instance was it success. As usual. By the way, this method does not work in ios<11.
3. script I tried to load html using <script> tag. Emits onload and onerror correctly, sadly, only in Chrome.
And when I was ready to give up, my elder collegue told me about html4 tag <object>. It is like <iframe> tag except it has fallbacks when content is not loaded. That sounds like what we are need! Sadly it is not as easy as it sounds.
CODE SECTION
var obj = document.createElement('object');
// we need to specify a callback (i will mention why later)
obj.innerHTML = '<div style="height:5px"><div/>'; // fallback
obj.style.display = 'block'; // so height=5px will work
obj.style.visibility = 'hidden'; // to hide before loaded
obj.data = src;
After this we can set some attributes to <object> like we'd wanted to do with iframe. The only difference, we should use <params>, not attributes, but their names and values are identical.
for (var prop in params) {
if (params.hasOwnProperty(prop)) {
var param = document.createElement('param');
param.name = prop;
param.value = params[prop];
obj.appendChild(param);
}
}
Now, the hard part. Like many same-like elements, <object> doesn't have specs for callbacks, so each browser behaves differently.
Chrome. On error and on load emits load event.
Firefox. Emits load and error correctly.
Safari. Emits nothing....
Seems like no different from iframe, getEntriesByType, script....
But, we have native browser fallback! So, because we set fallback (innerHtml) directly, we can tell if <object> is loaded or not
function isReallyLoaded(obj) {
return obj.offsetHeight !== 5; // fallback height
}
/**
* Chrome calls always, Firefox on load
*/
obj.onload = function() {
isReallyLoaded(obj) ? onLoaded() : onError();
};
/**
* Firefox on error
*/
obj.onerror = function() {
onError();
};
But what to do with Safari? Good old setTimeout.
var interval = function() {
if (isLoaded) { // some flag
return;
}
if (hasResult(obj)) {
if (isReallyLoaded(obj)) {
onLoaded();
} else {
onError();
}
}
setTimeout(interval, 100);
};
function hasResult(obj) {
return obj.offsetHeight > 0;
}
Yeah.... not so fast. The thing is, <object> when fails has unmentioned in specs behaviour:
Trying to load (size=0)
Fails (size = any) really
Fallback (size = as in innnerHtml)
So, code needs a little enhancement
var interval = function() {
if (isLoaded) { // some flag
return;
}
if (hasResult(obj)) {
if (isReallyLoaded(obj)) {
interval.count++;
// needs less then 400ms to fallback
interval.count > 4 && onLoadedResult(obj, onLoaded);
} else {
onErrorResult(obj, onError);
}
}
setTimeout(interval, 100);
};
interval.count = 0;
setTimeout(interval, 100);
Well, and to start loading
document.body.appendChild(obj);
That is all. I tried to explain code in every detail, so it may look not so foolish.
P.S. WebDev sucks
I had this problem recently and had to resort to setting up a Javascript Polling action on the Parent Page (that contains the IFRAME tag). This JavaScript function checks the IFRAME's contents for explicit elements that should only exist in a GOOD response. This assumes of course that you don't have to deal with violating the "same origin policy."
Instead of checking for all possible errors which might be generated from the many different network resources.. I simply checked for the one constant positive Element(s) that I know should be in a good response.
After a pre-determined time and/or # of failed attempts to detect the expected Element(s), the JavaScript modifies the IFRAME's SRC attribute (to request from my Servlet) a User Friendly Error Page as opposed to displaying the typical HTTP ERROR message. The JavaScript could also just as easily modify the SRC attribute to make an entirely different request.
function checkForContents(){
var contents=document.getElementById('myiframe').contentWindow.document
if(contents){
alert('found contents of myiframe:' + contents);
if(contents.documentElement){
if(contents.documentElement.innerHTML){
alert("Found contents: " +contents.documentElement.innerHTML);
if(contents.documentElement.innerHTML.indexOf("FIND_ME") > -1){
openMediumWindow("woot.html", "mypopup");
}
}
}
}
}
I think that the pageshow event is fired for error pages. Or if you're doing this from chrome, then your check your progress listener's request to see if it's an HTTP channel in which case you can retrieve the status code.
As for page dependencies, I think you can only do this from chrome by adding a capturing onerror event listener, and even then it will only find errors in elements, not CSS backgrounds or other images.
Doesn't answer your question exactly, but my search for an answer brought me here, so I'm posting just in case anyone else had a similar query to me.
It doesn't quite use a load event, but it can detect whether a website is accessible and callable (if it is, then the iFrame, in theory, should load).
At first, I thought to do an AJAX call like everyone else, except that it didn't work for me initially, as I had used jQuery. It works perfectly if you do a XMLHttpRequest:
var url = http://url_to_test.com/
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status != 200) {
console.log("iframe failed to load");
}
};
xhttp.open("GET", url, true);
xhttp.send();
Edit:
So this method works ok, except that it has a lot of false negatives (picks up a lot of stuff that would display in an iframe) due to cross-origin malarky. The way that I got around this was to do a CURL/Web request on a server, and then check the response headers for a) if the website exists, and b) if the headers had set x-frame-options.
This isn't a problem if you run your own webserver, as you can make your own api call for it.
My implementation in node.js:
app.get('/iframetest',function(req,res){ //Call using /iframetest?url=url - needs to be stripped of http:// or https://
var url = req.query.url;
var request = require('https').request({host: url}, function(response){ //This does an https request - require('http') if you want to do a http request
var headers = response.headers;
if (typeof headers["x-frame-options"] != 'undefined') {
res.send(false); //Headers don't allow iframe
} else {
res.send(true); //Headers don't disallow iframe
}
});
request.on('error',function(e){
res.send(false); //website unavailable
});
request.end();
});
Have a id for the top most (body) element in the page that is being loaded in your iframe.
on the Load handler of your iframe, check to see if getElementById() returns a non null value.
If it is, iframe has loaded successfully. else it has failed.
in that case, put frame.src="about:blank". Make sure to remove the loadhandler before doing that.
If the iframe is loaded on the same origin as the parent page, then you can do this:
iframeEl.addEventListener('load', function() {
// NOTE: contentDocument is null if a connection error occurs or if
// X-Frame-Options is not SAMESITE (which could happen with
// 4xx or 5xx error pages if the corresponding error handlers
// do not specify SAMESITE). If error handlers do not specify
// SAMESITE, then networkErrorOccurred will incorrectly be set
// to true.
const networkErrorOccurred = !iframeEl.contentDocument;
const serverErrorOccurred = (
!networkErrorOccurred &&
!iframeEl.contentDocument.querySelector('#well-known-element')
);
if (networkErrorOccurred || serverErrorOccurred) {
let errorMessage;
if (networkErrorOccurred) {
errorMessage = 'Error: Network error';
} else if (serverErrorOccurred) {
errorMessage = 'Error: Server error';
} else {
// Assert that the above code is correct.
throw new Error('networkErrorOccurred and serverErrorOccurred are both false');
}
alert(errorMessage);
}
});

Categories

Resources