mirror of
https://github.com/webrecorder/pywb.git
synced 2025-03-26 07:49:24 +01:00
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
254 lines
7.1 KiB
JavaScript
Executable File
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 };
|
|
};
|