From df65e0dd10f12e11990665971e5f7e019168fd39 Mon Sep 17 00:00:00 2001 From: Thomas Steur Date: Wed, 10 Sep 2014 14:25:08 +0200 Subject: refs #4996 many new tests and many bugfixes. Tests should be done now except for cross browser support. I fixed many issues as I did not consider that link tracking may be disabled or enabled and the behavior should be different in such a case. --- js/piwik.js | 206 +++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 168 insertions(+), 38 deletions(-) (limited to 'js') diff --git a/js/piwik.js b/js/piwik.js index 10ef6a6533..f9309c217f 100644 --- a/js/piwik.js +++ b/js/piwik.js @@ -452,7 +452,9 @@ if (typeof JSON2 !== 'object') { contentInteractionTrackingSetupDone, contains, match, pathname, piece, trackContentInteractionNode, trackContentInteractionNode, trackContentImpressionsWithinNode, trackContentImpression, enableTrackOnlyVisibleContent, trackContentInteraction, clearEnableTrackOnlyVisibleContent, - trackVisibleContentImpressions, isTrackOnlyVisibleContentEnabled, port + trackVisibleContentImpressions, isTrackOnlyVisibleContentEnabled, port, isUrlToCurrentDomain, + isNodeAuthorizedToTriggerInteraction, replaceHrefIfInternalLink, getConfigDownloadExtensions, disableLinkTracking, + substr, setAnyAttribute */ /*global _paq:true */ /*members push */ @@ -1412,6 +1414,18 @@ if (typeof Piwik !== 'object') { var pos = linkElementNames.indexOf(elementName); return pos !== -1; + }, + setAnyAttribute: function (node, attrName, attrValue) + { + if (!node || !attrName) { + return; + } + + if (node.setAttribute) { + node.setAttribute(attrName, attrValue); + } else { + node[attrName] = attrValue; + } } }; @@ -1866,17 +1880,36 @@ if (typeof Piwik !== 'object') { var base = this.getLocation().origin + this.getLocation().pathname.match(new RegExp(regexMatchDir))[0]; return base + url; }, + isUrlToCurrentDomain: function (url) { + + var absoluteUrl = this.toAbsoluteUrl(url); + + if (!absoluteUrl) { + return false; + } + + var origin = this.getLocation().origin; + if (origin === absoluteUrl) { + return true; + } + + if (0 === String(absoluteUrl).indexOf(origin)) { + if (':' === String(absoluteUrl).substr(origin.length, 1)) { + return false; // url has port whereas origin has not => different URL + } + + return true; + } + + return false; + }, setHrefAttribute: function (node, url) { if (!node || !url) { return; } - if (node.setAttribute) { - node.setAttribute('href', url); - } else { - node.href = url; - } + query.setAnyAttribute(node, 'href', url); }, shouldIgnoreInteraction: function (targetNode) { @@ -2937,6 +2970,10 @@ if (typeof Piwik !== 'object') { * Link or Download? */ function getLinkType(className, href, isInLink) { + if (configTrackerUrl && href && 0 === String(href).indexOf(configTrackerUrl)) { + return 0; + } + // does class indicate whether it is an (explicit/forced) outlink or a download? var downloadPattern = getClassesRegExp(configDownloadClasses, 'download'), linkPattern = getClassesRegExp(configLinkClasses, 'link'), @@ -2990,6 +3027,12 @@ if (typeof Piwik !== 'object') { return; } + var href = query.getAttributeValueFromNode(sourceElement, 'href'); + + if (configTrackerUrl && href && 0 === String(href).indexOf(configTrackerUrl)) { + return; + } + // browsers, such as Safari, don't downcase hostname and href var originalSourceHostName = sourceElement.hostname || getHostName(sourceElement.href); var sourceHostName = originalSourceHostName.toLowerCase(); @@ -3044,34 +3087,47 @@ if (typeof Piwik !== 'object') { return configTrackerUrl + separator + request; } - function getContentInteractionToRequestIfPossible (anyNode, interaction, fallbackTarget) + function isNodeAuthorizedToTriggerInteraction(contentNode, interactedNode) { - if (!anyNode) { - return; - } - - var contentNode = content.findParentContentNode(anyNode); - - if (!contentNode) { - // we are not within a content block - return; + if (!contentNode || !interactedNode) { + return false; } var targetNode = content.findTargetNode(contentNode); if (content.shouldIgnoreInteraction(targetNode)) { // interaction should be ignored - return; + return false; } targetNode = content.findTargetNodeNoDefault(contentNode); - if (targetNode && !targetNode.contains(anyNode)) { + if (targetNode && !targetNode.contains(interactedNode)) { /** * There is a target node defined but the clicked element is not within the target node. example: *
YZ
* * The user clicked in this case on link Z and not on target Y */ + return false; + } + + return true; + } + + function getContentInteractionToRequestIfPossible (anyNode, interaction, fallbackTarget) + { + if (!anyNode) { + return; + } + + var contentNode = content.findParentContentNode(anyNode); + + if (!contentNode) { + // we are not within a content block + return; + } + + if (!isNodeAuthorizedToTriggerInteraction(contentNode, anyNode)) { return; } @@ -3110,43 +3166,109 @@ if (typeof Piwik !== 'object') { return false; } - function trackContentImpressionClickInteraction (targetNode) + function replaceHrefIfInternalLink(contentBlock) { - return function () { + if (!contentBlock) { + return false; + } - if (!targetNode || content.shouldIgnoreInteraction(targetNode)) { - return; - } + var targetNode = content.findTargetNode(contentBlock); - var link = getLinkIfShouldBeProcessed(targetNode); + if (!targetNode || content.shouldIgnoreInteraction(targetNode)) { + return false; + } - if (link && link.type) { - // click ignore, will be tracked via processClick, we do not want to track it twice + var link = getLinkIfShouldBeProcessed(targetNode); + if (linkTrackingInstalled && link && link.type) { - return link.type; + return false; // it is an outlink or download. + } + + if (query.isLinkElement(targetNode) && + query.hasNodeAttributeWithValue(targetNode, 'href')) { + var url = String(query.getAttributeValueFromNode(targetNode, 'href')); + + if (0 === url.indexOf('#')) { + return false; + } + + if (configTrackerUrl && 0 === url.indexOf(configTrackerUrl)) { + return true; + } + + if (!content.isUrlToCurrentDomain(url)) { + return false; } - var contentBlock = content.findParentContentNode(targetNode); var contentName = content.findContentName(contentBlock); var contentPiece = content.findContentPiece(contentBlock); var contentTarget = content.findContentTarget(contentBlock); - if (query.isLinkElement(targetNode) && - query.hasNodeAttributeWithValue(targetNode, 'href')) { - var url = query.getAttributeValueFromNode(targetNode, 'href'); + var targetUrl = buildContentInteractionTrackingRedirectUrl(url, 'click', contentName, contentPiece, contentTarget); - if (0 !== String(url).indexOf('#')) { + // make sure we still track the correct content target when an interaction is happening + if (!query.hasNodeAttributeWithValue(targetNode, content.CONTENT_TARGET_ATTR)) { + query.setAnyAttribute(targetNode, content.CONTENT_TARGET_ATTR, content.toAbsoluteUrl(url)); + } + // location.href does not respect target=_blank so we prefer to use this + content.setHrefAttribute(targetNode, targetUrl); - var targetUrl = buildContentInteractionTrackingRedirectUrl(url, 'click', contentName, contentPiece, contentTarget); + return true; + } - // location.href does not respect target=_blank so we prefer to use this - content.setHrefAttribute(targetNode, targetUrl); + return false; + } - return 'href'; - } + function replaceHrefsIfInternalLink(contentNodes) + { + if (!contentNodes || !contentNodes.length) { + return; + } + var index; + for (index = 0; index < contentNodes.length; index++) { + replaceHrefIfInternalLink(contentNodes[index]); + } + } + + function trackContentImpressionClickInteraction (targetNode) + { + return function (event) { + + if (!targetNode) { + return; + } + + var contentBlock = content.findParentContentNode(targetNode); + + var interactedElement; + if (event) { + interactedElement = event.target || event.srcElement; + } + if (!interactedElement) { + interactedElement = targetNode; + } + + if (!isNodeAuthorizedToTriggerInteraction(contentBlock, interactedElement)) { + return; } + var link = getLinkIfShouldBeProcessed(targetNode); + + if (linkTrackingInstalled && link && link.type) { + // click ignore, will be tracked via processClick, we do not want to track it twice + + return link.type; + } + + if (replaceHrefIfInternalLink(contentBlock)) { + return 'href'; + } + + var contentName = content.findContentName(contentBlock); + var contentPiece = content.findContentPiece(contentBlock); + var contentTarget = content.findContentTarget(contentBlock); + // click on any non link element, or on a link element that has not an href attribute or on an anchor var request = buildContentInteractionRequest('click', contentName, contentPiece, contentTarget); sendRequest(request, configTrackerPause); @@ -3171,7 +3293,6 @@ if (typeof Piwik !== 'object') { addEventListener(targetNode, 'click', trackContentImpressionClickInteraction(targetNode)); } - } } @@ -3200,6 +3321,7 @@ if (typeof Piwik !== 'object') { return []; } + replaceHrefsIfInternalLink(contentNodes); setupInteractionsTracking(contentNodes); var requests = []; @@ -3708,6 +3830,11 @@ if (typeof Piwik !== 'object') { setupInteractionsTracking: setupInteractionsTracking, trackContentImpressionClickInteraction: trackContentImpressionClickInteraction, internalIsNodeVisible: isVisible, + isNodeAuthorizedToTriggerInteraction: isNodeAuthorizedToTriggerInteraction, + replaceHrefIfInternalLink: replaceHrefIfInternalLink, + getConfigDownloadExtensions: function () { + return configDownloadExtensions; + }, enableTrackOnlyVisibleContent: function (checkOnScroll, timeIntervalInMs) { return enableTrackOnlyVisibleContent(checkOnScroll, timeIntervalInMs, this); }, @@ -3723,6 +3850,9 @@ if (typeof Piwik !== 'object') { clearEnableTrackOnlyVisibleContent: function () { isTrackOnlyVisibleContentEnabled = false; }, + disableLinkTracking: function () { + linkTrackingInstalled = false; + }, /**/ /** -- cgit v1.2.3