debian-mirror-gitlab/snowplow-javascript-tracker/src/js/links.js

182 lines
6.2 KiB
JavaScript
Executable File

/*
* JavaScript tracker for Snowplow: links.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var isUndefined = require('lodash/isUndefined'),
helpers = require('./lib/helpers'),
object = typeof exports !== 'undefined' ? exports : this;
/**
* Object for handling automatic link tracking
*
* @param object core The tracker core
* @param string trackerId Unique identifier for the tracker instance, used to mark tracked links
* @param function contextAdder Function to add common contexts like PerformanceTiming to all events
* @return object linkTrackingManager instance
*/
object.getLinkTrackingManager = function (core, trackerId, contextAdder) {
// Filter function used to determine whether clicks on a link should be tracked
var linkTrackingFilter,
// Whether pseudo clicks are tracked
linkTrackingPseudoClicks,
// Whether to track the innerHTML of clicked links
linkTrackingContent,
// The context attached to link click events
linkTrackingContext,
// Internal state of the pseudo click handler
lastButton,
lastTarget;
/*
* Process clicks
*/
function processClick(sourceElement, context) {
var parentElement,
tag,
elementId,
elementClasses,
elementTarget,
elementContent;
while ((parentElement = sourceElement.parentNode) !== null &&
!isUndefined(parentElement) && // buggy IE5.5
((tag = sourceElement.tagName.toUpperCase()) !== 'A' && tag !== 'AREA')) {
sourceElement = parentElement;
}
if (!isUndefined(sourceElement.href)) {
// browsers, such as Safari, don't downcase hostname and href
var originalSourceHostName = sourceElement.hostname || helpers.getHostName(sourceElement.href),
sourceHostName = originalSourceHostName.toLowerCase(),
sourceHref = sourceElement.href.replace(originalSourceHostName, sourceHostName),
scriptProtocol = new RegExp('^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):', 'i');
// Ignore script pseudo-protocol links
if (!scriptProtocol.test(sourceHref)) {
elementId = sourceElement.id;
elementClasses = helpers.getCssClasses(sourceElement);
elementTarget = sourceElement.target;
elementContent = linkTrackingContent ? sourceElement.innerHTML : null;
// decodeUrl %xx
sourceHref = unescape(sourceHref);
core.trackLinkClick(sourceHref, elementId, elementClasses, elementTarget, elementContent, contextAdder(helpers.resolveDynamicContexts(context, sourceElement)));
}
}
}
/*
* Return function to handle click event
*/
function getClickHandler(context) {
return function (evt) {
var button,
target;
evt = evt || window.event;
button = evt.which || evt.button;
target = evt.target || evt.srcElement;
// Using evt.type (added in IE4), we avoid defining separate handlers for mouseup and mousedown.
if (evt.type === 'click') {
if (target) {
processClick(target, context);
}
} else if (evt.type === 'mousedown') {
if ((button === 1 || button === 2) && target) {
lastButton = button;
lastTarget = target;
} else {
lastButton = lastTarget = null;
}
} else if (evt.type === 'mouseup') {
if (button === lastButton && target === lastTarget) {
processClick(target, context);
}
lastButton = lastTarget = null;
}
};
}
/*
* Add click listener to a DOM element
*/
function addClickListener(element) {
if (linkTrackingPseudoClicks) {
// for simplicity and performance, we ignore drag events
helpers.addEventListener(element, 'mouseup', getClickHandler(linkTrackingContext), false);
helpers.addEventListener(element, 'mousedown', getClickHandler(linkTrackingContext), false);
} else {
helpers.addEventListener(element, 'click', getClickHandler(linkTrackingContext), false);
}
}
return {
/*
* Configures link click tracking: how to filter which links will be tracked,
* whether to use pseudo click tracking, and what context to attach to link_click events
*/
configureLinkClickTracking: function (criterion, pseudoClicks, trackContent, context) {
linkTrackingContent = trackContent;
linkTrackingContext = context;
linkTrackingPseudoClicks = pseudoClicks;
linkTrackingFilter = helpers.getFilter(criterion, true);
},
/*
* Add click handlers to anchor and AREA elements, except those to be ignored
*/
addClickListeners: function () {
var linkElements = document.links,
i;
for (i = 0; i < linkElements.length; i++) {
// Add a listener to link elements which pass the filter and aren't already tracked
if (linkTrackingFilter(linkElements[i]) && !linkElements[i][trackerId]) {
addClickListener(linkElements[i]);
linkElements[i][trackerId] = true;
}
}
}
};
};