1
0
mirror of https://github.com/webrecorder/pywb.git synced 2025-03-26 07:49:24 +01:00
pywb/wombat/src/wombatLite.js
John Berlin 94784d6e5d wombat overhaul! fixes #449 (#451)
wombat:
 - I: function overrides applied by wombat now better appear to be the original new function name same as originals when possible
 - I: WombatLocation now looks and behaves more like the original Location interface
 - I: The custom storage class now looks and behaves more like the original Storage
 - I: SVG image rewriting has been improved: both the href and xlink:href deprecated since SVG2 now rewritten always
 - I: document.open now handles the case of creation of a new window
 - I: Request object rewriting of the readonly href property is now correctly handled
 - I: EventTarget.addEventListener, removeEventListener overrides now preserve the original this argument of the wrapped listener
 - A: document.close override to ensure wombat is initialized after write or writeln usage
 - A: reconstruction of <doctype...> in rewriteHTMLComplete IFF it was included in the original string of HTML
 - A: document.body setter override to ensure rewriting of the new body or frameset
 - A: Attr.[value, nodeValue, textContent] added setter override to perform URL rewrites
 - A: SVGElements rewriting of the filter, style, xlink:href, href, and src attributes
 - A: HTMLTrackElement rewriting of the src attribute of the
 - A: HTMLQuoteElement and HTMLModElement rewriting of the cite attribute
 - A: Worklet.addModule: Loads JS module specified by a URL.
 - A: HTMLHyperlinkElementUtils overrides to the areaelement
 - A: ShadowRootoverrides to: innerHTML even though inherites from DocumentFragement and Node it still has innerHTML getter setter.
 - A: ShadowRoot, Element, DocumentFragment append, prepend: adds strings of HTML or a new Node inherited from ParentNode
 - A: StylePropertyMap override: New way to access and set CSS properties.
 - A: Response.redirecthttps rewriting of the URL argument.
 - A:  UIEvent, MouseEvent, TouchEvent, KeyboardEvent, WheelEvent, InputEvent, and CompositionEven constructor and init{even-name} overrides in order to ensure that wombats JS Proxy usage does not affect their defined behaviors
 - A: XSLTProcessor override to ensure its usage is not affected by wombats JS Proxy usage.
 - A: navigator.unregisterProtocolHandler: Same override as existing navigator.registerProtocolHandler but from the inverse operation
 - A: PresentationRequest: Constructor takes a URL or an array of URLs.
 - A: EventSource and WebSocket override in order to ensure that they do not cause live leaks
 - A: overrides for the child node interface
 - Fix: autofetch worker creatation of the backing worker when it is operating within an execution context with a null origin
tests:
  - A: 559 tests specific to wombat and client side rewritting
pywb:
  - Fix: a few broken tests due to iana.org requiring a user agent in its requests
rewrite:
  - introduced a new JSWorkerRewriter class in order to support rewriting via wombat workers in the context of all supported worker variants via
  - ensured rewriter app correctly sets the static prefix
ci:
 - Modified travis.yml to specifically enumerate jobs
documentation:
  - Documented new wombat, wombat proxy moded, wombat workers
auto-fetch:
 - switched to mutation observer when in proxy mode so that the behaviors can operate in tandem with the autofetcher
2019-05-15 11:42:51 -07:00

254 lines
7.1 KiB
JavaScript
Executable File

/* eslint-disable camelcase */
import AutoFetchWorkerProxyMode from './autoFetchWorkerProxyMode';
/**
* Wombat lite for proxy-mode
* @param {Window} $wbwindow
* @param {Object} wbinfo
*/
export default function WombatLite($wbwindow, wbinfo) {
if (!(this instanceof WombatLite)) return new WombatLite($wbwindow, wbinfo);
this.wb_info = wbinfo;
this.$wbwindow = $wbwindow;
this.wb_info.top_host = this.wb_info.top_host || '*';
this.wb_info.wombat_opts = this.wb_info.wombat_opts || {};
this.wbAutoFetchWorkerPrefix =
(this.wb_info.auto_fetch_worker_prefix || this.wb_info.static_prefix) +
'autoFetchWorkerProxyMode.js';
this.WBAutoFetchWorker = null;
}
/**
* Applies an override to Math.seed and Math.random using the supplied
* seed in order to ensure that random numbers are deterministic during
* replay
* @param {string} seed
*/
WombatLite.prototype.initSeededRandom = function(seed) {
// Adapted from:
// http://indiegamr.com/generate-repeatable-random-numbers-in-js/
this.$wbwindow.Math.seed = parseInt(seed);
var wombat = this;
this.$wbwindow.Math.random = function random() {
wombat.$wbwindow.Math.seed =
(wombat.$wbwindow.Math.seed * 9301 + 49297) % 233280;
return wombat.$wbwindow.Math.seed / 233280;
};
};
/**
* Applies an override to crypto.getRandomValues in order to make
* the values it returns are deterministic during replay
*/
WombatLite.prototype.initCryptoRandom = function() {
if (!this.$wbwindow.crypto || !this.$wbwindow.Crypto) return;
// var orig_getrandom = this.$wbwindow.Crypto.prototype.getRandomValues
var wombat = this;
var new_getrandom = function getRandomValues(array) {
for (var i = 0; i < array.length; i++) {
array[i] = parseInt(wombat.$wbwindow.Math.random() * 4294967296);
}
return array;
};
this.$wbwindow.Crypto.prototype.getRandomValues = new_getrandom;
this.$wbwindow.crypto.getRandomValues = new_getrandom;
};
/**
* Forces, when possible, the devicePixelRatio property of window to 1
* in order to ensure deterministic replay
*/
WombatLite.prototype.initFixedRatio = function() {
try {
// otherwise, just set it
this.$wbwindow.devicePixelRatio = 1;
} catch (e) {}
// prevent changing, if possible
if (Object.defineProperty) {
try {
// fixed pix ratio
Object.defineProperty(this.$wbwindow, 'devicePixelRatio', {
value: 1,
writable: false
});
} catch (e) {}
}
};
/**
* Applies an override to the Date object in order to ensure that
* all Dates used during replay are in the datetime of replay
* @param {string} timestamp
*/
WombatLite.prototype.initDateOverride = function(timestamp) {
if (this.$wbwindow.__wb_Date_now) return;
var newTimestamp = parseInt(timestamp) * 1000;
// var timezone = new Date().getTimezoneOffset() * 60 * 1000;
// Already UTC!
var timezone = 0;
var start_now = this.$wbwindow.Date.now();
var timediff = start_now - (newTimestamp - timezone);
var orig_date = this.$wbwindow.Date;
var orig_utc = this.$wbwindow.Date.UTC;
var orig_parse = this.$wbwindow.Date.parse;
var orig_now = this.$wbwindow.Date.now;
this.$wbwindow.__wb_Date_now = orig_now;
this.$wbwindow.Date = (function(Date_) {
return function Date(A, B, C, D, E, F, G) {
// Apply doesn't work for constructors and Date doesn't
// seem to like undefined args, so must explicitly
// call constructor for each possible args 0..7
if (A === undefined) {
return new Date_(orig_now() - timediff);
} else if (B === undefined) {
return new Date_(A);
} else if (C === undefined) {
return new Date_(A, B);
} else if (D === undefined) {
return new Date_(A, B, C);
} else if (E === undefined) {
return new Date_(A, B, C, D);
} else if (F === undefined) {
return new Date_(A, B, C, D, E);
} else if (G === undefined) {
return new Date_(A, B, C, D, E, F);
} else {
return new Date_(A, B, C, D, E, F, G);
}
};
})(this.$wbwindow.Date);
this.$wbwindow.Date.prototype = orig_date.prototype;
this.$wbwindow.Date.now = function now() {
return orig_now() - timediff;
};
this.$wbwindow.Date.UTC = orig_utc;
this.$wbwindow.Date.parse = orig_parse;
this.$wbwindow.Date.__WB_timediff = timediff;
Object.defineProperty(this.$wbwindow.Date.prototype, 'constructor', {
value: this.$wbwindow.Date
});
};
/**
* Applies an override that disables the pages ability to send OS native
* notifications. Also disables the ability of the replayed page to retrieve the geolocation
* of the view.
*
* This is done in order to ensure that no malicious abuse of these functions
* can happen during replay.
*/
WombatLite.prototype.initDisableNotifications = function() {
if (window.Notification) {
window.Notification.requestPermission = function requestPermission(
callback
) {
if (callback) {
// eslint-disable-next-line standard/no-callback-literal
callback('denied');
}
return Promise.resolve('denied');
};
}
var applyOverride = function(on) {
if (!on) return;
if (on.getCurrentPosition) {
on.getCurrentPosition = function getCurrentPosition(
success,
error,
options
) {
if (error) {
error({ code: 2, message: 'not available' });
}
};
}
if (on.watchPosition) {
on.watchPosition = function watchPosition(success, error, options) {
if (error) {
error({ code: 2, message: 'not available' });
}
};
}
};
if (window.geolocation) {
applyOverride(window.geolocation);
}
if (window.navigator.geolocation) {
applyOverride(window.navigator.geolocation);
}
};
/**
* Initializes and starts the auto-fetch worker IFF wbUseAFWorker is true
*/
WombatLite.prototype.initAutoFetchWorker = function() {
if (!this.$wbwindow.Worker) {
return;
}
var isTop = this.$wbwindow.self === this.$wbwindow.top;
if (this.$wbwindow.$WBAutoFetchWorker$ == null) {
this.WBAutoFetchWorker = new AutoFetchWorkerProxyMode(this, isTop);
// expose the WBAutoFetchWorker
Object.defineProperty(this.$wbwindow, '$WBAutoFetchWorker$', {
enumerable: false,
value: this.WBAutoFetchWorker
});
} else {
this.WBAutoFetchWorker = this.$wbwindow.$WBAutoFetchWorker$;
}
if (isTop) {
var wombatLite = this;
this.$wbwindow.addEventListener(
'message',
function(event) {
if (event.data && event.data.wb_type === 'aaworker') {
wombatLite.WBAutoFetchWorker.postMessage(event.data.msg);
}
},
false
);
}
};
/**
* Initialize wombat's internal state and apply all overrides
* @return {Object}
*/
WombatLite.prototype.wombatInit = function() {
if (this.wb_info.enable_auto_fetch && this.wb_info.is_live) {
this.initAutoFetchWorker();
}
// proxy mode overrides
// Random
this.initSeededRandom(this.wb_info.wombat_sec);
// Crypto Random
this.initCryptoRandom();
// set fixed pixel ratio
this.initFixedRatio();
// Date
this.initDateOverride(this.wb_info.wombat_sec);
// disable notifications
this.initDisableNotifications();
return { actual: false };
};