1/** 2 * Developer's Notice: 3 * 4 * Note: JS in this file (and this file itself) is not garunteed backwards compatibility. JS can be added, changed or removed at any time without notice. 5 * For more information see the `Backwards Compatibility Guidelines for Developers` section of the README.md file. 6 */ 7/** 8 * Handles: 9 * - JS Events handling 10 * 11 * @since 6.0.12 12 */ 13var ExactMetrics = function(){ 14 /* ExactMetrics JS events tracking works on all major browsers, including IE starting at IE 7, via polyfills for any major JS function used that 15 is not supported by at least 95% of the global and/or US browser marketshare. Currently, IE 7 & 8 which as of 2/14/17 have under 0.25% global marketshare, require 16 us to polyfill Array.prototype.lastIndexOf, and if they continue to drop, we might remove this polyfill at some point. In that case note that events tracking 17 for IE 7/8 will continue to work, with the exception of events tracking of downloads. */ 18 var lastClicked = []; 19 var internalAsOutboundCategory = ''; 20 21 this.setLastClicked = function(valuesArray,fieldsArray,tracked){ 22 valuesArray = typeof valuesArray !== 'undefined' ? valuesArray : []; 23 fieldsArray = typeof fieldsArray !== 'undefined' ? fieldsArray : []; 24 tracked = typeof tracked !== 'undefined' ? tracked : false; 25 26 lastClicked.valuesArray = valuesArray; 27 lastClicked.fieldsArray = fieldsArray; 28 }; 29 30 this.getLastClicked = function () { 31 return lastClicked; 32 }; 33 34 this.setInternalAsOutboundCategory = function( category ){ 35 internalAsOutboundCategory = category; 36 }; 37 38 this.getInternalAsOutboundCategory = function () { 39 return internalAsOutboundCategory; 40 }; 41 42 this.sendEvent = function ( fieldsArray ) { 43 __gaTrackerSend( [], fieldsArray ); 44 }; 45 46 function __gaTrackerIsDebug () { 47 if ( window.exactmetrics_debug_mode ) { 48 return true; 49 } else { 50 return false; 51 } 52 } 53 54 function __gaTrackerSend ( valuesArray, fieldsArray ) { 55 valuesArray = typeof valuesArray !== 'undefined' ? valuesArray : []; 56 fieldsArray = typeof fieldsArray !== 'undefined' ? fieldsArray : {}; 57 58 __gaTracker( 'send', fieldsArray ); 59 60 lastClicked.valuesArray = valuesArray; 61 lastClicked.fieldsArray = fieldsArray; 62 lastClicked.tracked = true; 63 __gaTrackerLog( 'Tracked: ' + valuesArray.type ); 64 __gaTrackerLog( lastClicked ); 65 } 66 67 function __gaTrackerNotSend ( valuesArray ) { 68 valuesArray = typeof valuesArray !== 'undefined' ? valuesArray : []; 69 70 lastClicked.valuesArray = valuesArray; 71 lastClicked.fieldsArray = []; 72 lastClicked.tracked = false; 73 __gaTrackerLog( 'Not Tracked: ' + valuesArray.exit ); 74 __gaTrackerLog( lastClicked ); 75 } 76 77 function __gaTrackerLog ( message ) { 78 if ( __gaTrackerIsDebug() ) { 79 console.dir( message ); 80 } 81 } 82 83 function __gaTrackerStringTrim( x ) { 84 return x.replace(/^\s+|\s+$/gm,''); 85 } 86 87 function __gaTrackerGetDomain() { 88 var i=0,currentdomain=document.domain,p=currentdomain.split('.'),s='_gd'+(new Date()).getTime(); 89 while(i<(p.length-1) && document.cookie.indexOf(s+'='+s)==-1){ 90 currentdomain = p.slice(-1-(++i)).join('.'); 91 document.cookie = s+"="+s+";domain="+currentdomain+";"; 92 } 93 document.cookie = s+"=;expires=Thu, 01 Jan 1970 00:00:01 GMT;domain="+currentdomain+";"; 94 return currentdomain; 95 } 96 97 function __gaTrackerGetExtension( extension ) { 98 extension = extension.toString(); 99 extension = extension.substring( 0, (extension.indexOf( "#" ) == -1 ) ? extension.length : extension.indexOf( "#" ) ); /* Remove the anchor at the end, if there is one */ 100 extension = extension.substring( 0, (extension.indexOf( "?" ) == -1 ) ? extension.length : extension.indexOf( "?" ) ); /* Remove the query after the file name, if there is one */ 101 extension = extension.substring( extension.lastIndexOf( "/" ) + 1, extension.length ); /* Remove everything before the last slash in the path */ 102 if ( extension.length > 0 && extension.indexOf('.') !== -1 ) { /* If there's a period left in the URL, then there's a extension. Else it is not a extension. */ 103 extension = extension.substring( extension.indexOf( "." ) + 1 ); /* Remove everything but what's after the first period */ 104 return extension; 105 } else { 106 return ""; 107 } 108 } 109 110 function __gaTrackerLoaded() { 111 return typeof(__gaTracker) !== 'undefined' && __gaTracker && __gaTracker.hasOwnProperty( "loaded" ) && __gaTracker.loaded == true; /* jshint ignore:line */ 112 } 113 114 function __gaTrackerTrackedClick( event ) { 115 return event.which == 1 || event.which == 2 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey; 116 } 117 118 function __gaTrackerGetDownloadExtensions() { 119 var download_extensions = []; 120 if ( typeof exactmetrics_frontend.download_extensions == 'string' ) { 121 download_extensions = exactmetrics_frontend.download_extensions.split(","); 122 } 123 return download_extensions; 124 } 125 126 function __gaTrackerGetInboundPaths() { 127 var inbound_paths = []; 128 if ( typeof exactmetrics_frontend.inbound_paths == 'string' ) { 129 inbound_paths = JSON.parse( exactmetrics_frontend.inbound_paths ); 130 } 131 132 return inbound_paths; 133 } 134 135 function __gaTrackerTrackedClickType( event ) { 136 if ( event.which == 1 ) { 137 return 'event.which=1'; 138 } else if ( event.which == 2 ) { 139 return 'event.which=2'; 140 } else if ( event.metaKey ){ 141 return 'metaKey'; 142 } else if ( event.ctrlKey ) { 143 return 'ctrlKey'; 144 } else if ( event.shiftKey ) { 145 return 'shiftKey'; 146 } else if ( event.altKey ) { 147 return 'altKey'; 148 } else { 149 return ''; 150 } 151 } 152 153 function __gaTrackerLinkType( el ) { 154 var download_extensions = __gaTrackerGetDownloadExtensions(); 155 var inbound_paths = __gaTrackerGetInboundPaths(); 156 var type = 'unknown'; 157 var link = el.href; 158 var extension = __gaTrackerGetExtension( el.href ); 159 var currentdomain = __gaTrackerGetDomain(); 160 var hostname = el.hostname; 161 var protocol = el.protocol; 162 var pathname = el.pathname; 163 link = link.toString(); 164 var index, len; 165 var category = el.getAttribute("data-vars-ga-category"); 166 167 if ( category ) { 168 return category; 169 } 170 171 if ( link.match( /^javascript\:/i ) ) { 172 type = 'internal'; /* if it's a JS link, it's internal */ 173 } else if ( protocol && protocol.length > 0 && ( __gaTrackerStringTrim( protocol ) == 'tel' || __gaTrackerStringTrim( protocol ) == 'tel:' ) ) { /* If it's a telephone link */ 174 type = "tel"; 175 } else if ( protocol && protocol.length > 0 && ( __gaTrackerStringTrim( protocol ) == 'mailto' || __gaTrackerStringTrim( protocol ) == 'mailto:' ) ) { /* If it's a email */ 176 type = "mailto"; 177 } else if ( hostname && currentdomain && hostname.length > 0 && currentdomain.length > 0 && ! hostname.endsWith( '.' + currentdomain) && hostname !== currentdomain ) { /* If it's a outbound */ 178 type = "external"; 179 }
179 else if ( pathname && JSON.stringify( inbound_paths ) != "{}" && pathname.length > 0 ) { /* If it's an internal as outbound */ 180 var inbound_paths_length = inbound_paths.length; 181 for ( var inbound_paths_index = 0; inbound_paths_index < inbound_paths_length; inbound_paths_index ++ ) { 182 if ( inbound_paths[ inbound_paths_index ].path && inbound_paths[ inbound_paths_index ].label && inbound_paths[ inbound_paths_index ].path.length > 0 && inbound_paths[ inbound_paths_index ].label.length > 0 && pathname.startsWith( inbound_paths[ inbound_paths_index ].path ) ) { 183 type = "internal-as-outbound"; 184 internalAsOutboundCategory = "outbound-link-" + inbound_paths[ inbound_paths_index ].label; 185 break; 186 } 187 } 188 /* Enable window.exactmetrics_experimental_mode at your own risk. We might eventually remove it. Also you may/can/will burn through GA quota for your property quickly. */ 189 } else if ( hostname && window.exactmetrics_experimental_mode && hostname.length > 0 && document.domain.length > 0 && hostname !== document.domain ) { /* If it's a cross-hostname link */ 190 type = "cross-hostname"; 191 } 192 193 if ( extension && ( type === 'unknown' || 'external' === type ) && download_extensions.length > 0 && extension.length > 0 ) { /* If it's a download */ 194 for ( index = 0, len = download_extensions.length; index < len; ++index ) { 195 if ( download_extensions[ index ].length > 0 && ( link.endsWith( download_extensions[ index ] ) || download_extensions[ index ] == extension ) ) { 196 type = "download"; 197 break; 198 } 199 } 200 } 201 202 if ( type === 'unknown' ) { 203 type = 'internal'; 204 } 205 return type; 206 } 207 208 function __gaTrackerLinkTarget( el, event ) { 209 210 /* Is actual target set and not _(self|parent|top)? */ 211 var target = ( el.target && !el.target.match( /^_(self|parent|top)$/i ) ) ? el.target : false;
212 213 /* Assume a target if Ctrl|shift|meta-click */ 214 if ( event.ctrlKey || event.shiftKey || event.metaKey || event.which == 2 ) { 215 target = "_blank"; 216 } 217 return target; 218 } 219 220 function __gaTrackerGetTitle( el ) { 221 if ( el.getAttribute("data-vars-ga-label") && el.getAttribute("data-vars-ga-label").replace(/\n/ig, '') ) { 222 return el.getAttribute("data-vars-ga-label").replace(/\n/ig, ''); 223 } else if ( el.title && el.title.replace(/\n/ig, '') ) { 224 return el.title.replace(/\n/ig, ''); 225 } else if ( el.innerText && el.innerText.replace(/\n/ig, '') ) { 226 return el.innerText.replace(/\n/ig, ''); 227 } else if ( el.getAttribute('aria-label') && el.getAttribute('aria-label').replace(/\n/ig, '') ) { 228 return el.getAttribute('aria-label').replace(/\n/ig, ''); 229 } else if ( el.alt && el.alt.replace(/\n/ig, '') ) { 230 return el.alt.replace(/\n/ig, ''); 231 } else if ( el.textContent && el.textContent.replace(/\n/ig, '') ) { 232 return el.textContent.replace(/\n/ig, ''); 233 } else { 234 return undefined; 235 } 236 } 237 238 function __gaTrackerGetInnerTitle( el ) { 239 var children = el.children; 240 var count = 0; 241 var child; 242 var value; 243 for (var i = 0; i < children.length; i++) { 244 child = children[i]; 245 value = __gaTrackerGetTitle( child ); 246 if ( value ) { 247 return value; 248 } 249 /* max search 100 elements to ensure performance */ 250 if ( count == 99 ) { 251 return undefined; 252 } 253 count++; 254 } 255 return undefined; 256 } 257 258 function __gaTrackerClickEvent( event ) { 259 var el = event.srcElement || event.target; 260 var valuesArray = []; 261 var fieldsArray; 262 263 /* Start Values Array */ 264 valuesArray.el = el; 265 valuesArray.ga_loaded = __gaTrackerLoaded(); 266 valuesArray.click_type = __gaTrackerTrackedClickType( event ); 267 268 /* If GA is blocked or not loaded, or not main|middle|touch click then don't track */ 269 if ( ! __gaTrackerLoaded() || ! __gaTrackerTrackedClick( event ) ) { 270 valuesArray.exit = 'loaded'; 271 __gaTrackerNotSend( valuesArray ); 272 return; 273 } 274 275 /* Loop up the DOM tree through parent elements if clicked element is not a link (eg: an image inside a link) */ 276 while ( el && (typeof el.tagName == 'undefined' || el.tagName.toLowerCase() != 'a' || ! el.href ) ) { 277 el = el.parentNode; 278 } 279 280 /* if a link with valid href has been clicked */ 281 if ( el && el.href && ! el.hasAttribute('xlink:href') ) { 282 var link = el.href; /* What link are we tracking */ 283 var extension = __gaTrackerGetExtension( el.href ); /* What extension is this link */ 284 var download_extensions = __gaTrackerGetDownloadExtensions(); /* Let's get the extensions to track */ 285 var inbound_paths = __gaTrackerGetInboundPaths(); /* Let's get the internal paths to track */ 286 var home_url = exactmetrics_frontend.home_url; /* Let's get the url to compare for external/internal use */ 287 var currentdomain = __gaTrackerGetDomain(); /* What domain are we on? */ 288 var type = __gaTrackerLinkType( el ); /* What type of link is this? */ 289 var target = __gaTrackerLinkTarget( el, event ); /* Is a new tab/window being opened? */ 290 var action = el.getAttribute("data-vars-ga-action"); 291 var label = el.getAttribute("data-vars-ga-label"); 292 293 /* Element */ 294 valuesArray.el = el; /* el is an a element so we can parse it */ 295 valuesArray.el_href = el.href; /* "http://example.com:3000/pathname/?search=test#hash" */ 296 valuesArray.el_protocol = el.protocol; /* "http:" */ 297 valuesArray.el_hostname = el.hostname; /* "example.com" */ 298 valuesArray.el_port = el.port; /* "3000" */ 299 valuesArray.el_pathname = el.pathname; /* "/pathname/" */ 300 valuesArray.el_search = el.search; /* "?search=test" */ 301 valuesArray.el_hash = el.hash; /* "#hash" */ 302 valuesArray.el_host = el.host; /* "example.com:3000" */ 303 304 /* Settings */ 305 valuesArray.debug_mode = __gaTrackerIsDebug(); /* "example.com:3000" */ 306 valuesArray.download_extensions = download_extensions; /* Let's get the extensions to track */ 307 valuesArray.inbound_paths = inbound_paths; /* Let's get the internal paths to track */ 308 valuesArray.home_url = home_url; /* Let's get the url to compare for external/internal use */ 309 310 /* Parsed/Logic */ 311 valuesArray.link = link; /* What link are we tracking */ 312 valuesArray.extension = extension; /* What extension is this link */ 313 valuesArray.type = type; /* What type of link is this */ 314 valuesArray.target = target; /* Is a new tab/window being opened? */ 315 valuesArray.title = __gaTrackerGetTitle( el ); /* Try link title, then text content */ 316 317 /* only find innerTitle if we need one */ 318 if ( ! valuesArray.label && ! valuesArray.title ) { 319 valuesArray.title = __gaTrackerGetInnerTitle( el ); 320 } 321 322 /* Let's track everything but internals (that aren't internal-as-externals) and javascript */
323 if ( type !== 'internal' && type !== 'javascript' ) { 324 325 var __gaTrackerHitBackRun = false; /* Tracker has not yet run */ 326 327 /* HitCallback to open link in same window after tracker */ 328 var __gaTrackerHitBack = function() { 329 /* Run the hitback only once */ 330 if ( __gaTrackerHitBackRun ){ 331 return; 332 } 333 __gaTrackerHitBackRun = true; 334 window.location.href = link; 335 }; 336 337 var __gaTrackerNoRedirectExternal = function() { 338 valuesArray.exit = 'external'; 339 __gaTrackerNotSend( valuesArray ); 340 }; 341 342 var __gaTrackerNoRedirectInboundAsExternal = function() { 343 valuesArray.exit = 'internal-as-outbound'; 344 __gaTrackerNotSend( valuesArray ); 345 }; 346 var __gaTrackerNoRedirectCrossHostname = function() { 347 valuesArray.exit = 'cross-hostname'; 348 __gaTrackerNotSend( valuesArray ); 349 }; 350 351 if ( target || type == 'mailto' || type == 'tel' ) { /* If target opens a new window then just track */ 352 if ( type == 'download' ) { 353 fieldsArray = { 354 hitType : 'event', 355 eventCategory : 'download', 356 eventAction : action || link, 357 eventLabel : label || valuesArray.title, 358 }; 359 360 __gaTrackerSend( valuesArray, fieldsArray ); 361 } else if ( type == 'tel' ) { 362 fieldsArray = { 363 hitType : 'event', 364 eventCategory : 'tel', 365 eventAction : action || link, 366 eventLabel : label || valuesArray.title.replace('tel:', ''), 367 }; 368 369 __gaTrackerSend( valuesArray, fieldsArray ); 370 } else if ( type == 'mailto' ) { 371 fieldsArray = { 372 hitType : 'event', 373 eventCategory : 'mailto', 374 eventAction : action || link, 375 eventLabel : label || valuesArray.title.replace('mailto:', ''), 376 }; 377 378 __gaTrackerSend( valuesArray, fieldsArray ); 379 } else if ( type == 'internal-as-outbound' ) { 380 fieldsArray = { 381 hitType : 'event', 382 eventCategory : internalAsOutboundCategory, 383 eventAction : action || link, 384 eventLabel : label || valuesArray.title, 385 }; 386 387 __gaTrackerSend( valuesArray, fieldsArray ); 388 } else if ( type == 'external' ) { 389 fieldsArray = { 390 hitType: 'event', 391 eventCategory:'outbound-link', 392 eventAction: action || link, 393 eventLabel: label || valuesArray.title, 394 }; 395 396 __gaTrackerSend( valuesArray, fieldsArray ); 397 } else if ( type == 'cross-hostname' ) { 398 fieldsArray = { 399 hitType: 'event', 400 eventCategory:'cross-hostname', 401 eventAction: action || link, 402 eventLabel: label || valuesArray.title, 403 }; 404 405 __gaTrackerSend( valuesArray, fieldsArray ); 406 } else { 407 if ( type && type != 'internal' ) { 408 fieldsArray = { 409 hitType: 'event', 410 eventCategory: type, 411 eventAction: action || link, 412 eventLabel: label || valuesArray.title, 413 }; 414 415 __gaTrackerSend( valuesArray, fieldsArray ); 416 } else { 417 valuesArray.exit = 'type'; 418 __gaTrackerNotSend( valuesArray ); 419 } 420 } 421 } else { 422 /* Prevent standard click, track then open */ 423 if ( type != 'cross-hostname' && type != 'external' && type != 'internal-as-outbound' ) { 424 if (! event.defaultPrevented ) { 425 if ( event.preventDefault ) { 426 event.preventDefault(); 427 } else { 428 event.returnValue = false; 429 } 430 } 431 } 432 433 if ( type == 'download' ) { 434 fieldsArray = { 435 hitType : 'event', 436 eventCategory : 'download', 437 eventAction : action || link, 438 eventLabel : label || valuesArray.title, 439 hitCallback : __gaTrackerHitBack, 440 }; 441 442 __gaTrackerSend( valuesArray, fieldsArray ); 443 } else if ( type == 'internal-as-outbound' ) { 444 window.onbeforeunload = function(e) { 445 if (! event.defaultPrevented ) { 446 if ( event.preventDefault ) { 447 event.preventDefault(); 448 } else { 449 event.returnValue = false; 450 } 451 } 452 453 fieldsArray = { 454 hitType : 'event', 455 eventCategory : internalAsOutboundCategory, 456 eventAction : action || link, 457 eventLabel : label || valuesArray.title, 458 hitCallback : __gaTrackerHitBack, 459 }; 460
461 if ( navigator.sendBeacon ) { 462 fieldsArray.transport = 'beacon'; 463 } 464 465 __gaTrackerSend( valuesArray, fieldsArray ); 466 setTimeout( __gaTrackerHitBack, 1000 ); 467 }; 468 } else if ( type == 'external' ) { 469 window.onbeforeunload = function(e) { 470 if (! event.defaultPrevented ) { 471 if ( event.preventDefault ) { 472 event.preventDefault(); 473 } else { 474 event.returnValue = false; 475 } 476 } 477 478 fieldsArray = { 479 hitType : 'event', 480 eventCategory : 'outbound-link', 481 eventAction : action || link, 482 eventLabel : label || valuesArray.title, 483 hitCallback : __gaTrackerHitBack, 484 }; 485 486 if ( navigator.sendBeacon ) { 487 fieldsArray.transport = 'beacon'; 488 } 489 490 __gaTrackerSend( valuesArray, fieldsArray ); 491 setTimeout( __gaTrackerHitBack, 1000 ); 492 }; 493 } else if ( type == 'cross-hostname' ) { 494 window.onbeforeunload = function(e) { 495 if (! event.defaultPrevented ) { 496 if ( event.preventDefault ) { 497 event.preventDefault(); 498 } else { 499 event.returnValue = false; 500 } 501 } 502 503 fieldsArray = { 504 hitType : 'event', 505 eventCategory : 'cross-hostname', 506 eventAction : action || link, 507 eventLabel : label || valuesArray.title, 508 hitCallback : __gaTrackerHitBack, 509 }; 510 511 if ( navigator.sendBeacon ) { 512 fieldsArray.transport = 'beacon'; 513 } 514 515 __gaTrackerSend( valuesArray, fieldsArray ); 516 setTimeout( __gaTrackerHitBack, 1000 ); 517 }; 518 } else { 519 if ( type && type !== 'internal' ) { 520 fieldsArray = { 521 hitType: 'event', 522 eventCategory: type, 523 eventAction: action || link, 524 eventLabel: label || valuesArray.title, 525 hitCallback : __gaTrackerHitBack, 526 }; 527 528 __gaTrackerSend( valuesArray, fieldsArray ); 529 } else { 530 valuesArray.exit = 'type'; 531 __gaTrackerNotSend( valuesArray ); 532 } 533 } 534 535 if ( type != 'external' && type != 'cross-hostname' && type != 'internal-as-outbound' ) { 536 /* Run hitCallback again if GA takes longer than 1 second */ 537 setTimeout( __gaTrackerHitBack, 1000 ); 538 } else { 539 if ( type == 'external' ) { 540 setTimeout( __gaTrackerNoRedirectExternal, 1100 ); 541 } else if ( type == 'cross-hostname' ) { 542 setTimeout( __gaTrackerNoRedirectCrossHostname, 1100 ); 543 } else { 544 setTimeout( __gaTrackerNoRedirectInboundAsExternal, 1100 ); 545 } 546 } 547 } 548 } else { 549 valuesArray.exit = 'internal'; 550 __gaTrackerNotSend( valuesArray ); 551 } 552 } else { 553 valuesArray.exit = 'notlink'; 554 __gaTrackerNotSend( valuesArray ); 555 } 556 } 557 var prevHash = window.location.hash; 558 function __gaTrackerHashChangeEvent() { 559 /* Todo: Ready this section for JS unit testing */ 560 if ( exactmetrics_frontend.hash_tracking === "true" && prevHash != window.location.hash ) { 561 prevHash = window.location.hash; 562 __gaTracker('set', 'page', location.pathname + location.search + location.hash ); 563 __gaTracker('send', 'pageview' ); 564 __gaTrackerLog( "Hash change to: " + location.pathname + location.search + location.hash ); 565 } else { 566 __gaTrackerLog( "Hash change to (untracked): " + location.pathname + location.search + location.hash ); 567 } 568 } 569 570 /* Attach the event to all clicks in the document after page has loaded */ 571 var __gaTrackerWindow = window; 572 if ( __gaTrackerWindow.addEventListener ) { 573 __gaTrackerWindow.addEventListener( 574 "load", 575 function() { 576 document.body.addEventListener( 577 "click", 578 __gaTrackerClickEvent, 579 false 580 ); 581 }, 582 false 583 ); 584 window.addEventListener("hashchange", __gaTrackerHashChangeEvent, false ); 585 } else { 586 if ( __gaTrackerWindow.attachEvent ) { 587 __gaTrackerWindow.attachEvent( 588 "onload", 589 function() { 590 document.body.attachEvent( "onclick", __gaTrackerClickEvent); 591 } 592 ); 593 window.attachEvent( "onhashchange", __gaTrackerHashChangeEvent); 594 } 595 } 596 597 if (typeof String.prototype.endsWith !== 'function') { 598 String.prototype.endsWith = function(suffix) { 599 return this.indexOf(suffix, this.length - suffix.length) !== -1; 600 }; 601 } 602 if (typeof String.prototype.startsWith !== 'function') { 603 String.prototype.startsWith = function(prefix) { 604 return this.indexOf(prefix) === 0; 605 }; 606 } 607 608 if ( typeof Array.prototype.lastIndexOf !== 'function' ) { 609 Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) { 610 'use strict'; 611 612 if (this === void 0 || this === null) { 613 throw new TypeError(); 614 } 615 616 var n, k, 617 t = Object(this), 618 len = t.length >>> 0; /* jshint ignore:line */ 619 if (len === 0) { 620 return -1; 621 } 622 623 n = len - 1; 624 if (arguments.length > 1) {
625 n = Number(arguments[1]); 626 if (n != n) { 627 n = 0; 628 } 629 else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) { /* jshint ignore:line */ 630 n = (n > 0 || -1) * Math.floor(Math.abs(n)); 631 } 632 } 633 634 for (k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); k >= 0; k--) { 635 if (k in t && t[k] === searchElement) { 636 return k; 637 } 638 } 639 return -1; 640 }; 641 } 642}; 643var ExactMetricsObject = new ExactMetrics();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.