2020-11-12 22:32:53 +00:00
|
|
|
import { RuffleObject } from "./ruffle-object";
|
|
|
|
import { RuffleEmbed } from "./ruffle-embed";
|
2020-11-17 22:18:04 +00:00
|
|
|
import { installPlugin, FLASH_PLUGIN } from "./plugin-polyfill";
|
2020-11-17 22:16:55 +00:00
|
|
|
import { publicPath } from "./public-path";
|
2020-11-17 21:48:34 +00:00
|
|
|
import { Config } from "./config";
|
2020-11-12 22:32:53 +00:00
|
|
|
|
2020-12-03 18:09:07 +00:00
|
|
|
let isExtension: boolean;
|
|
|
|
const globalConfig: Config = window.RufflePlayer?.config ?? {};
|
|
|
|
const jsScriptUrl = publicPath(globalConfig, "ruffle.js") + "ruffle.js";
|
2020-03-25 00:16:40 +00:00
|
|
|
|
2019-08-22 01:02:43 +00:00
|
|
|
/**
|
2020-12-03 18:09:07 +00:00
|
|
|
* Polyfill native Flash elements with Ruffle equivalents.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-16 02:02:03 +00:00
|
|
|
* This polyfill isn't fool-proof: If there's a chance site JavaScript has
|
|
|
|
* access to a pre-polyfill element, then this will break horribly. We can
|
2019-08-22 01:02:43 +00:00
|
|
|
* keep native objects out of the DOM, and thus out of JavaScript's grubby
|
|
|
|
* little hands, but only if we load first.
|
|
|
|
*/
|
2020-12-03 18:09:07 +00:00
|
|
|
let objects: HTMLCollectionOf<HTMLObjectElement>;
|
|
|
|
let embeds: HTMLCollectionOf<HTMLEmbedElement>;
|
2020-11-17 22:53:17 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2020-12-03 18:09:07 +00:00
|
|
|
function polyfillFlashInstances(): void {
|
2019-08-25 20:04:40 +00:00
|
|
|
try {
|
2020-03-25 00:16:40 +00:00
|
|
|
// Create live collections to track embed tags.
|
|
|
|
objects = objects || document.getElementsByTagName("object");
|
|
|
|
embeds = embeds || document.getElementsByTagName("embed");
|
2019-08-22 01:02:43 +00:00
|
|
|
|
2020-03-25 00:16:40 +00:00
|
|
|
// Replace <object> first, because <object> often wraps <embed>.
|
2020-11-12 22:32:53 +00:00
|
|
|
for (const elem of Array.from(objects)) {
|
2020-11-16 23:03:23 +00:00
|
|
|
if (RuffleObject.isInterdictable(elem)) {
|
2020-11-17 21:55:29 +00:00
|
|
|
const ruffleObject = RuffleObject.fromNativeObjectElement(elem);
|
|
|
|
elem.replaceWith(ruffleObject);
|
2020-03-25 00:16:40 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-12 22:32:53 +00:00
|
|
|
for (const elem of Array.from(embeds)) {
|
2020-11-17 19:53:08 +00:00
|
|
|
if (RuffleEmbed.isInterdictable(elem)) {
|
2020-12-03 18:09:07 +00:00
|
|
|
const ruffleEmbed = RuffleEmbed.fromNativeEmbedElement(elem);
|
|
|
|
elem.replaceWith(ruffleEmbed);
|
2019-08-25 20:04:40 +00:00
|
|
|
}
|
2019-08-22 01:02:43 +00:00
|
|
|
}
|
2019-08-25 20:04:40 +00:00
|
|
|
} catch (err) {
|
2020-05-12 22:24:41 +00:00
|
|
|
console.error(
|
2020-12-03 18:09:07 +00:00
|
|
|
`Serious error encountered when polyfilling native Flash elements: ${err}`
|
2020-05-12 22:24:41 +00:00
|
|
|
);
|
2019-08-22 01:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-17 22:53:17 +00:00
|
|
|
/**
|
2020-12-03 18:09:07 +00:00
|
|
|
* Inject Ruffle into <iframe> and <frame> elements.
|
2020-11-17 22:53:17 +00:00
|
|
|
*
|
2020-12-03 18:09:07 +00:00
|
|
|
* This polyfill isn't fool-proof either: On self-hosted builds, it may
|
|
|
|
* not work due to browsers CORS policy or be loaded too late for some
|
|
|
|
* libraries like SWFObject. These should be less of a problem on the
|
|
|
|
* web extension. This polyfill should, however, do the trick in most
|
|
|
|
* cases, but users should be aware of its natural limits.
|
2020-11-17 22:53:17 +00:00
|
|
|
*/
|
2020-12-03 18:09:07 +00:00
|
|
|
let iframes: HTMLCollectionOf<HTMLIFrameElement>;
|
|
|
|
let frames: HTMLCollectionOf<HTMLFrameElement>;
|
2020-11-17 22:53:17 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2020-12-03 18:09:07 +00:00
|
|
|
function polyfillFrames(): void {
|
|
|
|
// Create live collections to track embed tags.
|
|
|
|
iframes = iframes || document.getElementsByTagName("iframe");
|
|
|
|
frames = frames || document.getElementsByTagName("frame");
|
2019-08-22 01:02:43 +00:00
|
|
|
|
2020-12-03 18:09:07 +00:00
|
|
|
[iframes, frames].forEach((elementsList) => {
|
|
|
|
for (let i = 0; i < elementsList.length; i++) {
|
|
|
|
const element = elementsList[i];
|
|
|
|
if (element.dataset.rufflePolyfilled !== undefined) {
|
|
|
|
// Don't re-polyfill elements with the "data-ruffle-polyfilled" attribute.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
element.dataset.rufflePolyfilled = "";
|
2019-08-25 21:40:40 +00:00
|
|
|
|
2020-12-03 18:09:07 +00:00
|
|
|
const elementWindow = element.contentWindow;
|
2020-04-02 14:08:33 +00:00
|
|
|
|
2020-12-03 18:09:07 +00:00
|
|
|
// Cross origin requests may reach an exception, so let's prepare for this eventuality.
|
|
|
|
const errorMessage = `Couldn't load Ruffle into ${element.tagName}[${element.src}]: `;
|
2020-09-08 14:19:55 +00:00
|
|
|
try {
|
2020-12-03 18:09:07 +00:00
|
|
|
if (elementWindow!.document!.readyState === "complete") {
|
|
|
|
injectRuffle(elementWindow!, errorMessage);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
if (!isExtension) {
|
|
|
|
// The web extension should be able to load Ruffle into cross origin frames
|
|
|
|
// because it has "all_frames" set to true in its manifest.json: RufflePlayer
|
|
|
|
// config won't be injected but it's not worth showing an error.
|
|
|
|
console.warn(errorMessage + err);
|
2020-09-08 14:19:55 +00:00
|
|
|
}
|
2020-11-12 22:32:53 +00:00
|
|
|
}
|
2020-12-03 18:09:07 +00:00
|
|
|
|
|
|
|
// Attach listener to the element to handle frame navigation.
|
|
|
|
element.addEventListener(
|
|
|
|
"load",
|
|
|
|
() => {
|
|
|
|
injectRuffle(elementWindow!, errorMessage);
|
|
|
|
},
|
|
|
|
false
|
|
|
|
);
|
2020-05-23 17:26:40 +00:00
|
|
|
}
|
2020-12-03 18:09:07 +00:00
|
|
|
});
|
2020-04-02 14:08:33 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 22:53:17 +00:00
|
|
|
/**
|
2020-12-03 18:09:07 +00:00
|
|
|
* @param elementWindow The (i)frame's window object.
|
|
|
|
* @param errorMessage The message to log when Ruffle cannot access the (i)frame's document.
|
2020-11-17 22:53:17 +00:00
|
|
|
*/
|
2020-12-03 18:09:07 +00:00
|
|
|
async function injectRuffle(
|
|
|
|
elementWindow: Window,
|
|
|
|
errorMessage: string
|
|
|
|
): Promise<void> {
|
|
|
|
// The document is supposed to be completely loaded when this function is run.
|
|
|
|
// As Chrome may be unable to read from the dataset below, we have to delay the execution a little bit.
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
window.setTimeout(() => {
|
|
|
|
resolve();
|
|
|
|
}, 100);
|
|
|
|
});
|
2020-05-23 17:26:40 +00:00
|
|
|
|
2020-12-03 18:09:07 +00:00
|
|
|
let elementDocument: HTMLDocument;
|
|
|
|
try {
|
|
|
|
elementDocument = elementWindow.document;
|
|
|
|
} catch (err) {
|
|
|
|
if (!isExtension) {
|
|
|
|
console.warn(errorMessage + err);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2020-04-02 14:08:33 +00:00
|
|
|
|
2020-12-03 18:09:07 +00:00
|
|
|
if (
|
|
|
|
!isExtension &&
|
|
|
|
elementDocument.documentElement.dataset.ruffleOptout !== undefined
|
|
|
|
) {
|
|
|
|
// Don't polyfill elements with the "data-ruffle-optout" attribute.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isExtension) {
|
|
|
|
if (!elementWindow.RufflePlayer) {
|
|
|
|
elementWindow.RufflePlayer = {};
|
|
|
|
const script = elementDocument.createElement("script");
|
|
|
|
script.setAttribute("src", jsScriptUrl);
|
|
|
|
script.onload = () => {
|
|
|
|
// Inject parent configuration once the script is loaded, preventing it from being ignored.
|
|
|
|
elementWindow!.RufflePlayer!.config = globalConfig;
|
|
|
|
};
|
|
|
|
elementDocument.head.appendChild(script);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!elementWindow.RufflePlayer) {
|
|
|
|
elementWindow.RufflePlayer = {};
|
|
|
|
}
|
|
|
|
// Merge parent window and frame configurations, will likely be applied too late though.
|
|
|
|
elementWindow.RufflePlayer.config = {
|
|
|
|
...globalConfig,
|
|
|
|
...(elementWindow.RufflePlayer?.config ?? {}),
|
|
|
|
};
|
2020-04-02 14:08:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-17 22:53:17 +00:00
|
|
|
/**
|
2020-12-03 18:09:07 +00:00
|
|
|
* Listen for changes to the DOM.
|
2020-11-17 22:53:17 +00:00
|
|
|
*
|
|
|
|
*/
|
2020-12-03 18:09:07 +00:00
|
|
|
function initMutationObserver(): void {
|
|
|
|
const observer = new MutationObserver(function (mutationsList) {
|
|
|
|
// If any nodes were added, re-run the polyfill to detect any new instances.
|
|
|
|
const nodesAdded = mutationsList.some(
|
|
|
|
(mutation) => mutation.addedNodes.length > 0
|
|
|
|
);
|
|
|
|
if (nodesAdded) {
|
|
|
|
polyfillFlashInstances();
|
|
|
|
polyfillFrames();
|
|
|
|
}
|
|
|
|
});
|
2020-05-12 22:24:41 +00:00
|
|
|
observer.observe(document, { childList: true, subtree: true });
|
2020-04-02 14:08:33 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 21:48:34 +00:00
|
|
|
/**
|
2020-12-03 18:09:07 +00:00
|
|
|
* Polyfills the detection of Flash plugins in the browser.
|
2020-11-17 21:48:34 +00:00
|
|
|
*/
|
2020-11-17 21:53:04 +00:00
|
|
|
export function pluginPolyfill(): void {
|
2020-11-17 22:18:04 +00:00
|
|
|
installPlugin(FLASH_PLUGIN);
|
2020-11-12 22:32:53 +00:00
|
|
|
}
|
2019-10-13 02:57:03 +00:00
|
|
|
|
2020-11-17 21:48:34 +00:00
|
|
|
/**
|
2020-12-03 18:09:07 +00:00
|
|
|
* Polyfills legacy Flash content on the page.
|
|
|
|
*
|
|
|
|
* @param isExt Whether or not Ruffle is running as a browser's extension.
|
2020-11-17 21:48:34 +00:00
|
|
|
*/
|
2020-12-03 18:09:07 +00:00
|
|
|
export function polyfill(isExt: boolean): void {
|
|
|
|
isExtension = isExt;
|
|
|
|
polyfillFlashInstances();
|
|
|
|
polyfillFrames();
|
|
|
|
initMutationObserver();
|
2020-11-12 22:32:53 +00:00
|
|
|
}
|