Chrome extension: detect headers after reload - javascript

I need to detect the request/response headers from my browser. I'm storing these headers in temporary variables that I will use later.
I want also to reset these variables when a page in a tab is reloaded.
I think I figured out the commands thanks to the guides and other people's answers. Anyway I saw that when I want to detect the reload of a page, the reload event seems to be fired after some of the request headers of the page are retrieved again.
Here's an example of what I get immediately after I refresh the page:
Here's my code:
/* Listener to the page for reloading */
chrome.tabs.onUpdated.addListener( function(tabId,changeInfo,tab){
//Check changeInfo.url: check existence - if it does, then that means that the url was changed and thus not a refresh.
console.log(changeInfo)
if (changeInfo.url === undefined) {
//reset the variables for a certain tab id on its refresh
if (changeInfo.status == 'loading') {
console.log('Refresh happened for tab: '+ tabId)
//global variables
requestHeaders = {}
responseHeaders = {}
}
}
});
/**
* Stores HTTP request headers into an object with TabId and URI as key.
*/
chrome.webRequest.onBeforeSendHeaders.addListener(
function(req){
var tabId = req.tabId;
/* header memorization */
console.log( tabId, req.url );
});
I realize these calls are asynchronous and to concatenate them I should put the call to onBeforeSendHeaders inside the callback of tabs.onUpdated, but in this case I'm losing (as in the case I reset the buffers) some of them because some of the headers seem to be received before the onUpdated event is fired.
How can I do to capture all the HTTP requests from when the page is loaded? That is, is there a way to attach the headers capturing function before the page starts receiving them?

Do not use the chrome.tabs event at all. Rendering (chrome.tabs) is completely unrelated to fetching the resource (chrome.webRequest).
Every set of requests for a tab starts with loading the top-level frame. You can detect this kind of request by checking whether req.type == 'main_frame'.
Note that your code only works for one tab. You should probably store the data in a dictionary using the tabId as key, and delete the value of the dictionary when the chrome.tabs.onRemoved event is triggered.

Related

Chrome Extension's persistent cookies not expiring correctly?

Summary:
Basically, I'm using a background page to listen to events, such as: onStartup, onInstalled and cookies.onChanged to decide which page should be displayed to the user when the browserAction is clicked. My question regards the latter and how it is triggered.
Code sample:
chrome.cookies.onChanged.addListener(function(info){
if(info.cookie.name === "dummycookie"){
/* Possibilities of info.cause (as described in the docs):
* evicted
* expired
* explicit (it's used when setting or removing a cookie)
* expired_overwrite
* overwrite
*/
if(info.cause == "overwrite" || (info.cause == "explicit" && !info.removed)){
// Cookie was set (explicit or overwrite)
chrome.browserAction.setPopup({ popup: "dummy1.html" });
}
else{
// Cookie was removed (evicted, expired or expired_overwrite)
chrome.browserAction.setPopup({ popup: "dummy2.html" });
}
}
});
The thing is, although the code above handles explicit calls just fine (cookies.set & cookies.get), it doesn't seem to trigger when a cookie life-span expires..
From the debugging sessions I conducted, the code is only triggered when a explicit call is made after the cookie's expected expiration date.
E.g. if I make a call like cookies.getAll() after the supposed expiration time, the browser realizes that the cookie has expired and only then the event is triggered.
Did I miss anything ? Can anyone please enlighten me if I'm misusing the cookies API or if I misunderstood the mechanic behind it ?
Any help is greatly appreciated !
Best regards,
For rare actions such as opening the browser action popup, you'd better actively query the cookies API for the latest state of the relevant cookie, instead of listening for cookie changes via the chrome.cookies.onChanged, because:
If your observed bug is real, then this will work around it.
Since the popup is not opened very often, it's quite a waste of resources to keep a background/event page alive merely to get notified of cookie changes, especially given the alternative method presented in this answer.
Example (popup.js, requires activeTab and cookies permission):
// Example: Get the value of the _gaq cookie for the top-level frame of the
// current tab.
chrome.tabs.query({
active: true,
currentWindow: true
}, function(tabs) {
// Need activeTab permission to read url, e.g. http://example.com/home
var url = tabs[0].url;
chrome.cookies.get({
url: url,
name: '_gaq'
}, function(cookie) {
// TODO: Do something with cookie, e.g. by updating the view (via DOM).
});
});

How can I get the previous URL of a tab?

When writing a Chrome extension, given a tab, how can I get the URL of the previously-visited page in that tab? i.e. the url that will appear in the omnibar after I hit "back"?
Since I could not find any API approach, I just applied vux777's suggestion above: every time a page loads I store a mapping from its id to its URL. Then when I want to find the previous page of a tab, I can search for it there.
So, storage:
chrome.webNavigation.onCommitted.addListener(function (data) {
if (data.frameId !== 0) {
// Don't trigger on iframes
return;
}
var tabIdToUrl = {};
tabIdToUrl[data.tabId.toString()] = data.url;
chrome.storage.local.set(tabIdToUrl);
});
And retrieval:
chrome.storage.local.get(tabId, function (item) {
var url = item[tabId];
...
});
I am running into the same issue, really wished that chrome api could return both the before and after url at chrome.tabs.onUpdated event.
My solution is similar to #Oak, but instead of using chrome.storage.local I am using Window.sessionStorage due to the following two reasons:
chrome.storage.local behaves similarly to Window.localStorage, it persists even when the browser is closed and reopened. If you don't do cleanup yourself, your local storage will grow overtime with a lot of redundant information. With session storage, whenever you closed all of your browser instances (end of persistent background page's lifetime). it will conveniently forget everything :)
Window.sessionStorage stores data in strings only, which is good for this use case (tab.url), chrome.storage.local is a more powerful tool, you can save some space when you want to store objects.
my test case is something like this:
chrome.tabs.onUpdated.addListener(function(tabId,changeInfo,tab){
var newUrl = changeInfo.url;
if(newUrl){
window.sessionStorage[tabId] = newUrl;
}
});
Another approach uses the referrer of the page. This requires that:
there must be some way to retrieve the page referrer, either by loading a content script into the page that communicates the referrer to the extension, or by somehow inspecting the web navigation or request as it is happening in the background script to retrieve the Referer header (notice the typo)
the page that referred to the current page must have a referrer policy that provides sufficient information
content-script.js
// notify extension that a page has loaded its content script, and send referrer
chrome.runtime.sendMessage({ referrer: document.referrer });
background.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
console.log(sender.tab.id);
console.log(request.referrer);
});
Alternatively, the extension could query a tab to get its referrer. You must ensure the tab is ready (has a loaded content script):
content-script.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
sendResponse({ referrer: document.referrer });
});
background.js
function askTabForReferrer(tabId) {
chrome.tabs.sendMessage(tabId, {}, function(response) {
console.log(response.referrer);
});
}
const exisitingTabWithLoadedContentScriptId = 83;
askTabForReferrer(exisitingTabWithLoadedContentScriptId);

How do I provide an extra function to javascript code through an extension?

I want to write an extension that does the following:
Defines a custom function
Allows Javascript code loaded from the Internet to run such a function
The function should take as a parameter an event listener. Basically, something like:
newApiFunctionDefinedInExtension( function( responseHeaders ){
console.log("Headers arrived!", responseHeaders );
} ;
Then using chrome.webRequest, my extension (which made newApiFunctionDefinedInExtension available in the first place) will call the listener (in the locally loaded page) every time response headers are received from the network.
I am new to Chrome extensions and cannot find a way to make that happen. It would be great to know:
How to make a function defined in a module available to the loaded page's scope
How to make such an EventEmitter -- is there a constructor class I can extend?
My goal is simple: the loaded page should define a function, and that function should be called every time there is a network connection.
Every webRequest event receives information about a request, including the ID of the originating tab.
So, assuming that the tab exists note 1, you can use the following flow:
// background.js
chrome.webRequest.onHeadersReceived.addListener(function(details) {
if (details.tabId == -1)
return; // Not related to any tab
chrome.tabs.sendMessage(details.tabId, {
responseHeaders: details.responseHeaders
});
}, {
urls: ['*://*/*'], // e.g. all http(s) URLs. See match patterns docs
// types: ['image'] // for example, defaults to **all** request types
}, ['responseHeaders']);
Then, in a content script (declared in the manifest file), you take the message and pass it to the web page:
// contentscript.js
chrome.runtime.onMessage.addListener(function(message) {
// Assuming that all messages from the background are meant for the page:
document.dispatchEvent(new CustomEvent('my-extension-event', {
detail: message
}));
});
After doing that, your web page can just receive these events as follows:
document.addEventListener('my-extension-event', function(event) {
var message = event.detail;
if (message.responseHeaders) {
// Do something with response headers
}
});
If you want to put an abstraction on top (e.g. implementing a custom EventEmitter), then you need to inject a script in the main execution environment, and declare your custom API over there.
note 1. For simplicity, I assumed that the tab existed. In reality, that is never true for type "main_frame" (and "sub_frame"), because the page has not yet been rendered. If you want to get response headers for the top-level/frame documents, then you need to temporarily store the response headers in some data structure (e.g. a queue / dictionary) in the background page, and send the data to the content script whenever the script is ready.
This can be implemented by using chrome.runtime.sendMessage in the content script to send a message to the background page. Then, whenever a page has loaded and the content script is ready, the background page can use sendResponse to deliver any queued messages.

How to properly handle chrome extension updates from content scripts

In background page we're able to detect extension updates using chrome.runtime.onInstalled.addListener.
But after extension has been updated all content scripts can't connect to the background page. And we get an error: Error connecting to extension ....
It's possible to re-inject content scripts using chrome.tabs.executeScript... But what if we have a sensitive data that should be saved before an update and used after update? What could we do?
Also if we re-inject all content scripts we should properly tear down previous content scripts.
What is the proper way to handle extension updates from content scripts without losing the user data?
If you've established a communication through var port = chrome.runtime.connect(...) (as described on
https://developer.chrome.com/extensions/messaging#connect), it should be possible to listen to the runtime.Port.onDisconnect event:
tport.onDisconnect.addListener(function(msg) {...})
There you can react and, e.g. apply some sort of memoization, let's say via localStorage. But in general, I would suggest to keep content scripts as tiny as possible and perform all the data manipulations in the background, letting content only to collect/pass data and render some state, if needed.
Once Chrome extension update happens, the "orphaned" content script is cut off from the extension completely. The only way it can still communicate is through shared DOM. If you're talking about really sensitive data, this is not secure from the page. More on that later.
First off, you can delay an update. In your background script, add a handler for the chrome.runtime.onUpdateAvailable event. As long as the listener is there, you have a chance to do cleanup.
// Background script
chrome.runtime.onUpdateAvailable.addListener(function(details) {
// Do your work, call the callback when done
syncRemainingData(function() {
chrome.runtime.reload();
});
});
Second, suppose the worst happens and you are cut off. You can still communicate using DOM events:
// Content script
// Get ready for data
window.addEventListener("SendRemainingData", function(evt) {
processData(evt.detail);
}, false);
// Request data
var event = new CustomEvent("RequestRemainingData");
window.dispatchEvent(event);
// Be ready to send data if asked later
window.addEventListener("RequestRemainingData", function(evt) {
var event = new CustomEvent("SendRemainingData", {detail: data});
window.dispatchEvent(event);
}, false);
However, this communication channel is potentially eavesdropped on by the host page. And, as said previously, that eavesdropping is not something you can bypass.
Yet, you can have some out-of-band pre-shared data. Suppose that you generate a random key on first install and keep it in chrome.storage - this is not accessible by web pages by any means. Of course, once orphaned you can't read it, but you can at the moment of injection.
var PSK;
chrome.storage.local.get("preSharedKey", function(data) {
PSK = data.preSharedKey;
// ...
window.addEventListener("SendRemainingData", function(evt) {
processData(decrypt(evt.detail, PSK));
}, false);
// ...
window.addEventListener("RequestRemainingData", function(evt) {
var event = new CustomEvent("SendRemainingData", {detail: encrypt(data, PSK)});
window.dispatchEvent(event);
}, false);
});
This is of course proof-of-concept code. I doubt that you will need more than an onUpdateAvailable listener.

How to tell the difference between 'real' and 'virtual' onpopstate events

I'm building a tool that uses AJAX and pushState/replaceState on top of a non-javascript fallback (http://biologos.org/resources/find). Basically, it's a search tool that returns a list of real HTML links (clicking a link takes you out of the tool).
I am using onpopstate so the user can navigate through their query history created by pushState. This event also fires when navigating back from a real link (one NOT created with pushState but by actual browser navigation). I don't want it to fire here.
So here's my question: how can I tell the difference between a onpopstate event coming from a pushState history item, vs one that comes from real navigation?
I want to do something like this:
window.onpopstate = function(event){
if(event.realClick) return;
// otherwise do something
}
I've tried onpopstate handler - ajax back button but with no luck :(
Thanks in advance!
EDIT:
A problem here is the way different browsers handle the onpopstate event. Here's what seems to be happening:
Chrome
Fires onpopstate on both real and virtual events
Actually re-runs javascript (so setting loaded=false will actually test false)
The solution in the above link actually works!
Firefox
Only fires onpopstate on virtual events
Actually re-runs javascript (so setting loaded=false will actually test false)
For the linked solution to actually work, loaded needs to be set true on page load, which breaks Chrome!
Safari
Fires onpopstate on both real and virtual events
Seems to NOT re-run javascript before the event (so loaded will be true if previously set to be true!)
Hopefully I'm just missing something...
You may be able to use history.js. It should give you an API that behaves consistently across all major platforms (though it's possible that it does not address this specific issue; you'll have to try it to find out).
However, in my opinion, the best way to handle this (and other related issues too) is to design your application in such a way that these issues don't matter. Keep track of your application's state yourself, instead of relying exclusively on the state object in the history stack.
Keep track of what page your application is currently showing. Track it in a variable -- separate from window.location. When a navigation event (including popstate) arrives, compare your known current page to the requested next page. Start by figuring out whether or not a page change is actually required. If so, then render the requested page, and call pushState if necessary (only call pushState for "normal" navigation -- never in response to a popstate event).
The same code that handles popstate, should also handle your normal navigation. As far as your application is concerned, there should be no difference (except that normal nav includes a call to pushState, while popstate-driven nav does not).
Here's the basic idea in code (see the live example at jsBin)
// keep track of the current page.
var currentPage = null;
// This function will be called every time a navigation
// is requested, whether the navigation request is due to
// back/forward button, or whether it comes from calling
// the `goTo` function in response to a user's click...
// either way, this function will be called.
//
// The argument `pathToShow` will indicate the pathname of
// the page that is being requested. The var `currentPage`
// will contain the pathname of the currently visible page.
// `currentPage` will be `null` if we're coming in from
// some other site.
//
// Don't call `_renderPage(path)` directly. Instead call
// `goTo(path)` (eg. in response to the user clicking a link
// in your app).
//
function _renderPage(pathToShow) {
if (currentPage === pathToShow) {
// if we're already on the proper page, then do nothing.
// return false to indicate that no actual navigation
// happened.
//
return false;
}
// ...
// your data fetching and page-rendering
// logic goes here
// ...
console.log("renderPage");
console.log(" prev page : " + currentPage);
console.log(" next page : " + pathToShow);
// be sure to update `currentPage`
currentPage = pathToShow;
// return true to indicate that a real navigation
// happened, and should be stored in history stack
// (eg. via pushState - see `function goTo()` below).
return true;
}
// listen for popstate events, so we can handle
// fwd/back buttons...
//
window.addEventListener('popstate', function(evt) {
// ask the app to show the requested page
// this will be a no-op if we're already on the
// proper page.
_renderPage(window.location.pathname);
});
// call this function directly whenever you want to perform
// a navigation (eg. when the user clicks a link or button).
//
function goTo(path) {
// turn `path` into an absolute path, so it will compare
// with `window.location.pathname`. (you probably want
// something a bit more robust here... but this is just
// an example).
//
var basePath, absPath;
if (path[0] === '/') {
absPath = path;
} else {
basePath = window.location.pathname.split('/');
basePath.pop();
basePath = basePath.join('/');
absPath = basePath + '/' + path;
}
// now show that page, and push it onto the history stack.
var changedPages = _renderPage(absPath);
if (changedPages) {
// if renderPage says that a navigation happened, then
// store it on the history stack, so the back/fwd buttons
// will work.
history.pushState({}, document.title, absPath);
}
}
// whenever the javascript is executed (or "re-executed"),
// just render whatever page is indicated in the browser's
// address-bar at this time.
//
_renderPage(window.location.pathname);
If you check out the example on jsBin, you'll see that the _renderPage function is called every time the app requests a transition to a new page -- whether it's due to popstate (eg. back/fwd button), or it's due to calling goTo(page) (eg. a user action of some sort). It's even called when the page first loads.
Your logic, in the _renderPage function can use the value of currentPage to determine "where the request is coming from". If we're coming from an outside site then currentPage will be null, otherwise, it will contain the pathname of the currently visible page.

Categories

Resources