PageSourceSearch

https://morinvillenews.com/printfriendly.js

js morinvillenews.com collected 2026-10-02 13:47:14 UTC 57,561 bytes, 1,635 lines download raw bytes

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.