1var commonUtils = { 2 hasClass: function(node, className) { 3 if (node.nodeType !== Node.ELEMENT_NODE) { return false; } 4 if (node.classList) { return node.classList.contains(className); } 5 6 var classNames = (node.getAttribute('class') || '').split(/\s/); 7 return classNames.indexOf(className) >= 0; 8 }, 9 addClassTo: function(node, className) { 10 if (node.nodeType !== Node.ELEMENT_NODE) { return; } 11 12 if(!commonUtils.hasClass(node, className)) { 13 if (node.classList) { 14 node.classList.add(className); 15 } else { 16 node.className = (node.className || '') + ' ' + className; 17 } 18 } 19 }, 20 removeClassFrom: function(node, className) { 21 if (node.nodeType !== Node.ELEMENT_NODE) { return; } 22 23 if(commonUtils.hasClass(node, className)) { 24 if (node.classList) { 25 node.classList.remove(className); 26 } else { 27 var classNames = (node.getAttribute('class') || '').split(' '); 28 var index = classNames.indexOf(className); 29 if (index >= 0) { classNames.splice(index, 1); } 30 node.setAttribute('class', classNames.join(' ')); 31 } 32 } 33 }, 34 getImageWidth: function (img, onServer) { 35 if (img.jquery) { img = img[0]; } 36 var result = null; 37 if (onServer) { 38 var pfDataWidth = img.getAttribute('pf-data-width') || img.getAttribute('data-pf_rect_width'); 39 if (pfDataWidth) { result = parseInt(pfDataWidth, 10); } 40 } else if (img.getAttribute('data-pf_rect_width')) { 41 result = parseInt(img.getAttribute('data-pf_rect_width'), 10); 42 } 43 44 /* 45 Fetch current height/width if value is zero as firefox 46 sets the width/height as zero for some images when we initially persist 47 the values 48 */ 49 if (result === null || result === 0 || typeof result != 'number' || isNaN(result)) { 50 result = img.getBoundingClientRect().width; 51 } 52 return result; 53 }, 54 55 getImageHeight: function (img, onServer) { 56 if (img.jquery) { img = img[0]; } 57 var result = null; 58 if (onServer) { 59 var pfDataHeight = img.getAttribute('pf-data-height') || img.getAttribute('data-pf_rect_height'); 60 if (pfDataHeight) { result = parseInt(pfDataHeight, 10); } 61 } else if (img.getAttribute('data-pf_rect_height')) { 62 result = parseInt(img.getAttribute('data-pf_rect_height'), 10); 63 } 64 65 /* 66 Fetch current height/width if value is zero as firefox 67 sets the width/height as zero for some images when we initially persist 68 the values 69 */ 70 if (result === null || result === 0 || typeof result != 'number' || isNaN(result)) { 71 result = img.getBoundingClientRect().height; 72 } 73 return result; 74 }, 75 76 MAX_SVG_SIZE: 1000, 77 MAX_SVG_ICON_SIZE: 21, 78 ICON_REGEXP: /icon/i, 79 svgMaxValue: function(svg) { 80 return this.ICON_REGEXP.test(svg.getAttribute('class') || '') ? this.MAX_SVG_ICON_SIZE : this.MAX_SVG_SIZE; 81 }, 82 83 svgViewBox: function(svg) { 84 var viewBox = svg.getAttribute('viewBox'); 85 if (viewBox) { 86 var viewBoxValues = viewBox.split(' '); 87 if (viewBoxValues.length === 4) { return { width: parseInt(viewBoxValues[2], 10), height: parseInt(viewBoxValues[3], 10) }; } 88 } 89 return {}; 90 }, 91 92 INFINITY: 1000000, 93 getSvgImageWidth: function(svg, onServer) { 94 var result = this.getImageWidth(svg, onServer) || this.INFINITY; 95 var maxValue = this.svgMaxValue(svg); 96 var viewBoxValue = this.svgViewBox(svg).width || this.INFINITY; 97 return Math.min(result, maxValue, viewBoxValue); 98 }, 99 100 getSvgImageHeight: function(svg, onServer) { 101 var result = this.getImageHeight(svg, onServer); 102 var maxValue = this.svgMaxValue(svg); 103 var viewBoxValue = this.svgViewBox(svg).height || this.INFINITY; 104 return Math.min(result, maxValue, viewBoxValue); 105 }, 106 107 getTopWrapper: function(node) { 108 var parent = node.parentNode; 109 if (parent.childNodes.length > 1) { return node; } 110 return this.getTopWrapper(parent); 111 }, 112 selectorsNotToBeRemoved: [ 113 '.copyright', 114 '#copyright', 115 '.delete-no', 116 '.delete-off', 117 '.pf-author', 118 '.print-content', 119 '#print-content', 120 '.pf-date', 121 '#pf-date', 122 '.pf-title', 123 '.pf-footer', 124 '.print-header', 125 '.print-footer', 126 '.print-yes', 127 '.pf-content', 128 '#pf-content', 129 ], 130 isDeletableElement: (function () { 131 var CLICK_TO_DEL_ELEMENTS = 'small, footer, header, aside, details, dialog, figure, nav, summary, twitter-widget, p, img, blockquote, h1, h2, h3, h4, h5, h6, ol, ul, li, a, table, td, pre, span, code, dl, dt, dd, hr, div.pf-caption, video, figcaption, data'; 132 var MANY_ELEMENTS_THRESHOLD = 15; 133 134 return function isDeletableElement(node) { 135 return ( 136 !commonUtils.hasClass(node, 'non-delete') && 137 !$(node).find(commonUtils.selectorsNotToBeRemoved.join(', ')).length && ( 138 node.matches(CLICK_TO_DEL_ELEMENTS) || 139 $(node).find('*:visible').length <= MANY_ELEMENTS_THRESHOLD 140 ) 141 ); 142 }; 143 })(), 144 145 resizeImageCssClass: function(value) { 146 return 'pf-size-' + value.replace('-size', '').replace('-images', ''); 147 }, 148 149 addCSS: function(css, doc, useBody) { 150 var tagName = useBody ? 'body' : 'head'; 151 var element = doc.getElementsByTagName(tagName)[0]; 152 var style = doc.createElement('style'); 153 154 style.type = 'text/css'; 155 style.setAttribute('name', 'pf-style'); 156 157 if (style.styleSheet) { 158 style.styleSheet.cssText = css; 159 } else { 160 style.appendChild(doc.createTextNode(css)); 161 } 162 element.appendChild(style); 163 }, 164 165 createIframe: function(doc) { 166 var iframe = doc.createElement('iframe'); 167 // TODO: do we need this src? If yes add sha256 to manifest.json CSP to avoid. Commented for now 168 // Error: "Refused to execute JavaScript URL because it violates the following Content Security Policy..."
169 /* jshint -W107 */ 170 // iframe.src = "javascript:false"; 171 /* jshint +W107 */ 172 iframe.frameBorder = '0'; 173 iframe.allowTransparency = 'true'; 174 return iframe; 175 }, 176 177 loadHtmlInIframe: function(doc, iframe, html) { 178 var dom, idoc; 179 180 try { 181 idoc = iframe.contentWindow.document; 182 } catch(e) { 183 dom = doc.domain; 184 /* jshint -W107 */ 185 iframe.src = "javascript:var d=document.open();d.domain='"+dom+"';void(0);"; 186 /* jshint +W107 */ 187 idoc = iframe.contentWindow.document; 188 } 189 idoc.write(html); 190 idoc.close(); 191 } 192}; 193function UUID() { // Public Domain/MIT 194 var d = new Date().getTime();//Timestamp 195 var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported 196 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 197 var r = Math.random() * 16;//random number between 0 and 16 198 if(d > 0){//Use timestamp until depleted 199 r = (d + r)%16 | 0; 200 d = Math.floor(d/16); 201 } else {//Use microseconds since page-load if supported 202 r = (d2 + r)%16 | 0; 203 d2 = Math.floor(d2/16); 204 } 205 return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 206 }); 207}; 208function toCdnUrl(url) { 209 if(url.indexOf('//') === 0) { url = pfData.config.protocol + url; } 210 var cdnPrefix = pfData.config.hosts.cdn; 211 if (url.indexOf(cdnPrefix) === 0) { return url; } 212 return cdnPrefix + url; 213}; 214var persistComputedStylesAndRect = (function() { 215 var STYLES_TO_SAVE = ['display', 'visibility']; 216 var RECT_TO_SAVE = ['width', 'height']; 217 var PREFIX = 'pf'; 218 219 function persistObject(node, object, propsToSave, objPrefix) { 220 for(var i = 0, length = propsToSave.length; i < length; i++) { 221 var prop = propsToSave[i]; 222 var key = [PREFIX, objPrefix, prop].join('_'); 223 node.dataset[key] = object[prop]; 224 } 225 } 226 227 function parentIsNotVisible(node) { 228 var parent = node.parentElement; 229 if (!parent) { return false; } 230 return parent.getAttribute('data-pf_style_display') === 'none' || parent.getAttribute('data-pf_style_visibility') === 'hidden'; 231 } 232 233 return function persistComputedStylesAndRect(node) { 234 if(parentIsNotVisible(node)) { 235 node.dataset.pf_style_display = 'none'; 236 node.dataset.pf_style_visibility = 'hidden'; 237 return; 238 } 239 240 var style = node.currentStyle || window.getComputedStyle(node); 241 if (style) { persistObject(node, style, STYLES_TO_SAVE, 'style'); } 242 var rect = node.getBoundingClientRect && node.getBoundingClientRect(); 243 var nodeName = node.nodeName.toUpperCase(); 244 if (nodeName === 'IMG' || nodeName === 'SVG' || nodeName === 'IFRAME') { 245 if (rect) { persistObject(node, rect, RECT_TO_SAVE, 'rect'); } 246 } 247 }; 248})(); 249var toAbsoluteUrl = (function() { 250 var absoluteUrlRegexp = /^https?:\/\//i; 251 return function(src, pfConfigLocation) { 252 if(!src) { return src; } 253 var js = ['javascript', ':'].join(''); 254 255 if (src.startsWith(js)) { 256 return src; 257 } else if (src.startsWith('#')) { 258 return src; 259 } else if (absoluteUrlRegexp.test(src)) { 260 return src; 261 } else if (src.startsWith('//')) { 262 return pfConfigLocation.protocol + src; 263 } else { 264 var imageDomain = pfConfigLocation.protocol + '//' + pfConfigLocation.host; 265 if (!src.startsWith('/')) { src = '/' + src; } 266 return imageDomain + src; 267 } 268 }; 269})(); 270var messageBus = (function() { 271 var windowMappings = { 272 algo: null, 273 core: null, 274 root: null 275 }; 276 var setupDone = false; 277 var connectionName; 278 279 function findFrameWindow(name, rootWindow) { 280 for (var i = 0; i < rootWindow.frames.length; i++) { 281 var frame = rootWindow.frames[i]; 282 // NOTE: some 3d party iframes like twitter/youtube 283 // doesn't allow to read their names throwing an exception 284 try { 285 if (frame.name === name) { return frame; } 286 } catch(e) {} 287 } 288 var iframe = rootWindow.document.querySelector("iframe[name='" + name + "']"); 289 if (iframe) { return iframe.contentWindow; } 290 } 291 292 function findDestinationWindow(name) { 293 var windowGetter = windowMappings[name]; 294 if (windowGetter) { return windowGetter(); } 295 296 switch(connectionName) { 297 case "algo": 298 windowMappings.root = function() { return window.parent.parent; }; 299 windowMappings.core = function() { return window.parent; }; 300 windowMappings.algo = function() { return window; }; 301 break; 302 case "pf-core": 303 windowMappings.root = function() { return window.parent; }; 304 windowMappings.core = function() { return window; }; 305 windowMappings.algo = function() { return findFrameWindow('algo', window); }; 306 break; 307 case "pdf_iframe": 308 windowMappings.core = function() { return window.parent; }; 309 break; 310 default: 311 var coreWindow = function() { return findFrameWindow('pf-core', window); }; 312 windowMappings.root = function() { return window; }; 313 windowMappings.core = coreWindow; 314 windowMappings.algo = function() { return findFrameWindow('algo', coreWindow()); }; 315 break; 316 } 317 318 return windowMappings[name](); 319 } 320 321 return { 322 postMessage: function(destination, type, payload, onError) { 323 var destinationWindow = findDestinationWindow(destination); 324 if(!destinationWindow) { 325 if (onError) { onError(); } 326 return; 327 } 328 329 destinationWindow.postMessage({ type: type, payload: payload }, '*'); 330 }, 331 listen: function(name, handlers, onError) { 332 if(setupDone) { return; } 333 334 connectionName = name; 335 336 window.addEventListener('message', function(event) { 337 try { 338 var data = event.data; 339 if (!data) { return; } 340 341 var type = data.type; 342 if (!type) { return; } 343 344 var handler = handlers[type]; 345 if (!handler) { return; } 346 var payload = data.payload; 347 348 handler(payload); 349 } catch (e) { 350 if (onError) { onError(e); } 351 else { throw e; } 352 } 353 }); 354 setupDone = true; 355 }, 356 }; 357})(); 358 359var logger = (function() { 360 var METHODS = ['log', 'error', 'time', 'timeEnd']; 361 var NOOP = function() {}; 362 363 function logEnabled(pfData) { 364 return ( 365 pfData.config.environment === "development" || 366 pfData.config.environment === 'test' || 367 pfData.config.pfEnableLog || 368 pfData.config.urls.page.indexOf("pfdebug=1") >= 0 369 ); 370 } 371 372 var result = { 373 init: function(pfData) { 374 for(var i = 0; i < METHODS.length; i++) { 375 var functionName = METHODS[i]; 376 if (logEnabled(pfData)) { 377 result[functionName] = Function.prototype.bind.call(console[functionName], console); 378 } else { 379 result[functionName] = NOOP; 380 } 381 } 382 }, 383 }; 384 385 // NOTE: setup default is to proxy to console 386 for(var i = 0; i < METHODS.length; i++) { 387 var functionName = METHODS[i]; 388 result[functionName] = Function.prototype.bind.call(console[functionName], console); 389 } 390 391 return result; 392})(); 393var pfRedirect = (function () { 394 return function(pfHost, userSettings, url) { 395 var components = ['source=cs', 'url=' + encodeURIComponent(url)]; 396 397 for(var config in userSettings) { 398 var setting = userSettings[config]; 399 if (typeof setting === 'object' && setting.value) { 400 components.push(config + '=' + encodeURIComponent(setting.value)); 401 } else if (typeof setting !== 'undefined') { 402 components.push(config + '=' + encodeURIComponent(setting)); 403 } 404 } 405 return pfHost + '/print/?' + components.join('&'); 406 }; 407})(); 408var printfriendlyOptionsParser = (function() { 409 var META_REGEXP = /meta\[(\w+)=['"]([^'"]+)['"]\]/i; 410 411 var __printfriendlyOptionsElement; 412 var printfriendlyOptions = function() { 413 __printfriendlyOptionsElement = __printfriendlyOptionsElement || document.querySelector('printfriendly-options'); 414 return __printfriendlyOptionsElement; 415 }; 416 417 /** 418 * This function accepts attribute value like this `foobar=1;foobaz=2;foobag=3` 419 * and based on that constructs a new object. Example: 420 * ```js 421 * parseAttribute('foobar=1;foobaz=2;foobag=3') 422 * => { 423 * foobar: 1, // read from string 424 * foobaz: 2, // read from string 425 * foobag: 3 426 * } 427 * ``` 428 * @param {string} attrValue - string like `foobar=1;foobaz=2;foobag=3` 429 * @returns {Object} 430 */ 431 var parseAttribute = function(attrValue) { 432 var result = {}; 433 if(!attrValue) { return result; } 434 435 var attrValuePieces = attrValue.trim().split(';'); 436 for (var i = 0; i < attrValuePieces.length; i++) { 437 var element = attrValuePieces[i].trim(); 438 if (element) { 439 var splitPoint = element.indexOf('='); 440 var key = element.slice(0, splitPoint).trim(); 441 var value = element.slice(splitPoint + 1).trim(); 442 if (key && value) { 443 if (value.startsWith('meta')) { 444 var match = value.match(META_REGEXP); 445 if (match) { 446 result[key] = {type: 'meta', attributeName: match[1], attributeValue: match[2], value: value}; 447 } 448 } else { 449 result[key] = {type: 'selector', value: value}; 450 } 451 } 452 } 453 } 454 return result; 455 }; 456 457 return { 458 selectors: function() { 459 var result = {}; 460 461 var options = printfriendlyOptions(); 462 if(!options) { return result; } 463 464 var printfriendlyOptionsValue = options.getAttribute('data-selectors') || options.getAttribute('data-content'); 465 var parsedAttributeResult = parseAttribute(printfriendlyOptionsValue); 466 467 if (parsedAttributeResult.contentSelectors) { result.contentSelectors = parsedAttributeResult.contentSelectors; } 468 if (parsedAttributeResult.titleSelector) { result.titleSelector = parsedAttributeResult.titleSelector; } 469 if (parsedAttributeResult.primaryImageSelector) { result.primaryImageSelector = parsedAttributeResult.primaryImageSelector; } 470 if (parsedAttributeResult.authorSelector) { result.authorSelector = parsedAttributeResult.authorSelector; } 471 if (parsedAttributeResult.dateSelector) { result.dateSelector = parsedAttributeResult.dateSelector; } 472 if (parsedAttributeResult.removeSelectors) { result.removeSelectors = parsedAttributeResult.removeSelectors; } 473 474 return result; 475 }, 476 contentFeatures: function() { 477 var result = { primaryImage: 'all', fetchOriginalPage: 'default' }; 478 var options = printfriendlyOptions(); 479 if(!options) { return result; } 480 481 var contentFeaturesValue = options.getAttribute('data-content-features'); 482 var parsedAttributeResult = parseAttribute(contentFeaturesValue); 483 484 if (parsedAttributeResult.showHiddenContent) { result.showHiddenContent = parsedAttributeResult.showHiddenContent.value; } 485 if (parsedAttributeResult.customCSSURL) { result.customCSSURL = parsedAttributeResult.customCSSURL.value; } 486 if (parsedAttributeResult.disablePDF) { result.disablePDF = parsedAttributeResult.disablePDF.value; } 487 if (parsedAttributeResult.disablePrint) { result.disablePrint = parsedAttributeResult.disablePrint.value; } 488 if (parsedAttributeResult.disableEmail) { result.disableEmail = parsedAttributeResult.disableEmail.value; } 489 if (parsedAttributeResult.disableClickToDel) { result.disableClickToDel = parsedAttributeResult.disableClickToDel.value; } 490 if (parsedAttributeResult.imageDisplayStyle) { result.imageDisplayStyle = parsedAttributeResult.imageDisplayStyle.value; } 491 if (parsedAttributeResult.hideImages && parsedAttributeResult.hideImages.value === '1') { 492 result.imagesSize = 'remove-images'; 493 } 494 if (parsedAttributeResult.headerImageUrl) { result.headerImageUrl = parsedAttributeResult.headerImageUrl.value; } 495 if (parsedAttributeResult.headerTagline) { result.headerTagline = parsedAttributeResult.headerTagline.value; } 496 if (parsedAttributeResult.encodeImages) { result.encodeImages = parsedAttributeResult.encodeImages.value === '1'; } 497 if (parsedAttributeResult.primaryImage) { result.primaryImage = parsedAttributeResult.primaryImage; } 498 if (parsedAttributeResult.fetchOriginalPage) { result.fetchOriginalPage = parsedAttributeResult.fetchOriginalPage.value; } 499 if (parsedAttributeResult.keepStyle) { result.keepStyle = parsedAttributeResult.keepStyle.value; } 500 if (parsedAttributeResult.bgImageUrlSupport) { result.bgImageUrlSupport = parsedAttributeResult.bgImageUrlSupport.value; } 501 502 return result; 503 }, 504 505 fallbackStrategy: function() { 506 var result = 'algo'; 507 var options = printfriendlyOptions(); 508 if(!options) { return result; } 509 510 var fallbackStrategyValue = options.getAttribute('data-fallback-strategy'); 511 if (!fallbackStrategyValue) { return result; } 512 result = fallbackStrategyValue.trim(); 513 return result; 514 } 515 }; 516})(); 517var OriginalPageFetch = (function() { 518 var __fetchOriginalPageCallbacksQueue = []; 519 var __fetchOriginalPageResult = null; 520 521 function triggerCallbacks() { 522 for (var i = 0; i < __fetchOriginalPageCallbacksQueue.length; i++) { 523 var callback = __fetchOriginalPageCallbacksQueue[i]; 524 callback(__fetchOriginalPageResult); 525 } 526 } 527 528 return function(pfData, callback) { 529 if (__fetchOriginalPageResult) { 530 if (callback) { callback(__fetchOriginalPageResult); } 531 return; 532 } 533 if (__fetchOriginalPageCallbacksQueue > 0) { 534 if (callback) { __fetchOriginalPageCallbacksQueue.push(callback); } 535 return; 536 } 537 538 if (callback) { __fetchOriginalPageCallbacksQueue.push(callback); } 539 540 var isEnabled = false;
541 if (pfData.userSettings.fetchOriginalPage === 'default') { 542 isEnabled = pfData.config.isExtension || pfData.config.isBookmarklet; 543 } else { 544 isEnabled = pfData.userSettings.fetchOriginalPage === 'yes'; 545 } 546 547 if (!isEnabled) { 548 __fetchOriginalPageResult = {status: 'disabled'}; 549 triggerCallbacks(); 550 return; 551 } 552 553 var xhr = new XMLHttpRequest(); 554 xhr.open('GET', pfData.page.location.href); 555 xhr.timeout = 5000; 556 xhr.onload = function() { 557 if (__fetchOriginalPageResult) { return; } 558 559 __fetchOriginalPageResult = { 560 status: 'success', 561 responseText: xhr.responseText, 562 headers: { CSP: xhr.getResponseHeader('Content-Security-Policy') } 563 }; 564 triggerCallbacks(); 565 }; 566 567 xhr.timeout = function() { 568 if (__fetchOriginalPageResult) { return; } 569 570 __fetchOriginalPageResult = {status: 'timeout'}; 571 triggerCallbacks(); 572 }; 573 xhr.send(); 574 }; 575})(); 576 577 578 579 580 581 582 583 584 585 586 587 588var PF_VERSION='client'; 589 590// FF browser extension doesn't have direct access to original page global variables 591(function() { 592 // PF Firefox Extension 1.2 uses hidden div element to pass data 593 var dataElement = document.getElementById("printfriendly-data"); 594 if (dataElement) { 595 var jsonData = JSON.parse(dataElement.getAttribute('data')); 596 window.pfstyle = jsonData.pfstyle; 597 window.pfOptions = jsonData.pfOptions; 598 window.pfShowHiddenContent = jsonData.pfShowHiddenContent; 599 } 600 601 // PF Firefox Extension 1.3 uses wrappedJSObject to pass data 602 if (window.wrappedJSObject && window.wrappedJSObject.extensionPath) { 603 var originalWindow = window.wrappedJSObject; 604 window.extensionId = originalWindow.extensionId; 605 window.extensionRootTabId = originalWindow.extensionRootTabId; 606 window.extensionPath = originalWindow.extensionPath; 607 window.extensionPageSize = originalWindow.extensionPageSize; 608 window.extensionFingerprint = originalWindow.extensionFingerprint; 609 window.pfstyle = originalWindow.pfstyle; 610 window.pfOptions = originalWindow.pfOptions; 611 window.pfShowHiddenContent = originalWindow.pfShowHiddenContent; 612 } 613})(); 614 615var pfMod = window.pfMod || (function(window, undef) { 616 var doc = window.document; 617 var protocol = 'https:'; 618 619 messageBus.listen('', { 620 PfCoreLoaded: function() { 621 messageBus.postMessage('core', 'PfStartCore', {pfData: pf.pfData, dsData: pf.dsData}); 622 }, 623 PfExtensionCoreLoaded: function() {
624 messageBus.postMessage('core', 'PfLoadCore', {pfData: pf.pfData}); 625 }, 626 PfClosePreview: function() { 627 pf.closePreview(); 628 }, 629 PfAddCSS: function(payload) { 630 commonUtils.addCSS(payload.css, doc, payload.useBody); 631 }, 632 PfRestoreStyles: function() { 633 docHelper.restoreStyles(); 634 }, 635 PfAddViewPortTag: function() { 636 docHelper.addViewPortTag(); 637 }, 638 PfScrollTop: function() { 639 window.scrollTo(0,0); 640 }, 641 PfFinished: function(payload) { 642 pf.hasContent = payload.hasContent; 643 pf.finished = true; 644 }, 645 PfOriginalPageFetch: function() { 646 OriginalPageFetch(pfMod.pfData, function(data) { 647 messageBus.postMessage('core', 'PfOriginalPageFetchResult', data); 648 }); 649 }, 650 PfImageToBase64: function(payload) { 651 var xhr = new XMLHttpRequest(); 652 xhr.onload = function() { 653 var reader = new FileReader(); 654 reader.onloadend = function() { 655 messageBus.postMessage('core', 'PfImageToBase64Hook', {id: payload.id, state: 'success', data: reader.result}); 656 }; 657 reader.readAsDataURL(xhr.response); 658 }; 659 xhr.onerror = function() { 660 messageBus.postMessage('core', 'PfImageToBase64Hook', {id: payload.id, state: 'error', data: payload.url}); 661 }; 662 xhr.open('GET', payload.url); 663 xhr.responseType = 'blob'; 664 xhr.send(); 665 }, 666 PfRedirectIfNecessary: function(payload) { 667 pf.dsData = payload.dsData; 668 var redirectCheck = pf.runRedirectChecks(); 669 switch(redirectCheck.action) { 670 case 'redirect': 671 pf.redirect(); 672 break; 673 case 'proceed': 674 messageBus.postMessage('core', 'PfLaunchCore'); 675 break; 676 } 677 } 678 }, 679 function(e) { 680 logger.error(e); 681 // Since we have got an exception we are setting the finished status to true 682 pf.finished = true; 683 }); 684 685 686 var defaultConfig = { 687 environment: 'production', 688 disableUI: false, 689 protocol: protocol, 690 dir: 'ltr', 691 usingBM: false, 692 maxImageWidth: 750, 693 filePath: '/assets/', 694 platform: 'unknown', 695 enablePrintOnly: false, 696 hosts: { 697 cdn: protocol + 'https://cdn.printfriendly.com', 698 pf: 'https://www.printfriendly.com', 699 ds: 'https://www.printfriendly.com', 700 translations: 'https://www.printfriendly.com', 701 ds_cdn: 'https://www.printfriendly.com', 702 pdf: 'https://pdf.printfriendly.com', 703 email: 'https://www.printfriendly.com', 704 page: window.location.host.split(':')[0] 705 }, 706 domains: { 707 page: window.location.host.split(':')[0].split('www.').pop() 708 } 709 }; 710 711 var utils = { 712 isBookmarklet: function() { 713 return (window.pfstyle && window.pfstyle !== 'wp'); 714 }, 715 removeEmailsFromUrl: function(url) { 716 url = url.split('?')[0]; 717 var parts = url.split('/'); 718 719 //reverse iteration as may be destructive 720 for (var i = parts.length; i-- > 0;) { 721 var foundIndex = parts[i].indexOf('@'); 722 // NOTE: we ignore case where @ is first character as it's might be an nickname 723 // Ex.: https://medium.com/@marikalam/study-guide-cassandra-data-consistency-496e5bf9cadb 724 if (foundIndex > 0) { 725 parts.splice(i, 1); 726 } 727 } 728 729 return parts.join('/'); 730 }, 731 732 ogImageUrl: function() { 733 var url = ''; 734 var ogImageElement = document.querySelector && 735 document.querySelector('meta[property="og:image"]'); 736 if (ogImageElement && ogImageElement.content) { 737 url = ogImageElement.content; 738 } 739 return url; 740 }, 741 742 isWix: function() { 743 return utils.ogImageUrl().indexOf('wixstatic.com') !== -1; 744 }, 745 746 isOverBlog: function() { 747 return utils.ogImageUrl().indexOf('over-blog-kiwi.com') !== -1; 748 }, 749 750 isLocalHost: function() { 751 var host = window.location.host; 752 var hostName = window.location.hostname; 753 return host.indexOf(':') !== -1 || 754 !!hostName.match(/\d+\.\d+\.\d+\.\d+/) || 755 hostName === 'localhost' || 756 !!hostName.split('.').pop().match(/invalid|test|example|localhost|dev/); 757 } 758 }; 759 760 var docHelper = { 761 addViewPortTag: function() { 762 var head = doc.getElementsByTagName('head')[0]; 763 var meta = doc.querySelector("meta[name=viewport]"); 764 765 if (!meta) { 766 meta = doc.createElement('meta'); 767 meta.name = 'viewport'; 768 } 769 770 meta.content = 'width=device-width, initial-scale=1'; 771 head.appendChild(meta); 772 }, 773 774 restoreStyles: function() { 775 var elements = document.getElementsByName("pf-style"); 776 777 for (var index = elements.length - 1; index >= 0; index--) { 778 elements[index].parentNode.removeChild(elements[index]); 779 } 780 } 781 }; 782 783 // FIXME: Extract non dom methods to another var - helper 784 var dom = { 785 isReady: false, 786 readyBound: false, 787 788 ready: function() { 789 if ( !dom.isReady) { 790 if ( !document.body) { 791 return setTimeout( dom.ready, 13); 792 } 793 dom.isReady = true; 794 dom.readyFunc.call(); 795 } 796 }, 797 798 doScrollCheck: function() { 799 if ( dom.isReady ) { 800 return; 801 } 802
803 try { 804 document.documentElement.doScroll("left"); 805 } catch(e) { 806 return setTimeout(dom.doScrollCheck, 50); 807 } 808 dom.detach(); 809 dom.ready(); 810 }, 811 812 detach: function() { 813 if ( document.addEventListener ) { 814 document.removeEventListener( "DOMContentLoaded", dom.completed, false ); 815 window.removeEventListener( "load", dom.completed, false ); 816 } else if ( document.attachEvent ) { 817 if ( document.readyState === "complete" ) { 818 document.detachEvent( "onreadystatechange", dom.completed ); 819 window.detachEvent( "onload", dom.completed ); 820 } 821 } 822 if (this.raceTimeoutId) { clearTimeout(this.raceTimeoutId); } 823 }, 824 825 completed: function(event) { 826 if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { 827 dom.detach(); 828 dom.ready(); 829 } 830 }, 831 832 bindReady: function() { 833 if(dom.readyBound) 834 return; 835 836 dom.readyBound = true; 837 838 if ( document.readyState === "complete" ) { 839 return dom.ready(); 840 } 841 // NOTE: setup race condition in case we are not on server 842 // as the PF rendering triggered by user 843 if (!pf.onServer) { 844 this.raceTimeoutId = setTimeout(function() { 845 dom.detach(); 846 dom.ready(); 847 }, 1500); 848 } 849 if ( document.addEventListener ) { 850 document.addEventListener("DOMContentLoaded", dom.completed, false ); 851 window.addEventListener( "load", dom.completed, false ); 852 } else if ( document.attachEvent ) { 853 document.attachEvent("onreadystatechange", dom.completed); 854 window.attachEvent( "onload", dom.completed ); 855 var toplevel = false; 856 857 try { 858 /* jshint -W041 */ 859 toplevel = window.frameElement == null && document.documentElement; 860 /* jshint +W041 */ 861 } catch(e) {} 862 863 if ( toplevel && toplevel.doScroll ) { 864 dom.doScrollCheck(); 865 } 866 } 867 }, 868 869 domReady: function(fn) { 870 this.readyFunc = fn; 871 this.bindReady(); 872 }, 873 874 canonicalize: function (url) { 875 if(url) { 876 var div = document.createElement('div'); 877 div.innerHTML = "<a></a>"; 878 div.firstChild.href = url; // Ensures that the href is properly escaped 879 div.innerHTML = div.innerHTML; // Run the current innerHTML back through the parser 880 url = div.firstChild.href; 881 if (url.indexOf("//") !== -1) { 882 url = window.location.protocol + '//' + div.firstChild.href.split("//")[1]; 883 } 884 } 885 return url; 886 } 887 }; 888 var userSettings = { 889 headerImageUrl: dom.canonicalize(window.pfHeaderImgUrl), 890 headerTagline: window.pfHeaderTagline, 891 imageDisplayStyle: window.pfImageDisplayStyle, 892 customCSSURL: !window.extensionPath && dom.canonicalize(window.pfCustomCSS), 893 disableClickToDel: window.pfdisableClickToDel, 894 disablePDF: window.pfDisablePDF, 895 disableImages: window.pfDisableImages, 896 encodeImages: window.pfEncodeImages === undefined ? window.location.protocol === 'http:' : (parseInt(window.pfEncodeImages) === 1), 897 showHiddenContent: (parseInt(window.pfShowHiddenContent) === 1), 898 disablePrint: window.pfDisablePrint, 899 disableEmail: window.pfDisableEmail 900 }; 901 902 if('|full-size|remove-images|large|medium|small|'.indexOf('|' + window.pfImagesSize + '|') !== -1) { 903 userSettings.imagesSize = window.pfImagesSize; 904 } else { 905 userSettings.imagesSize = window.pfHideImages == 1 ? 'remove-images' : 'full-size'; 906 } 907 908 var pf = { 909 version: PF_VERSION, 910 initialized: false, 911 finished: false, 912 onServer: false, 913 hasContent: false, 914 messages: [], 915 errors: [], 916 waitDsCounter: 0, 917 autoStart: false, 918 stats: {}, 919 920 init: function(options) { 921 try { 922 this.initialized = true; 923 this.configure(options); 924 this.onServerSetup(); 925 this.getDSSettingsFromPFServer() 926 this.setVariables(); 927 this.detectBrowser(); 928 this.setStats(); 929 this.startIfNecessary(); 930 931 window.print = function() { 932 pf.start(); 933 }; 934 } catch(e) { 935 logger.error(e); 936 // Since we have got an exception we are setting the finished status to true 937 pf.finished = true; 938 } 939 }, 940 941 configure: function(options) { 942 this.config = defaultConfig; 943 if (options) { 944 this.config.environment = options.environment || 'development'; 945 for(var val in options.hosts) { 946 this.config.hosts[val] = options.hosts[val]; 947 } 948 if(options.filePath) { 949 this.config.filePath = options.filePath; 950 } 951 if(options.ssLocation) { 952 this.config.ssLocation = options.ssLocation; 953 } 954 if(options.ssStyleSheetHrefs) { 955 this.config.ssStyleSheetHrefs = options.ssStyleSheetHrefs; 956 } 957 if(options.ssFaviconUrl) { 958 this.config.ssFaviconUrl = options.ssFaviconUrl; 959 } 960 if (options.enablePrintOnly) { 961 this.config.enablePrintOnly = options.enablePrintOnly; 962 } 963 } 964 this.config.enableLog = this.config.environment === 'development' || 965 this.config.environment === 'test' || 966 window.pfEnableLog; 967 this.config.isExtension = !!window.extensionPath; 968 this.config.isBookmarklet = utils.isBookmarklet(); 969 this.config.extensionFingerprint = window.extensionFingerprint; 970 }, 971 972 // Pass an object a string path. Similar to 973 // https://github.com/TheNodeILs/lodash-contrib/blob/master/docs/_.object.selectors.js.md#getpath 974 // Pass an object and property names separated by dot 975 // like country.state.city 976 getVal: function(obj, path) { 977 var keys = path.split('.'); 978 while( (typeof obj !== 'undefined') && keys.length) { 979 obj = obj[keys.shift()]; 980 } 981 return obj; 982 }, 983 984 startIfNecessary: function() { 985 if (window.pfstyle || this.autoStart) { this.start(); } 986 }, 987 988 urlHasAutoStartParams: function() { 989 return this.config.urls.page.indexOf('pfstyle=wp') !== -1; 990 }, 991 992 setUserSettings: function() { 993 function mergeToUserSettings(object) { 994 for (var key in object) { pf.userSettings[key] = object[key]; } 995 } 996 997 mergeToUserSettings(printfriendlyOptionsParser.selectors()); 998 pf.userSettings.fallbackStrategy = printfriendlyOptionsParser.fallbackStrategy(); 999 mergeToUserSettings(printfriendlyOptionsParser.contentFeatures()); 1000 1001 var printfriendlyCss = document.querySelector('printfriendly-css'); 1002 if (printfriendlyCss) { 1003 pf.userSettings.customCssStyle = printfriendlyCss.textContent; 1004 } 1005 }, 1006 1007 start: function() { 1008 // Extension must load DS in core because of CSP 1009 pf.config.extensionPageSize = window.extensionPageSize; 1010 this.setUserSettings(); 1011 if (pf.onServer) { 1012 pf.launch(); 1013 } else if(pf.config.isExtension) { 1014 pf.launch(); 1015 } else { 1016 // Wait 2 seconds for DS API 1017 pf.waitDsCounter += 1; 1018 if (pf.waitDsCounter < 20 && !pf.dsData) { 1019 return setTimeout(function(){pf.start();}, 100); 1020 } 1021 var redirectCheck = pf.runRedirectChecks(); 1022 switch(redirectCheck.action) { 1023 case 'show_message': 1024 alert('Current domain is not supported for printfriendly.com'); 1025 break; 1026 case 'redirect': 1027 pf.redirect(); 1028 break; 1029 case 'proceed':
1030 if (pf.supportedSiteType()) { 1031 pf.launch(); 1032 } 1033 break; 1034 } 1035 } 1036 }, 1037 1038 launch: function() { 1039 dom.domReady(function() { 1040 try { 1041 pf.startTime = new Date().getTime(); 1042 1043 htmlPreProcessor.run(); 1044 pf.pfData = pfDataHelper.get(); 1045 logger.init(pf.pfData); 1046 if (!pf.config.disableUI) { 1047 pf.sanitizeLocation(); 1048 pf.createMask(); 1049 } 1050 pf.loadCore(); 1051 } catch(e) { 1052 logger.error(e); 1053 // Since we have got an exception we are setting the finished status to true 1054 pf.finished = true; 1055 } 1056 }); 1057 }, 1058 1059 // Remove Querystring and emails from URL to avoid sending PII to google 1060 sanitizeLocation: function() { 1061 url = document.location.href.split('?')[0]; 1062 url = utils.removeEmailsFromUrl(url); 1063 1064 if(url !== document.location.href) { 1065 if (history && typeof history.pushState === 'function') { 1066 history.pushState({pf: 'sanitized'}, document.title, url); 1067 } 1068 } 1069 }, 1070 1071 // Restore url to be able to refresh page 1072 unsanitizeLocation: function() { 1073 if (history && history.state && history.state.pf == 'sanitized') { 1074 history.back(); 1075 } 1076 }, 1077 1078 onServerSetup: function() { 1079 if (window.onServer) { 1080 this.onServer = true; 1081 this.config.disableUI = true; 1082 } 1083 }, 1084 1085 setVariables: function() { 1086 var _t = this; 1087 var url; 1088 var cdnVersion = _t.config.hosts.cdn + _t.config.filePath + _t.version; 1089 var pfAppCSS = this.config.disableUI ? '' : "assets/pf-app/main-3969ccdff6487292316596861305ab2cb3f3e979ca610a44fc3cb33ec9414aad.css"; 1090 var contentCSS = this.config.disableUI ? '' : "assets/content/main-3cfe82c205ab841b23bdee21da68b74f56d6d802a559448e5723798431f88cd9.css"; 1091 var js = { 1092 jquery: "https://cdn.printfriendly.com/assets/unversioned/jquery/3.6.0.min-62efb0f451c1202e83668dd0942d2b0839e18b4a76c73809174dec2cad131a10.js", 1093 core: "https://cdn.printfriendly.com/assets/client/core-130f51c6e2ee2faf2c639859e017f62223079f3998a3c58da0cd6133f7984aa2.js", 1094 algo: "https://cdn.printfriendly.com/assets/client/algo-6a828bbc1ba6d90953b7e58df646c8a75b71401bd2e36690814733a4ef226980.js" 1095 }; 1096 1097 this.config.urls = { 1098 version: cdnVersion, 1099 js: js, 1100 css: { 1101 pfApp: pfAppCSS, 1102 content: contentCSS 1103 }, 1104 pdfMake: _t.config.hosts.pdf + '/pdfs/make', 1105 email: _t.config.hosts.email + '/email/new' 1106 }; 1107 try {url = top.location.href;} catch(e) {url = window.location.href;} 1108 1109 this.config.urls.page = url; 1110 this.userSettings = userSettings; 1111 this.config.pfstyle = window.pfstyle; 1112 1113 if(window.pfstyle && (window.pfstyle === "bk" || window.pfstyle === "nbk" || window.pfstyle === "cbk")) { 1114 this.config.usingBM = true; 1115 } 1116 1117 this.autoStart = this.urlHasAutoStartParams(); 1118 }, 1119 1120 detectBrowser: function() { 1121 this.browser = {}; 1122 if (navigator.userAgentData) { 1123 this.browser.isIE = false; 1124 } else if (navigator.appVersion) { 1125 var versionString = navigator.appVersion; 1126 if (versionString && versionString.indexOf('MSIE') !== -1) { 1127 this.browser.version = parseFloat(versionString.split('MSIE')[1]); 1128 this.browser.isIE = true; 1129 } else { 1130 this.browser.isIE = false; 1131 } 1132 } 1133 }, 1134 1135 redirect: function() { 1136 var href = typeof pfData !== 'undefined' ? pfData.page.location.href : top.window.location.href; 1137 var url = pfRedirect(this.config.hosts.pf, userSettings, href) 1138 // If PF was started automatically by URL params, location.replace ensures that the page 1139 // won't be in browser history thus avoiding a redirect loop if the the back button is 1140 // used. 1141 if (this.autoStart) { 1142 top.location.replace(url); 1143 } else { 1144 top.location.href = url; 1145 } 1146 }, 1147 1148 supportedSiteType: function() { 1149 if(pf.config.urls.page === 'about:blank' || 1150 (pf.config.protocol !== 'http:' && pf.config.protocol !== 'https:')) { 1151 return false;
1152 } else { 1153 return true; 1154 } 1155 }, 1156 1157 setStats: function() { 1158 pf.stats.page = { 1159 bm: utils.isBookmarklet(), 1160 local: utils.isLocalHost(), 1161 unSupported: utils.isWix() || utils.isOverBlog() 1162 }; 1163 }, 1164 1165 domainsListInclude: function(domain, domainsList) { 1166 if (!domainsList.length) { return false; } 1167 1168 var domainsListRegexp = new RegExp(domainsList.join('|').replace(/\./g, "\\.")); 1169 return domain.match(domainsListRegexp); 1170 }, 1171 1172 skipRedirectReasons: [ 1173 { 1174 name: 'noApiResponse', 1175 check: function() { return !pf.dsData; } 1176 }, { 1177 name: 'adFree', 1178 check: function() { 1179 return pf.dsData.domain_settings.ad_free; 1180 } 1181 }, { 1182 name: 'isLocalHost', 1183 check: function() { 1184 return utils.isLocalHost(); 1185 }, 1186 }, { 1187 name: 'unSupportedBySS', 1188 check: function() { 1189 return utils.isWix() || utils.isOverBlog(); 1190 }, 1191 }, { 1192 name: 'BMExt', 1193 check: function() { 1194 return utils.isBookmarklet(); 1195 } 1196 } 1197 ], 1198 1199 forceRedirectReasons: [ 1200 { 1201 name: 'unSupportedBrowser', 1202 check: function() { 1203 try { 1204 var edge = !navigator.userAgentData && navigator.userAgent.match(/Edge\/(\d+.\d+)/); 1205 var isLegacyEdge = edge && edge[1] && parseFloat(edge[1]) < 79; 1206 var supportedLegacyDomainsRegexp = /^((.+\.)?jhana-dev\.com|(.+\.)?jhana-stage\.com|(.+\.)?jhana\.com|guide\.matrix\.ip\.com|guide\.ip\.com|componentagro\.test\.huss\.nl|componentagro\.nl|hansa-online\.de|binnenschifffahrt-online\.de|mikrooek-apotheke\.de)$/; 1207 if (isLegacyEdge && !supportedLegacyDomainsRegexp.test(pf.config.domains.page)) { return true; } 1208 if(!utils.isBookmarklet() && 1209 (!(history && typeof history.pushState === 'function') || 1210 (!navigator.userAgentData && navigator.userAgent.match(/(ipod|opera.mini|blackberry|playbook|bb10)/i)) || 1211 pf.browser.isIE) 1212 ) { 1213 return true; 1214 } else { 1215 return false; 1216 } 1217 } catch(e) { 1218 logger.error(e); 1219 return false; 1220 } 1221 } 1222 } 1223 ], 1224 1225 unsupportedReasons: [ 1226 { 1227 name: 'blacklist', 1228 check: function() { 1229 return pf.domainsListInclude(pf.config.domains.page, [ 1230 // 'unsupported-domain.org' 1231 ]); 1232 } 1233 } 1234 ], 1235 1236 runChecksFor: function(checks) { 1237 for(var i = 0, length = checks.length; i < length; i++) { 1238 var reason = checks[i]; 1239 if (reason.check()) { return reason; } 1240 } 1241 }, 1242 1243 __redirectChecksResult: null, 1244 runRedirectChecks: function() { 1245 if (this.__redirectChecksResult) { return this.__redirectChecksResult; } 1246 1247 var result, forceRedirectReason, skipRedirectReason, unsupportedReason; 1248 1249 if (!(skipRedirectReason = this.runChecksFor(this.skipRedirectReasons))) { 1250 result = { action: 'redirect' }; 1251 } else if (unsupportedReason = this.runChecksFor(this.unsupportedReasons)) { 1252 result = { reason: unsupportedReason.name, action: 'show_message' } 1253 } else if (forceRedirectReason = this.runChecksFor(this.forceRedirectReasons)) { 1254 result = { reason: forceRedirectReason.name, action: 'redirect' }; 1255 } else { 1256 result = { reason: skipRedirectReason.name, action: 'proceed' } 1257 } 1258 1259 this.__redirectChecksResult = result; 1260 return this.__redirectChecksResult; 1261 }, 1262 1263 createMask: function() { 1264 var div = document.createElement('div'); 1265 div.innerHTML = '<div id="pf-mask" style="z-index: 2147483627!important; position: fixed !important; top: 0pt !important; left: 0pt !important; background-color: rgb(0, 0, 0) !important; opacity: 0.8 !important;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=80); height: 100% !important; width: 100% !important;"></div>'; 1266 document.body.appendChild(div.firstChild); 1267 }, 1268 1269 closePreview: function() { 1270 dom.readyBound = false;
1271 dom.isReady = false; 1272 1273 pf.unsanitizeLocation(); 1274 1275 var pfCoreElement = document.getElementById('pf-core'); 1276 if (pfCoreElement && pfCoreElement.parentElement) { 1277 pfCoreElement.parentElement.removeChild(pfCoreElement); 1278 } 1279 1280 var maskElement = document.getElementById('pf-mask'); 1281 if (maskElement && maskElement.parentElement) { 1282 maskElement.parentElement.removeChild(maskElement); 1283 } 1284 1285 var gaiframe = document.getElementById('gaiframe'); 1286 if (gaiframe && gaiframe.parentElement) { 1287 gaiframe.parentElement.removeChild(gaiframe); 1288 } 1289 }, 1290 1291 removeDoubleSemiColon: function(str) { 1292 return str.replace(/;{2}/g, ';'); 1293 }, 1294 1295 loadCore: function() { 1296 window.coreIframe = commonUtils.createIframe(document); 1297 coreIframe.id = 'pf-core'; 1298 coreIframe.name = 'pf-core'; 1299 1300 document.body.appendChild(coreIframe); 1301 1302 var style = coreIframe.style.cssText + ';' + 'width: 100% !important;max-width:100vw !important;height: 100% !important; max-height:100vh !important; display: block !important; overflow: hidden !important; position: absolute !important; top: 0px !important; left: 0px !important; background-color: transparent !important; z-index: 2147483647!important'; 1303 coreIframe.style.cssText = this.removeDoubleSemiColon(style); 1304 1305 if (this.config.isExtension) { 1306 // extensionPath is declared if script running from browser extension 1307 coreIframe.src = extensionPath + '/pages/core.html'; 1308 coreIframe.onload = function() { 1309 messageBus.postMessage('core', 'PfInitCoreExtension', {extensionRootTabId: window.extensionRootTabId}); 1310 }; 1311 } else { 1312 coreIframe.src = "https://cdn.printfriendly.com/assets/client/core-0a818227f18b1ede53dd48edc9f61296a829938e3e2d3c4c9b75e2e5f7937d08.html" 1313 } 1314 }, 1315 1316 getDSSettingsFromPFServer: function() { 1317 if (pf.onServer || this.config.isExtension) { return; } 1318 1319 var script = document.createElement('script'); 1320 1321 script.src = pf.config.hosts.ds_cdn + '/api/v3/domain_settings/a' + 1322 '?callback=pfMod.saveDSSettings' + 1323 '&hostname=' + pf.config.hosts.page + 1324 '&client_version=' + pf.version; 1325 1326 document.getElementsByTagName('head')[0].appendChild(script); 1327 }, 1328 1329 saveDSSettings: function(data) { 1330 pf.dsData = data; 1331 if (window.coreIframe && coreIframe.contentWindow) { 1332 messageBus.postMessage('core', 'PfConfigureDSSettings', {dsData: data}); 1333 } 1334 } 1335 }; 1336 1337 var pfDataHelper = { 1338 emailText: function() { 1339 var elements = document.getElementsByClassName('pf-email'); 1340 return elements.length ? elements[0].textContent : ''; 1341 }, 1342 1343 csStyleSheetHrefs: function() { 1344 var hrefs = []; 1345 1346 for(var i = 0; i < doc.styleSheets.length; i++) { 1347 hrefs.push(doc.styleSheets[i].href); 1348 } 1349 1350 return hrefs; 1351 }, 1352 1353 metas: function () { 1354 var metaTags = doc.getElementsByTagName('meta'); 1355 var metas = []; 1356 1357 for(var i = 0; i < metaTags.length; i++) { 1358 metas.push({ 1359 name: metaTags[i].getAttribute('name'), 1360 content: metaTags[i].getAttribute('content'), 1361 property: metaTags[i].getAttribute('property'), 1362 itemprop: metaTags[i].getAttribute('itemprop') 1363 }); 1364 } 1365 1366 return metas; 1367 }, 1368 1369 screen: function() { 1370 var w = window.top; 1371 try { 1372 w.document; 1373 } catch(e) { 1374 w = window; 1375 } 1376 return { 1377 x: typeof w.screenX != 'undefined' ? w.screenX : w.screenLeft, 1378 y: typeof w.screenY != 'undefined' ? w.screenY : w.screenTop, 1379 width: typeof w.outerWidth!='undefined' ? w.outerWidth : w.document.documentElement.clientWidth, 1380 height: typeof w.outerHeight != 'undefined' ? w.outerHeight: (w.document.documentElement.clientHeight - 22) 1381 }; 1382 }, 1383 1384 language: function() { 1385 var lang = document.getElementsByTagName('html')[0].getAttribute('lang'); 1386 1387 if (!lang) { 1388 var metaLang = document.querySelector('meta[http-equiv=Content-Language]'); 1389 if (metaLang) { 1390 lang = metaLang.getAttribute('content'); 1391 } 1392 } 1393 1394 return lang; 1395 }, 1396 1397 canvasDataUrls: function() { 1398 var dataUrls = []; 1399
1400 var canvases = doc.getElementsByTagName('canvas'); 1401 for (var i = 0; i < canvases.length; i++) { 1402 try { 1403 var canvas = canvases[i]; 1404 var dataUrl = canvas.toDataURL('image/png'); 1405 canvas.setAttribute('pf-dataurl-index', dataUrls.length); 1406 dataUrls.push(dataUrl); 1407 } catch(e) {} // Ignore canvas tainted error 1408 } 1409 1410 return dataUrls; 1411 }, 1412 1413 favicon: function() { 1414 if (pf.config.ssFaviconUrl) { return pf.config.ssFaviconUrl; } 1415 1416 var faviconRels = ['shortcut icon', 'icon', 'apple-touch-icon', 'apple-touch-icon-precomposed']; 1417 var faviconElement = null; 1418 for (var i = 0; i < faviconRels.length; i++) { 1419 var rel = faviconRels[i]; 1420 var result = document.querySelector('link[rel="' + rel +'"]'); 1421 if (result) { 1422 faviconElement = result; 1423 break; 1424 } 1425 } 1426 1427 if (!faviconElement || !faviconElement.href) { return "../icons/globe.svg"; } 1428 1429 return faviconElement.href; 1430 }, 1431 1432 enablePrintOnly: function(doc) { 1433 return !!doc.querySelector('.at-svc-printfriendly, a[href*="printfriendly\.com"], script[src*="\/\/cdn\.printfriendly\.com"], script[src*="\/\/pf-cdn\.printfriendly\.com"], img[src*="\/\/cdn\.printfriendly\.com"]') || 1434 pf.config.enablePrintOnly; 1435 }, 1436 1437 getApplicationLd: function(doc) { 1438 var applicationLdNode = doc.querySelector('script[type="application/ld+json"]') 1439 if (applicationLdNode) { 1440 var applicationLdJSON = applicationLdNode.textContent.replace(/^\s*\/*\s<!\[CDATA\[|\]\]>\s*$/g, "").replace(/[/\s]*$/, ''); 1441 try { 1442 return JSON.parse(applicationLdJSON); 1443 } catch (e) { 1444 logger.error(e); 1445 } 1446 } 1447 }, 1448 1449 replaceShadowRootElements: function(body) { 1450 var duplicate = body.cloneNode(true); 1451 duplicate.innerHTML = body.innerHTML; 1452 1453 body.querySelectorAll('.pf-shadow-root').forEach(function(shadowRoot) { 1454 var shadowRootContent = shadowRoot.shadowRoot.innerHTML; 1455 if(!shadowRootContent) return; 1456 var replace = duplicate.querySelector('[data-pf-shadow-root="' + shadowRoot.dataset.pfShadowRoot + '"]') 1457 if (!replace) { return; } 1458 var shadowRootContentElement = document.createElement('div'); 1459 shadowRootContentElement.innerHTML = shadowRootContent; 1460 replace.parentNode.replaceChild(shadowRootContentElement, replace); 1461 }); 1462 1463 return duplicate; 1464 }, 1465 get: function() { 1466 pf.config.extensionId = window.extensionId; 1467 pf.config.extensionRootTabId = window.extensionRootTabId; 1468 pf.config.extensionPath = window.extensionPath; 1469 1470 var canvasDataUrls = this.canvasDataUrls(); 1471 var location = document.location; 1472 page = { 1473 dir: doc.body.getAttribute('dir') || doc.querySelector('html').getAttribute('dir') || getComputedStyle(doc.body).getPropertyValue('direction') || 'ltr', 1474 bodyClassList: [].slice.call(doc.body.classList || doc.body.className.split(' ')), // Convert Array like object to Array 1475 emailText: this.emailText(), 1476 screen: this.screen(), 1477 metas: this.metas(), 1478 csStyleSheetHrefs: this.csStyleSheetHrefs(), 1479 location: { 1480 href: location.href, 1481 host: location.host, 1482 pathname: location.pathname, 1483 protocol: location.protocol 1484 }, 1485 enablePrintOnly: this.enablePrintOnly(doc), 1486 hasPrintOnly: !!doc.querySelector('#print-only, .print-only'), 1487 title: document.title, 1488 // NOTE: in case we populate algo iframe with HTML containing <embed> tag in some cases it become broken for printing 1489 // Ref.: https://github.com/printfriendly/printfriendly/issues/2099 1490 body: this.replaceShadowRootElements(document.body).innerHTML.replace(/<\s*?embed[^>]*?>(.*?<\s*?\/\s*?embed\s*?>)?/g, ''), 1491 language: this.language(), 1492 canvasDataUrls: canvasDataUrls, 1493 favicon: this.favicon(), 1494 applicationLd: this.getApplicationLd(doc) 1495 }; 1496 1497 return { 1498 startTime: pf.startTime, 1499 config: pf.config, 1500 userSettings: pf.userSettings, 1501 version: pf.version, 1502 onServer: pf.onServer, 1503 browser: pf.browser, 1504 dsData: pf.dsData, 1505 stats: pf.stats, 1506 page: page 1507 }; 1508 } 1509 }; 1510 1511 var htmlPreProcessor = { 1512 LARGE_IMAGE_WIDTH: 300, 1513 LARGE_IMAGE_HEIGHT: 200, 1514 1515 run: function() { 1516 this.processChildren(document.body); 1517 }, 1518 1519 /* Mark hidden elements 1520 * Set Full URLs for images and links 1521 * Set width and height for images to keep size with removed stylesheets 1522 * Not splitting it into separate functions to avoid the 1523 * function call overhead as allElements can be very huge 1524 */ 1525 processChildren: function(sourceElement) { 1526 var nodeName; 1527 var queue = []; 1528 // NOTE: phantomjs children property returns `undefined` for some elements(ex. SVGElements) 1529 // so we have to verify it first 1530 if (sourceElement.children && sourceElement.children.length) { 1531 for(var i = 0, length = sourceElement.children.length; i < length; i++) { queue.push(sourceElement.children[i]); } 1532 } 1533 1534 var sourceChild = null; 1535 while (sourceChild = queue.shift()) { 1536 if (!commonUtils.hasClass(sourceChild, 'comment-list')) { 1537 // Process current element 1538 if (sourceChild.nodeType === Node.ELEMENT_NODE) { 1539 try { 1540 nodeName = sourceChild.nodeName.toLowerCase(); 1541
1542 persistComputedStylesAndRect(sourceChild); 1543 if(sourceChild.shadowRoot) { 1544 commonUtils.addClassTo(sourceChild, 'pf-shadow-root'); 1545 sourceChild.dataset.pfShadowRoot = UUID(); 1546 } 1547 if (!userSettings.showHiddenContent && (sourceChild.getAttribute('data-pf_style_display') === 'none' || sourceChild.getAttribute('data-pf_style_visibility') === 'hidden')) { 1548 commonUtils.addClassTo(sourceChild, "hidden-originally"); 1549 } else if (commonUtils.hasClass(sourceChild, 'hidden-originally')) { 1550 commonUtils.removeClassFrom(sourceChild, 'hidden-originally'); 1551 } 1552 1553 if (nodeName === 'a') { 1554 // A: Convert relative to absolute URL 1555 var href = sourceChild.getAttribute('href'); 1556 if(href && !href.startsWith('#') && !href.startsWith('http') && !href.startsWith('javascript') && !href.startsWith('mailto')) { 1557 // sourceChild.href gives the full URL. So we use it here instead of 1558 // the result of getAttribute which return the original href 1559 sourceChild.setAttribute('href', toAbsoluteUrl(href, window.location)); 1560 } 1561 } else if ((nodeName === 'input' || nodeName === 'textarea') && !pfMod.onServer) { 1562 if (sourceChild.type === 'radio' || sourceChild.type === 'checkbox') { 1563 if (sourceChild.checked) { 1564 sourceChild.setAttribute("checked", "checked"); 1565 } 1566 } else { 1567 sourceChild.setAttribute('value', sourceChild.value); 1568 } 1569 } else if (!pfMod.onServer && nodeName === 'select' && sourceChild.options.length && sourceChild.selectedIndex >= 0) { 1570 sourceChild.options[sourceChild.selectedIndex].setAttribute("selected", "selected"); 1571 } else if (nodeName === 'img' || nodeName === 'svg') { 1572 // Convert relative to absolute URL 1573 if (sourceChild.getAttribute('src')) { sourceChild.src = sourceChild.src; } 1574 var srcset = sourceChild.getAttribute('srcset') || sourceChild.getAttribute('data-srcset'); 1575 if (srcset) { 1576 var srcs = srcset.split(/,\s/); 1577 var newSrcs = []; 1578 for (var j = 0; j < srcs.length; j++) { 1579 var src = srcs[j]; 1580 newSrcs.push(toAbsoluteUrl(src, window.location)); 1581 } 1582 1583 sourceChild.setAttribute('pf-data-srcset', newSrcs.join(', ')); 1584 } 1585 if (nodeName === 'img' && !commonUtils.hasClass(sourceChild, 'hidden-originally')) { 1586 var imgHeight = commonUtils.getImageHeight(sourceChild, pfMod.onServer); 1587 var imgWidth = commonUtils.getImageWidth(sourceChild, pfMod.onServer); 1588 1589 if ((imgWidth * imgHeight) > (this.LARGE_IMAGE_WIDTH * this.LARGE_IMAGE_HEIGHT)) { 1590 commonUtils.addClassTo(sourceChild, 'pf-large-image'); 1591 } 1592 } 1593 } else if (pf.userSettings.bgImageUrlSupport === 'on') { 1594 var bgSrc = window.getComputedStyle(sourceChild).backgroundImage 1595 if(bgSrc !== 'none') { 1596 bgSrc = bgSrc.replace(/^url\("(.+)"\)/, '$1') 1597 1598 sourceChild.setAttribute('data-pf-style-background-image', bgSrc); 1599 if (!commonUtils.hasClass(sourceChild, 'hidden-originally')) { 1600 var imgHeight = commonUtils.getImageHeight(sourceChild, pfMod.onServer); 1601 var imgWidth = commonUtils.getImageWidth(sourceChild, pfMod.onServer); 1602 1603 if ((imgWidth * imgHeight) > (this.LARGE_IMAGE_WIDTH * this.LARGE_IMAGE_HEIGHT)) { 1604 commonUtils.addClassTo(sourceChild, 'pf-large-image'); 1605 } 1606 } 1607 } 1608 } 1609 1610 // For some edgecases getComputedStyle returns null 1611 // Sometimes nodeName is undefined (should never happen ideally but it does happen) 1612 } catch(e) {} 1613 } 1614 1615 // NOTE: phantomjs children property returns `undefined` for some elements(ex. SVGElements) 1616 // so we have to verify it first 1617 if (sourceChild.children && sourceChild.children.length) { 1618 for(var i = 0, length = sourceChild.children.length; i < length; i++) { queue.push(sourceChild.children[i]); } 1619 } 1620 } 1621 } 1622 } 1623 }; 1624 1625 return pf; 1626})(window); 1627window.pfMod = pfMod; 1628var priFri = pfMod; 1629 1630// Required for browser extensions second run 1631if (pfMod.initialized && (document.getElementById('printfriendly-data') || window.extensionPath)) { 1632 pfMod.start(); 1633} else if (window.name !== 'algo' && window.name !== 'pf-core' && !pfMod.initialized) { 1634 pfMod.init(window.pfOptions); 1635};
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.