This is not a perfect solution and may contain bugs, but this seems to work pretty much as intended. You may need some more customization to handle certain situations that may not be covered by this script. The page loading times may have small deviation (~0,2 sec compared to times viewed from fire bug), but they seem to be correct enough.
<script type="text/javascript">
// Not used at the moment, but can be used with all cookies methods, e.g. $.cookies.get('cookie', cookieOptions)
var cookieOptions = {
domain: '*.mydomain.com',
path: '/piwik',
expiresAt: new Date(2100, 1, 1),
secure: true
}
var slowOnPages = ["firstPage", "secondPage"]; // A list of pages where it should be waited longer before sending a raport (e.g. there may be delayed ajax event that may trigger a moment after the page load completes, but should be included in the total page loading time)
// Note: The title used is loaded from a custom field with id 'piwikTitle' in this script. You need to add this field in page markup (e.g. <span id="piwikTitle" style="display: none;">firstPage</span>).
var requestStartCookie = "requestStart"; // The name of a cookie that will store the start time ms of the 1st independent request noticed (page (re)load/transition or 1st ajax request triggered from an event on a completely loaded page)
var taskPollCookie = "taskPoll"; // You may set elsewhere a cookie right before ajax call to indicate that this call should not be raported ($.cookies.set("taskPoll", true);)
var timeOutId = -1; // If there is an time out id set, it indicates that there is a pending raport
var delay = 25; // Note: browser speed may affect on how fast a new chained request would be initiated. This will introduce a bit of uncertainty for time tracking but is needed sometimes to prevent greater uncertainty
var _paq = _paq || []; // Must be here to make it global and detectable by piwik.js
$(document).ajaxStop(function() {
var startMs = $.cookies.get(requestStartCookie);
if (startMs == null) {
console.log("No requestStartCookie was set => no raport sent");
} else {
if (timeOutId > -1) {
// There was a new ajaxStop while the raport was purposedly waiting if such an event happens
// Track the time it takes to complete the last request that completes (this request + including the time it took to complete all the previous)
// clearTimeOut => Discard the pending raporting event of the previous request completed
clearTimeout(timeOutId);
console.log("Discarded pending raport with id: " + timeOutId);
}
var endTimeMs = new Date().getTime();
// There are some pages that may initiate delayed ajax requests
// In such cases the piwik raport sending has to be delayed also, else we will get unnecessary and incorrect raports
// Note: This will start a long waiting period only for the 1st raporting event on the specified pages.
// You may need to tune this for your specific case
if (slowOnPages.indexOf($("#piwikTitle").text()) > -1 && timeOutId == -1) {
timeOutId = setTimeout(function() {doPiwikRaport(startMs, endTimeMs);}, 5000);
} else {
timeOutId = setTimeout(function() {doPiwikRaport(startMs, endTimeMs);}, 1000);
}
}
});
// This will handle the actual raport sending when the time comes
// @param long requestChainStartMs The time in millis when the request chain started
// @param long requestChainEndMs The time in millis when the request chain ended
// @param boolean waitForPageLoadToComplete When this is false the raport is sent even if the page is not completed rendering yet (true by default)
var doPiwikRaport = function(requestChainStartMs, requestChainEndMs, waitForPageLoadToComplete) {
waitForPageLoadToComplete = (typeof waitForPageLoadToComplete !== 'undefined' ? waitForPageLoadToComplete : true);
if(waitForPageLoadToComplete && (document.readyState != 'complete' || $.active != 0)) {
var endTimeMs = new Date().getTime();
timeOutId = setTimeout(function() {doPiwikRaport(requestChainStartMs, endTimeMs);}, delay); // Recursive call, try again after short delay
} else {
var generationTime = requestChainEndMs - requestChainStartMs;
console.log("Page load time was: " + generationTime);
// Construct the Piwik raport
_paq.push(["setCustomUrl", location.pathname]);
_paq.push(['setDocumentTitle', $("#piwikTitle").text()]);
_paq.push(['setCustomVariable', 1, "visitPurpose", $("#visitPurpose").text(), "visit"]);
_paq.push(['setCustomVariable', 2, "pageLoadCompleted", waitForPageLoadToComplete, "page"]);
_paq.push(['setGenerationTimeMs', generationTime]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
_paq.push(['setTrackerUrl', 'http://www2.mydomain.com/piwik/piwik.php']);
_paq.push(['setSiteId', 1]); // Correct the site id
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
g.defer=true; g.async=true; g.src= '/' + location.pathname.split("/")[1] + '/js/piwik.js'; s.parentNode.insertBefore(g,s);
})();
// Remove attributes indicating a pending raport
$.cookies.del(requestStartCookie);
timeOutId = -1;
if (!waitForPageLoadToComplete) {
// If the page did not finish loading, a page transition was made
// Set a new requestStartCookie with the end time previous raport had
$.cookies.set(requestStartCookie, requestChainEndMs);
console.log("Piwik page content load raporting was done prematurely");
} else {
console.log("Piwik page content load raporting was done normally");
}
}
}
$(document).ajaxStart(function() {
var taskPoll = ($.cookies.get(taskPollCookie) != null ? true : false);
// Do not set new start time if a taskPoll iniated the request (taskPolls are not supposed to be raported)
if (!taskPoll) {
// If there is a time out set, then there already is a pending raport => do not change start time
// There also may be a requestStartCookie if there is ongoing page transition
if (timeOutId == -1 && $.cookies.get(requestStartCookie) == null) {
var start = new Date().getTime();
console.log("New request start detected: " + start);
$.cookies.set(requestStartCookie, start);
}
} else if (timeOutId == -1) {
console.log("taskPoll detected, no requestStartCookie set");
}
});
// Detects if the user is leaving page before a raport was sent
window.onbeforeunload = function() {
if (timeOutId > -1) {
clearTimeout(timeOutId);
var startMs = $.cookies.get(requestStartCookie);
doPiwikRaport(startMs, new Date().getTime(), false);
}
};
</script>
Note: JQuery used. No image tracker part included.
Remove the logging parts when done!
Also used this plugin to handle cookies: https://code.google.com/p/cookies/wiki/Documentation
// update
Improved this a bit (changes not included in the script above). The use of cookies can be done better by using html5 sessionStorage:
var checkSessionStorageAvailability = function() {
try {
return 'sessionStorage' in window && window['sessionStorage'] !== null;
} catch(e) {
return false;
}
}
var sessionStorageAvailable = checkSessionStorageAvailability();
var setRequestStart = function(millis) {
if (typeof millis == 'undefined' || millis == null) {
millis = new Date().getTime();
}
if (sessionStorageAvailable) {
sessionStorage.setItem(requestStart, millis);
} else {
$.cookies.set(requestStart, millis);
console.log("New request start detected");
}
}
Replace the get/set/remove/is-cookie functions in the tracking script with similar functions like the helper function example above.