Skip to content
Permalink
4.x-dev
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
* Update piwik.js

update * to originHost

* rebuilt piwik.js

* rebuilt piwik.js

* Update piwik.js

update protocol

* Update js/piwik.js

Co-authored-by: Stefan Giehl <stefan@matomo.org>

* update merge

update merge

* Delete piwik.min.js

* Delete piwik.min.js

Co-authored-by: peterhashair <peterhashair@users.noreply.github.com>
Co-authored-by: Stefan Giehl <stefan@matomo.org>
67 contributors

Users who have contributed to this file

@tsteur @robocoder @mattab @sgiehl @diosmosis @peterhashair @Findus23 @justinvelluppillai @sslavic @flamisz @dandv @BeezyT
7880 lines (6620 sloc) 314 KB
/*!
* Matomo - free/libre analytics platform
*
* JavaScript tracking client
*
* @link https://piwik.org
* @source https://github.com/matomo-org/matomo/blob/master/js/piwik.js
* @license https://piwik.org/free-software/bsd/ BSD-3 Clause (also in js/LICENSE.txt)
* @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
*/
// NOTE: if you change this above Piwik comment block, you must also change `$byteStart` in js/tracker.php
// Refer to README.md for build instructions when minifying this file for distribution.
/*
* Browser [In]Compatibility
* - minimum required ECMAScript: ECMA-262, edition 3
*
* Incompatible with these (and earlier) versions of:
* - IE4 - try..catch and for..in introduced in IE5
* - IE5 - named anonymous functions, array.push, encodeURIComponent, decodeURIComponent, and getElementsByTagName introduced in IE5.5
* - IE6 and 7 - window.JSON introduced in IE8
* - Firefox 1.0 and Netscape 8.x - FF1.5 adds array.indexOf, among other things
* - Mozilla 1.7 and Netscape 6.x-7.x
* - Netscape 4.8
* - Opera 6 - Error object (and Presto) introduced in Opera 7
* - Opera 7
*/
/* startjslint */
/*jslint browser:true, plusplus:true, vars:true, nomen:true, evil:true, regexp: false, bitwise: true, white: true */
/*global window */
/*global unescape */
/*global ActiveXObject */
/*global Blob */
/*members Piwik, Matomo, encodeURIComponent, decodeURIComponent, getElementsByTagName,
shift, unshift, piwikAsyncInit, matomoAsyncInit, matomoPluginAsyncInit , frameElement, self, hasFocus,
createElement, appendChild, characterSet, charset, all, piwik_log, AnalyticsTracker,
addEventListener, attachEvent, removeEventListener, detachEvent, disableCookies, setCookieConsentGiven,
areCookiesEnabled, getRememberedCookieConsent, rememberCookieConsentGiven, forgetCookieConsentGiven, requireCookieConsent,
cookie, domain, readyState, documentElement, doScroll, title, text, contentWindow, postMessage,
location, top, onerror, document, referrer, parent, links, href, protocol, name,
performance, mozPerformance, msPerformance, webkitPerformance, timing, getEntriesByType, connectEnd, requestStart,
responseStart, responseEnd, fetchStart, domInteractive, domLoading, domComplete, loadEventStart, loadEventEnd,
event, which, button, srcElement, type, target, data,
parentNode, tagName, hostname, className,
userAgent, cookieEnabled, sendBeacon, platform, mimeTypes, enabledPlugin, javaEnabled,
userAgentData, getHighEntropyValues, brands, uaFullVersion, fullVersionList,
serviceWorker, ready, then, sync, register,
XMLHttpRequest, ActiveXObject, open, setRequestHeader, onreadystatechange, send, readyState, status,
getTime, getTimeAlias, setTime, toGMTString, getHours, getMinutes, getSeconds,
toLowerCase, toUpperCase, charAt, indexOf, lastIndexOf, split, slice,
onload, src,
min, round, random, floor,
exec, success, trackerUrl, isSendBeacon, xhr,
res, width, height,
pdf, qt, realp, wma, fla, java, ag, showModalDialog,
_rcn, _rck, _refts, _ref,
maq_initial_value, maq_opted_in, maq_optout_by_default, maq_url,
initialized, hook, getHook, resetUserId, getVisitorId, getVisitorInfo, setUserId, getUserId, setSiteId, getSiteId, setTrackerUrl, getTrackerUrl, appendToTrackingUrl, getRequest, addPlugin,
getAttributionInfo, getAttributionCampaignName, getAttributionCampaignKeyword,
getAttributionReferrerTimestamp, getAttributionReferrerUrl,
setCustomData, getCustomData,
setCustomRequestProcessing,
setCustomVariable, getCustomVariable, deleteCustomVariable, storeCustomVariablesInCookie, setCustomDimension, getCustomDimension,
deleteCustomVariables, deleteCustomDimension, setDownloadExtensions, addDownloadExtensions, removeDownloadExtensions,
setDomains, setIgnoreClasses, setRequestMethod, setRequestContentType, setGenerationTimeMs, setPagePerformanceTiming,
setReferrerUrl, setCustomUrl, setAPIUrl, setDocumentTitle, setPageViewId, getPiwikUrl, getMatomoUrl, getCurrentUrl,
setExcludedReferrers, getExcludedReferrers,
setDownloadClasses, setLinkClasses,
setCampaignNameKey, setCampaignKeywordKey,
getConsentRequestsQueue, requireConsent, getRememberedConsent, hasRememberedConsent, isConsentRequired,
setConsentGiven, rememberConsentGiven, forgetConsentGiven, unload, hasConsent,
discardHashTag, alwaysUseSendBeacon, disableAlwaysUseSendBeacon, isUsingAlwaysUseSendBeacon,
setCookieNamePrefix, setCookieDomain, setCookiePath, setSecureCookie, setVisitorIdCookie, getCookieDomain, hasCookies, setSessionCookie,
setVisitorCookieTimeout, setSessionCookieTimeout, setReferralCookieTimeout, getCookie, getCookiePath, getSessionCookieTimeout,
setExcludedQueryParams, setConversionAttributionFirstReferrer, tracker, request,
disablePerformanceTracking, maq_confirm_opted_in,
doNotTrack, setDoNotTrack, msDoNotTrack, getValuesFromVisitorIdCookie,
enableCrossDomainLinking, disableCrossDomainLinking, isCrossDomainLinkingEnabled, setCrossDomainLinkingTimeout, getCrossDomainLinkingUrlParameter,
addListener, enableLinkTracking, disableBrowserFeatureDetection, enableBrowserFeatureDetection, enableJSErrorTracking, setLinkTrackingTimer, getLinkTrackingTimer,
enableHeartBeatTimer, disableHeartBeatTimer, killFrame, redirectFile, setCountPreRendered, setVisitStandardLength,
trackGoal, trackLink, trackPageView, getNumTrackedPageViews, trackRequest, ping, queueRequest, trackSiteSearch, trackEvent,
requests, timeout, enabled, sendRequests, queueRequest, canQueue, pushMultiple, disableQueueRequest,setRequestQueueInterval,interval,getRequestQueue, getJavascriptErrors, unsetPageIsUnloading,
setEcommerceView, getEcommerceItems, addEcommerceItem, removeEcommerceItem, clearEcommerceCart, trackEcommerceOrder, trackEcommerceCartUpdate,
deleteCookie, deleteCookies, offsetTop, offsetLeft, offsetHeight, offsetWidth, nodeType, defaultView,
innerHTML, scrollLeft, scrollTop, currentStyle, getComputedStyle, querySelectorAll, splice,
getAttribute, hasAttribute, attributes, nodeName, findContentNodes, findContentNodes, findContentNodesWithinNode,
findPieceNode, findTargetNodeNoDefault, findTargetNode, findContentPiece, children, hasNodeCssClass,
getAttributeValueFromNode, hasNodeAttributeWithValue, hasNodeAttribute, findNodesByTagName, findMultiple,
makeNodesUnique, concat, find, htmlCollectionToArray, offsetParent, value, nodeValue, findNodesHavingAttribute,
findFirstNodeHavingAttribute, findFirstNodeHavingAttributeWithValue, getElementsByClassName,
findNodesHavingCssClass, findFirstNodeHavingClass, isLinkElement, findParentContentNode, removeDomainIfIsInLink,
findContentName, findMediaUrlInNode, toAbsoluteUrl, findContentTarget, getLocation, origin, host, isSameDomain,
search, trim, getBoundingClientRect, bottom, right, left, innerWidth, innerHeight, clientWidth, clientHeight,
isOrWasNodeInViewport, isNodeVisible, buildInteractionRequestParams, buildImpressionRequestParams,
shouldIgnoreInteraction, setHrefAttribute, setAttribute, buildContentBlock, collectContent, setLocation,
CONTENT_ATTR, CONTENT_CLASS, LEGACY_CONTENT_CLASS, CONTENT_NAME_ATTR, CONTENT_PIECE_ATTR, CONTENT_PIECE_CLASS, LEGACY_CONTENT_PIECE_CLASS,
CONTENT_TARGET_ATTR, CONTENT_TARGET_CLASS, LEGACY_CONTENT_TARGET_CLASS, CONTENT_IGNOREINTERACTION_ATTR, CONTENT_IGNOREINTERACTION_CLASS, LEGACY_CONTENT_IGNOREINTERACTION_CLASS,
trackCallbackOnLoad, trackCallbackOnReady, buildContentImpressionsRequests, wasContentImpressionAlreadyTracked,
getQuery, getContent, setVisitorId, getContentImpressionsRequestsFromNodes,
buildContentInteractionRequestNode, buildContentInteractionRequest, buildContentImpressionRequest,
appendContentInteractionToRequestIfPossible, setupInteractionsTracking, trackContentImpressionClickInteraction,
internalIsNodeVisible, clearTrackedContentImpressions, getTrackerUrl, trackAllContentImpressions,
getTrackedContentImpressions, getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet,
contentInteractionTrackingSetupDone, contains, match, pathname, piece, trackContentInteractionNode,
trackContentInteractionNode, trackContentImpressionsWithinNode, trackContentImpression,
enableTrackOnlyVisibleContent, trackContentInteraction, clearEnableTrackOnlyVisibleContent, logAllContentBlocksOnPage,
trackVisibleContentImpressions, isTrackOnlyVisibleContentEnabled, port, isUrlToCurrentDomain, matomoTrackers,
isNodeAuthorizedToTriggerInteraction, getConfigDownloadExtensions, disableLinkTracking,
substr, setAnyAttribute, max, abs, childNodes, compareDocumentPosition, body,
getConfigVisitorCookieTimeout, getRemainingVisitorCookieTimeout, getDomains, getConfigCookiePath,
getConfigCookieSameSite, getCustomPagePerformanceTiming, setCookieSameSite,
getConfigIdPageView, newVisitor, uuid, createTs, currentVisitTs,
"", "\b", "\t", "\n", "\f", "\r", "\"", "\\", apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace,
sort, slice, stringify, test, toJSON, toString, valueOf, objectToJSON, addTracker, removeAllAsyncTrackersButFirst,
optUserOut, forgetUserOptOut, isUserOptedOut, withCredentials, visibilityState
*/
/*global _paq:true */
/*members push */
/*global Piwik:true */
/*global Matomo:true */
/*members addPlugin, getTracker, getAsyncTracker, getAsyncTrackers, addTracker, trigger, on, off, retryMissedPluginCalls,
DOM, onLoad, onReady, isNodeVisible, isOrWasNodeVisible, JSON */
/*global Matomo_Overlay_Client */
/*global AnalyticsTracker:true */
/*members initialize */
/*global define */
/*global console */
/*members amd */
/*members error */
/*members log */
// asynchronous tracker (or proxy)
if (typeof _paq !== 'object') {
_paq = [];
}
// Matomo singleton and namespace
if (typeof window.Matomo !== 'object') {
window.Matomo = window.Piwik = (function () {
'use strict';
/************************************************************
* Private data
************************************************************/
var expireDateTime,
/* plugins */
plugins = {},
eventHandlers = {},
/* alias frequently used globals for added minification */
documentAlias = document,
navigatorAlias = navigator,
screenAlias = screen,
windowAlias = window,
/* performance timing */
performanceAlias = windowAlias.performance || windowAlias.mozPerformance || windowAlias.msPerformance || windowAlias.webkitPerformance,
/* encode */
encodeWrapper = windowAlias.encodeURIComponent,
/* decode */
decodeWrapper = windowAlias.decodeURIComponent,
/* urldecode */
urldecode = unescape,
/* asynchronous tracker */
asyncTrackers = [],
/* iterator */
iterator,
/* local Matomo */
Matomo,
missedPluginTrackerCalls = [],
coreConsentCounter = 0,
coreHeartBeatCounter = 0,
trackerIdCounter = 0,
isPageUnloading = false;
/************************************************************
* Private methods
************************************************************/
/**
* See https://github.com/matomo-org/matomo/issues/8413
* To prevent Javascript Error: Uncaught URIError: URI malformed when encoding is not UTF-8. Use this method
* instead of decodeWrapper if a text could contain any non UTF-8 encoded characters eg
* a URL like http://apache.matomo/test.html?%F6%E4%FC or a link like
* <a href="test-with-%F6%E4%FC/story/0">(encoded iso-8859-1 URL)</a>
*/
function safeDecodeWrapper(url)
{
try {
return decodeWrapper(url);
} catch (e) {
return unescape(url);
}
}
/*
* Is property defined?
*/
function isDefined(property) {
// workaround https://github.com/douglascrockford/JSLint/commit/24f63ada2f9d7ad65afc90e6d949f631935c2480
var propertyType = typeof property;
return propertyType !== 'undefined';
}
/*
* Is property a function?
*/
function isFunction(property) {
return typeof property === 'function';
}
/*
* Is property an object?
*
* @return bool Returns true if property is null, an Object, or subclass of Object (i.e., an instanceof String, Date, etc.)
*/
function isObject(property) {
return typeof property === 'object';
}
/*
* Is property a string?
*/
function isString(property) {
return typeof property === 'string' || property instanceof String;
}
/*
* Is property a string?
*/
function isNumber(property) {
return typeof property === 'number' || property instanceof Number;
}
/*
* Is property a string?
*/
function isNumberOrHasLength(property) {
return isDefined(property) && (isNumber(property) || (isString(property) && property.length));
}
function isObjectEmpty(property)
{
if (!property) {
return true;
}
var i;
for (i in property) {
if (Object.prototype.hasOwnProperty.call(property, i)) {
return false;
}
}
return true;
}
/**
* Logs an error in the console.
* Note: it does not generate a JavaScript error, so make sure to also generate an error if needed.
* @param message
*/
function logConsoleError(message) {
// needed to write it this way for jslint
var consoleType = typeof console;
if (consoleType !== 'undefined' && console && console.error) {
console.error(message);
}
}
/*
* apply wrapper
*
* @param array parameterArray An array comprising either:
* [ 'methodName', optional_parameters ]
* or:
* [ functionObject, optional_parameters ]
*/
function apply() {
var i, j, f, parameterArray, trackerCall;
for (i = 0; i < arguments.length; i += 1) {
trackerCall = null;
if (arguments[i] && arguments[i].slice) {
trackerCall = arguments[i].slice();
}
parameterArray = arguments[i];
f = parameterArray.shift();
var fParts, context;
var isStaticPluginCall = isString(f) && f.indexOf('::') > 0;
if (isStaticPluginCall) {
// a static method will not be called on a tracker and is not dependent on the existence of a
// tracker etc
fParts = f.split('::');
context = fParts[0];
f = fParts[1];
if ('object' === typeof Matomo[context] && 'function' === typeof Matomo[context][f]) {
Matomo[context][f].apply(Matomo[context], parameterArray);
} else if (trackerCall) {
// we try to call that method again later as the plugin might not be loaded yet
// a plugin can call "Matomo.retryMissedPluginCalls();" once it has been loaded and then the
// method call to "Matomo[context][f]" may be executed
missedPluginTrackerCalls.push(trackerCall);
}
} else {
for (j = 0; j < asyncTrackers.length; j++) {
if (isString(f)) {
context = asyncTrackers[j];
var isPluginTrackerCall = f.indexOf('.') > 0;
if (isPluginTrackerCall) {
fParts = f.split('.');
if (context && 'object' === typeof context[fParts[0]]) {
context = context[fParts[0]];
f = fParts[1];
} else if (trackerCall) {
// we try to call that method again later as the plugin might not be loaded yet
missedPluginTrackerCalls.push(trackerCall);
break;
}
}
if (context[f]) {
context[f].apply(context, parameterArray);
} else {
var message = 'The method \'' + f + '\' was not found in "_paq" variable. Please have a look at the Matomo tracker documentation: https://developer.matomo.org/api-reference/tracking-javascript';
logConsoleError(message);
if (!isPluginTrackerCall) {
// do not trigger an error if it is a call to a plugin as the plugin may just not be
// loaded yet etc
throw new TypeError(message);
}
}
if (f === 'addTracker') {
// addTracker adds an entry to asyncTrackers and would otherwise result in an endless loop
break;
}
if (f === 'setTrackerUrl' || f === 'setSiteId') {
// these two methods should be only executed on the first tracker
break;
}
} else {
f.apply(asyncTrackers[j], parameterArray);
}
}
}
}
}
/*
* Cross-browser helper function to add event handler
*/
function addEventListener(element, eventType, eventHandler, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventType, eventHandler, useCapture);
return true;
}
if (element.attachEvent) {
return element.attachEvent('on' + eventType, eventHandler);
}
element['on' + eventType] = eventHandler;
}
function trackCallbackOnLoad(callback)
{
if (documentAlias.readyState === 'complete') {
callback();
} else if (windowAlias.addEventListener) {
windowAlias.addEventListener('load', callback, false);
} else if (windowAlias.attachEvent) {
windowAlias.attachEvent('onload', callback);
}
}
function trackCallbackOnReady(callback)
{
var loaded = false;
if (documentAlias.attachEvent) {
loaded = documentAlias.readyState === 'complete';
} else {
loaded = documentAlias.readyState !== 'loading';
}
if (loaded) {
callback();
return;
}
var _timer;
if (documentAlias.addEventListener) {
addEventListener(documentAlias, 'DOMContentLoaded', function ready() {
documentAlias.removeEventListener('DOMContentLoaded', ready, false);
if (!loaded) {
loaded = true;
callback();
}
});
} else if (documentAlias.attachEvent) {
documentAlias.attachEvent('onreadystatechange', function ready() {
if (documentAlias.readyState === 'complete') {
documentAlias.detachEvent('onreadystatechange', ready);
if (!loaded) {
loaded = true;
callback();
}
}
});
if (documentAlias.documentElement.doScroll && windowAlias === windowAlias.top) {
(function ready() {
if (!loaded) {
try {
documentAlias.documentElement.doScroll('left');
} catch (error) {
setTimeout(ready, 0);
return;
}
loaded = true;
callback();
}
}());
}
}
// fallback
addEventListener(windowAlias, 'load', function () {
if (!loaded) {
loaded = true;
callback();
}
}, false);
}
/*
* Call plugin hook methods
*/
function executePluginMethod(methodName, params, callback) {
if (!methodName) {
return '';
}
var result = '',
i,
pluginMethod, value, isFunction;
for (i in plugins) {
if (Object.prototype.hasOwnProperty.call(plugins, i)) {
isFunction = plugins[i] && 'function' === typeof plugins[i][methodName];
if (isFunction) {
pluginMethod = plugins[i][methodName];
value = pluginMethod(params || {}, callback);
if (value) {
result += value;
}
}
}
}
return result;
}
/*
* Handle beforeunload event
*
* Subject to Safari's "Runaway JavaScript Timer" and
* Chrome V8 extension that terminates JS that exhibits
* "slow unload", i.e., calling getTime() > 1000 times
*/
function beforeUnloadHandler(event) {
var now;
isPageUnloading = true;
executePluginMethod('unload');
now = new Date();
var aliasTime = now.getTimeAlias();
if ((expireDateTime - aliasTime) > 3000) {
expireDateTime = aliasTime + 3000;
}
/*
* Delay/pause (blocks UI)
*/
if (expireDateTime) {
// the things we do for backwards compatibility...
// in ECMA-262 5th ed., we could simply use:
// while (Date.now() < expireDateTime) { }
do {
now = new Date();
} while (now.getTimeAlias() < expireDateTime);
}
}
/*
* Load JavaScript file (asynchronously)
*/
function loadScript(src, onLoad) {
var script = documentAlias.createElement('script');
script.type = 'text/javascript';
script.src = src;
if (script.readyState) {
script.onreadystatechange = function () {
var state = this.readyState;
if (state === 'loaded' || state === 'complete') {
script.onreadystatechange = null;
onLoad();
}
};
} else {
script.onload = onLoad;
}
documentAlias.getElementsByTagName('head')[0].appendChild(script);
}
/*
* Get page referrer
*/
function getReferrer() {
var referrer = '';
try {
referrer = windowAlias.top.document.referrer;
} catch (e) {
if (windowAlias.parent) {
try {
referrer = windowAlias.parent.document.referrer;
} catch (e2) {
referrer = '';
}
}
}
if (referrer === '') {
referrer = documentAlias.referrer;
}
return referrer;
}
/*
* Extract scheme/protocol from URL
*/
function getProtocolScheme(url) {
var e = new RegExp('^([a-z]+):'),
matches = e.exec(url);
return matches ? matches[1] : null;
}
/*
* Extract hostname from URL
*/
function getHostName(url) {
// scheme : // [username [: password] @] hostame [: port] [/ [path] [? query] [# fragment]]
var e = new RegExp('^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)'),
matches = e.exec(url);
return matches ? matches[1] : url;
}
function isPositiveNumberString(str) {
// !isNaN(str) could be used but does not cover '03' (octal) and '0xA' (hex)
// nor negative numbers
return (/^[0-9][0-9]*(\.[0-9]+)?$/).test(str);
}
function filterIn(object, byFunction) {
var result = {}, k;
for (k in object) {
if (object.hasOwnProperty(k) && byFunction(object[k])) {
result[k] = object[k];
}
}
return result;
}
function onlyPositiveIntegers(data) {
var result = {}, k;
for (k in data) {
if (data.hasOwnProperty(k)) {
if (isPositiveNumberString(data[k])) {
result[k] = Math.round(data[k]);
} else {
throw new Error('Parameter "' + k + '" provided value "' + data[k] +
'" is not valid. Please provide a numeric value.');
}
}
}
return result;
}
function queryStringify(data) {
var queryString = '', k;
for (k in data) {
if (data.hasOwnProperty(k)) {
queryString += '&' + encodeWrapper(k) + '=' + encodeWrapper(data[k]);
}
}
return queryString;
}
function stringStartsWith(str, prefix) {
str = String(str);
return str.lastIndexOf(prefix, 0) === 0;
}
function stringEndsWith(str, suffix) {
str = String(str);
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
function stringContains(str, needle) {
str = String(str);
return str.indexOf(needle) !== -1;
}
function removeCharactersFromEndOfString(str, numCharactersToRemove) {
str = String(str);
return str.substr(0, str.length - numCharactersToRemove);
}
/**
* We do not check whether URL contains already url parameter, please use removeUrlParameter() if needed
* before calling this method.
* This method makes sure to append URL parameters before a possible hash. Will escape (encode URI component)
* the set name and value
*/
function addUrlParameter(url, name, value) {
url = String(url);
if (!value) {
value = '';
}
var hashPos = url.indexOf('#');
var urlLength = url.length;
if (hashPos === -1) {
hashPos = urlLength;
}
var baseUrl = url.substr(0, hashPos);
var urlHash = url.substr(hashPos, urlLength - hashPos);
if (baseUrl.indexOf('?') === -1) {
baseUrl += '?';
} else if (!stringEndsWith(baseUrl, '?')) {
baseUrl += '&';
}
// nothing to if ends with ?
return baseUrl + encodeWrapper(name) + '=' + encodeWrapper(value) + urlHash;
}
function removeUrlParameter(url, name) {
url = String(url);
if (url.indexOf('?' + name + '=') === -1 && url.indexOf('&' + name + '=') === -1) {
// nothing to remove, url does not contain this parameter
return url;
}
var searchPos = url.indexOf('?');
if (searchPos === -1) {
// nothing to remove, no query parameters
return url;
}
var queryString = url.substr(searchPos + 1);
var baseUrl = url.substr(0, searchPos);
if (queryString) {
var urlHash = '';
var hashPos = queryString.indexOf('#');
if (hashPos !== -1) {
urlHash = queryString.substr(hashPos + 1);
queryString = queryString.substr(0, hashPos);
}
var param;
var paramsArr = queryString.split('&');
var i = paramsArr.length - 1;
for (i; i >= 0; i--) {
param = paramsArr[i].split('=')[0];
if (param === name) {
paramsArr.splice(i, 1);
}
}
var newQueryString = paramsArr.join('&');
if