2020-11-12 22:32:53 +00:00
|
|
|
import {
|
2020-05-12 22:24:41 +00:00
|
|
|
FLASH_MIMETYPE,
|
|
|
|
FUTURESPLASH_MIMETYPE,
|
|
|
|
FLASH7_AND_8_MIMETYPE,
|
|
|
|
FLASH_MOVIE_MIMETYPE,
|
|
|
|
FLASH_ACTIVEX_CLASSID,
|
2021-08-24 18:52:02 +00:00
|
|
|
isBuiltInContextMenuVisible,
|
2021-10-17 16:49:37 +00:00
|
|
|
isFallbackElement,
|
2021-01-04 12:16:51 +00:00
|
|
|
isScriptAccessAllowed,
|
2020-11-16 21:54:35 +00:00
|
|
|
isSwfFilename,
|
2022-03-28 13:51:49 +00:00
|
|
|
isYoutubeFlashSource,
|
2022-03-28 18:14:58 +00:00
|
|
|
workaroundYoutubeMixedContent,
|
2020-05-12 22:24:41 +00:00
|
|
|
RufflePlayer,
|
2020-11-12 22:32:53 +00:00
|
|
|
} from "./ruffle-player";
|
2020-11-17 19:54:58 +00:00
|
|
|
import { registerElement } from "./register-element";
|
2022-08-26 19:52:21 +00:00
|
|
|
import type { URLLoadOptions, WindowMode } from "./load-options";
|
2020-11-25 06:21:54 +00:00
|
|
|
import { RuffleEmbed } from "./ruffle-embed";
|
2019-08-22 01:02:43 +00:00
|
|
|
|
2020-11-16 22:55:58 +00:00
|
|
|
/**
|
|
|
|
* Find and return the first value in obj with the given key.
|
|
|
|
* Many Flash params were case insensitive, so we use this when checking for them.
|
|
|
|
*
|
|
|
|
* @param obj Object to check
|
|
|
|
* @param key Key to find
|
|
|
|
* @param defaultValue Value if not found
|
2020-11-17 22:53:17 +00:00
|
|
|
* @returns Value if found, else [[defaultValue]]
|
2020-11-16 22:55:58 +00:00
|
|
|
*/
|
|
|
|
function findCaseInsensitive(
|
|
|
|
obj: { [key: string]: string | null },
|
|
|
|
key: string,
|
|
|
|
defaultValue: string | null
|
|
|
|
): string | null {
|
|
|
|
key = key.toLowerCase();
|
|
|
|
for (const k in obj) {
|
|
|
|
if (Object.hasOwnProperty.call(obj, k) && key === k.toLowerCase()) {
|
|
|
|
return obj[k];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
|
2020-11-16 22:57:29 +00:00
|
|
|
/**
|
|
|
|
* Returns all flash params ([[HTMLParamElement]]) that are for the given object.
|
|
|
|
*
|
|
|
|
* @param elem Element to check.
|
2020-11-17 22:53:17 +00:00
|
|
|
* @returns A record of every parameter.
|
2020-11-16 22:57:29 +00:00
|
|
|
*/
|
2022-05-05 09:42:45 +00:00
|
|
|
function paramsOf(elem: Element): Record<string, string> {
|
2020-11-16 22:57:29 +00:00
|
|
|
const params: Record<string, string> = {};
|
|
|
|
|
|
|
|
for (const param of elem.children) {
|
|
|
|
if (param instanceof HTMLParamElement) {
|
|
|
|
const key = param.attributes.getNamedItem("name")?.value;
|
|
|
|
const value = param.attributes.getNamedItem("value")?.value;
|
|
|
|
if (key && value) {
|
|
|
|
params[key] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return params;
|
|
|
|
}
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
/**
|
|
|
|
* A polyfill html element.
|
|
|
|
*
|
|
|
|
* This specific class tries to polyfill existing `<object>` tags,
|
|
|
|
* and should not be used. Prefer [[RufflePlayer]] instead.
|
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*/
|
2020-11-12 22:32:53 +00:00
|
|
|
export class RuffleObject extends RufflePlayer {
|
|
|
|
private params: Record<string, string> = {};
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
/**
|
|
|
|
* Constructs a new Ruffle flash player for insertion onto the page.
|
|
|
|
*
|
|
|
|
* This specific class tries to polyfill existing `<object>` tags,
|
|
|
|
* and should not be used. Prefer [[RufflePlayer]] instead.
|
|
|
|
*/
|
2020-11-12 22:32:53 +00:00
|
|
|
constructor() {
|
|
|
|
super();
|
2019-08-22 01:02:43 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
/**
|
|
|
|
* @ignore
|
2020-11-17 22:53:17 +00:00
|
|
|
* @internal
|
2020-11-16 22:52:39 +00:00
|
|
|
*/
|
|
|
|
connectedCallback(): void {
|
2019-08-25 19:46:38 +00:00
|
|
|
super.connectedCallback();
|
2020-05-12 22:24:41 +00:00
|
|
|
|
2020-11-16 22:57:29 +00:00
|
|
|
this.params = paramsOf(this);
|
2020-03-21 01:27:59 +00:00
|
|
|
|
2020-09-02 21:34:04 +00:00
|
|
|
let url = null;
|
2020-11-12 22:32:53 +00:00
|
|
|
if (this.attributes.getNamedItem("data")) {
|
|
|
|
url = this.attributes.getNamedItem("data")?.value;
|
2019-09-14 19:40:19 +00:00
|
|
|
} else if (this.params.movie) {
|
2020-09-02 21:34:04 +00:00
|
|
|
url = this.params.movie;
|
|
|
|
}
|
|
|
|
|
2021-01-31 00:59:58 +00:00
|
|
|
const allowScriptAccess = findCaseInsensitive(
|
|
|
|
this.params,
|
|
|
|
"allowScriptAccess",
|
|
|
|
null
|
|
|
|
);
|
|
|
|
|
2020-11-16 22:55:58 +00:00
|
|
|
const parameters = findCaseInsensitive(
|
2020-10-11 18:35:28 +00:00
|
|
|
this.params,
|
|
|
|
"flashvars",
|
2020-11-16 22:52:39 +00:00
|
|
|
this.getAttribute("flashvars")
|
2020-10-11 18:35:28 +00:00
|
|
|
);
|
|
|
|
|
2021-01-13 08:54:23 +00:00
|
|
|
const backgroundColor = findCaseInsensitive(
|
|
|
|
this.params,
|
|
|
|
"bgcolor",
|
|
|
|
this.getAttribute("bgcolor")
|
|
|
|
);
|
|
|
|
|
2021-08-08 20:38:55 +00:00
|
|
|
const base = findCaseInsensitive(
|
|
|
|
this.params,
|
|
|
|
"base",
|
|
|
|
this.getAttribute("base")
|
|
|
|
);
|
|
|
|
|
2021-08-24 18:52:02 +00:00
|
|
|
const menu = findCaseInsensitive(this.params, "menu", null);
|
2021-09-02 13:14:11 +00:00
|
|
|
const salign = findCaseInsensitive(this.params, "salign", "");
|
|
|
|
const quality = findCaseInsensitive(this.params, "quality", "high");
|
|
|
|
const scale = findCaseInsensitive(this.params, "scale", "showAll");
|
2022-04-13 18:55:15 +00:00
|
|
|
const wmode = findCaseInsensitive(this.params, "wmode", "window");
|
2021-08-24 18:52:02 +00:00
|
|
|
|
2020-09-02 21:34:04 +00:00
|
|
|
if (url) {
|
2021-01-31 00:59:58 +00:00
|
|
|
const options: URLLoadOptions = { url };
|
|
|
|
options.allowScriptAccess = isScriptAccessAllowed(
|
2021-01-04 12:16:51 +00:00
|
|
|
allowScriptAccess,
|
|
|
|
url
|
2020-11-16 22:52:39 +00:00
|
|
|
);
|
2020-11-25 16:22:01 +00:00
|
|
|
if (parameters) {
|
|
|
|
options.parameters = parameters;
|
|
|
|
}
|
2021-01-13 08:54:23 +00:00
|
|
|
if (backgroundColor) {
|
|
|
|
options.backgroundColor = backgroundColor;
|
|
|
|
}
|
2021-08-08 20:38:55 +00:00
|
|
|
if (base) {
|
|
|
|
options.base = base;
|
|
|
|
}
|
2021-08-24 18:52:02 +00:00
|
|
|
options.menu = isBuiltInContextMenuVisible(menu);
|
2021-09-02 13:14:11 +00:00
|
|
|
if (salign) {
|
|
|
|
options.salign = salign;
|
|
|
|
}
|
|
|
|
if (quality) {
|
|
|
|
options.quality = quality;
|
|
|
|
}
|
|
|
|
if (scale) {
|
|
|
|
options.scale = scale;
|
|
|
|
}
|
2022-04-13 18:55:15 +00:00
|
|
|
if (wmode) {
|
|
|
|
options.wmode = wmode as WindowMode;
|
|
|
|
}
|
2021-01-31 00:59:58 +00:00
|
|
|
|
|
|
|
// Kick off the SWF download.
|
2020-11-25 16:22:01 +00:00
|
|
|
this.load(options);
|
2019-08-22 01:02:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
protected debugPlayerInfo(): string {
|
2020-11-16 23:07:01 +00:00
|
|
|
let errorText = super.debugPlayerInfo();
|
|
|
|
errorText += "Player type: Object\n";
|
2020-09-12 22:03:19 +00:00
|
|
|
|
|
|
|
let url = null;
|
|
|
|
|
2020-11-12 22:32:53 +00:00
|
|
|
if (this.attributes.getNamedItem("data")) {
|
|
|
|
url = this.attributes.getNamedItem("data")?.value;
|
2020-09-12 22:03:19 +00:00
|
|
|
} else if (this.params.movie) {
|
|
|
|
url = this.params.movie;
|
|
|
|
}
|
2020-11-16 23:07:01 +00:00
|
|
|
errorText += `SWF URL: ${url}\n`;
|
2020-09-12 22:03:19 +00:00
|
|
|
|
|
|
|
Object.keys(this.params).forEach((key) => {
|
2020-11-16 23:07:01 +00:00
|
|
|
errorText += `Param ${key}: ${this.params[key]}\n`;
|
2020-09-12 22:03:19 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
Object.keys(this.attributes).forEach((key) => {
|
2020-11-16 23:07:01 +00:00
|
|
|
errorText += `Attribute ${key}: ${
|
2020-11-12 22:32:53 +00:00
|
|
|
this.attributes.getNamedItem(key)?.value
|
|
|
|
}\n`;
|
2020-09-12 22:03:19 +00:00
|
|
|
});
|
|
|
|
|
2020-11-16 23:07:01 +00:00
|
|
|
return errorText;
|
2020-09-12 22:03:19 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
/**
|
|
|
|
* Polyfill of HTMLObjectElement.
|
|
|
|
*
|
|
|
|
* @ignore
|
2020-11-17 22:53:17 +00:00
|
|
|
* @internal
|
2020-11-16 22:52:39 +00:00
|
|
|
*/
|
|
|
|
get data(): string | null {
|
|
|
|
return this.getAttribute("data");
|
2019-09-14 19:40:19 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
/**
|
|
|
|
* Polyfill of HTMLObjectElement.
|
|
|
|
*
|
|
|
|
* @ignore
|
2020-11-17 22:53:17 +00:00
|
|
|
* @internal
|
2020-11-16 22:52:39 +00:00
|
|
|
*/
|
|
|
|
set data(href: string | null) {
|
2022-05-05 09:16:29 +00:00
|
|
|
if (href) {
|
2020-11-12 22:32:53 +00:00
|
|
|
const attr = document.createAttribute("data");
|
|
|
|
attr.value = href;
|
|
|
|
this.attributes.setNamedItem(attr);
|
|
|
|
} else {
|
|
|
|
this.attributes.removeNamedItem("data");
|
|
|
|
}
|
2019-09-14 19:40:19 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
/**
|
|
|
|
* Checks if the given element may be polyfilled with this one.
|
|
|
|
*
|
|
|
|
* @param elem Element to check.
|
2020-11-17 22:53:17 +00:00
|
|
|
* @returns True if the element looks like a flash object.
|
2020-11-16 22:52:39 +00:00
|
|
|
*/
|
2022-05-05 09:42:45 +00:00
|
|
|
static isInterdictable(elem: Element): boolean {
|
2021-10-17 16:49:37 +00:00
|
|
|
// Don't polyfill if the element is inside a specific node.
|
|
|
|
if (isFallbackElement(elem)) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-04-03 14:04:16 +00:00
|
|
|
// Don't polyfill if there's already a <ruffle-object> or a <ruffle-embed> inside the <object>.
|
|
|
|
if (
|
|
|
|
elem.getElementsByTagName("ruffle-object").length > 0 ||
|
|
|
|
elem.getElementsByTagName("ruffle-embed").length > 0
|
|
|
|
) {
|
2020-11-28 12:39:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-10-17 16:49:37 +00:00
|
|
|
|
2020-11-25 06:21:54 +00:00
|
|
|
// Don't polyfill if no movie specified.
|
2020-11-12 22:32:53 +00:00
|
|
|
const data = elem.attributes.getNamedItem("data")?.value.toLowerCase();
|
2020-11-25 06:21:54 +00:00
|
|
|
const params = paramsOf(elem);
|
|
|
|
let isSwf;
|
|
|
|
// Check for SWF file.
|
|
|
|
if (data) {
|
2022-03-28 13:51:49 +00:00
|
|
|
// Don't polyfill when the file is a Youtube Flash source.
|
|
|
|
if (isYoutubeFlashSource(data)) {
|
2022-03-28 18:14:58 +00:00
|
|
|
// Workaround YouTube mixed content; this isn't what browsers do automatically, but while we're here, we may as well
|
|
|
|
workaroundYoutubeMixedContent(elem, "data");
|
2022-03-28 13:51:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-11-25 06:21:54 +00:00
|
|
|
isSwf = isSwfFilename(data);
|
|
|
|
} else if (params && params.movie) {
|
2022-03-28 13:51:49 +00:00
|
|
|
// Don't polyfill when the file is a Youtube Flash source.
|
|
|
|
if (isYoutubeFlashSource(params.movie)) {
|
2022-03-28 18:14:58 +00:00
|
|
|
// Workaround YouTube mixed content; this isn't what browsers do automatically, but while we're here, we may as well
|
2022-05-05 09:42:45 +00:00
|
|
|
const movie_elem = elem.querySelector("param[name='movie']");
|
2022-03-28 18:14:58 +00:00
|
|
|
if (movie_elem) {
|
|
|
|
workaroundYoutubeMixedContent(movie_elem, "value");
|
|
|
|
// The data attribute needs to be set for the re-fetch to happen
|
2022-04-10 19:04:16 +00:00
|
|
|
// It also needs to be set on Firefox for the YouTube object rewrite to work, regardless of mixed content
|
|
|
|
const movie_src = movie_elem.getAttribute("value");
|
|
|
|
if (movie_src) {
|
|
|
|
elem.setAttribute("data", movie_src);
|
2022-03-28 18:14:58 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-28 13:51:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-11-25 06:21:54 +00:00
|
|
|
isSwf = isSwfFilename(params.movie);
|
|
|
|
} else {
|
|
|
|
// Don't polyfill when no file is specified.
|
|
|
|
return false;
|
2020-05-12 06:33:46 +00:00
|
|
|
}
|
2020-11-12 22:32:53 +00:00
|
|
|
|
2020-11-25 06:21:54 +00:00
|
|
|
// Check ActiveX class ID.
|
2020-11-12 22:32:53 +00:00
|
|
|
const classid = elem.attributes
|
|
|
|
.getNamedItem("classid")
|
|
|
|
?.value.toLowerCase();
|
2020-11-25 06:21:54 +00:00
|
|
|
if (classid === FLASH_ACTIVEX_CLASSID.toLowerCase()) {
|
|
|
|
// classid is an old-IE style embed that would not work on modern browsers.
|
|
|
|
// Often there will be an <embed> inside the <object> that would take precedence.
|
2022-04-03 14:04:16 +00:00
|
|
|
// Only polyfill this <object> if it doesn't contain a polyfillable <embed> or
|
|
|
|
// another <object> that would be supported on modern browsers.
|
|
|
|
return (
|
|
|
|
!Array.from(elem.getElementsByTagName("object")).some(
|
|
|
|
RuffleObject.isInterdictable
|
|
|
|
) &&
|
|
|
|
!Array.from(elem.getElementsByTagName("embed")).some(
|
|
|
|
RuffleEmbed.isInterdictable
|
|
|
|
)
|
2020-11-25 06:21:54 +00:00
|
|
|
);
|
2022-05-05 09:16:29 +00:00
|
|
|
} else if (classid) {
|
2020-11-25 06:21:54 +00:00
|
|
|
// Non-Flash classid.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for MIME type.
|
2022-05-05 09:16:29 +00:00
|
|
|
const type = elem.attributes.getNamedItem("type");
|
|
|
|
if (!type) {
|
|
|
|
// If no MIME type is specified, polyfill if movie is an SWF file.
|
|
|
|
return isSwf;
|
2020-03-21 01:27:59 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 09:16:29 +00:00
|
|
|
switch (type.value.toLowerCase()) {
|
|
|
|
case FLASH_MIMETYPE.toLowerCase():
|
|
|
|
case FUTURESPLASH_MIMETYPE.toLowerCase():
|
|
|
|
case FLASH7_AND_8_MIMETYPE.toLowerCase():
|
|
|
|
case FLASH_MOVIE_MIMETYPE.toLowerCase():
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2019-08-22 01:02:43 +00:00
|
|
|
}
|
|
|
|
|
2020-11-16 22:52:39 +00:00
|
|
|
/**
|
|
|
|
* Creates a RuffleObject that will polyfill and replace the given element.
|
|
|
|
*
|
|
|
|
* @param elem Element to replace.
|
2020-11-17 22:53:17 +00:00
|
|
|
* @returns Created RuffleObject.
|
2020-11-16 22:52:39 +00:00
|
|
|
*/
|
2022-05-05 09:42:45 +00:00
|
|
|
static fromNativeObjectElement(elem: Element): RuffleObject {
|
2020-11-17 19:54:58 +00:00
|
|
|
const externalName = registerElement("ruffle-object", RuffleObject);
|
2020-11-16 23:07:01 +00:00
|
|
|
const ruffleObj: RuffleObject = <RuffleObject>(
|
|
|
|
document.createElement(externalName)
|
2020-11-12 22:32:53 +00:00
|
|
|
);
|
2020-11-25 06:21:54 +00:00
|
|
|
|
|
|
|
// Avoid copying embeds-inside-objects to avoid double polyfilling.
|
|
|
|
for (const embedElem of Array.from(
|
|
|
|
elem.getElementsByTagName("embed")
|
|
|
|
)) {
|
|
|
|
if (RuffleEmbed.isInterdictable(embedElem)) {
|
|
|
|
embedElem.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-10 21:36:40 +00:00
|
|
|
// Avoid copying objects-inside-objects to avoid double polyfilling.
|
|
|
|
// This may happen when Internet Explorer's conditional comments are used.
|
|
|
|
for (const objectElem of Array.from(
|
|
|
|
elem.getElementsByTagName("object")
|
|
|
|
)) {
|
|
|
|
if (RuffleObject.isInterdictable(objectElem)) {
|
|
|
|
objectElem.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 23:07:01 +00:00
|
|
|
ruffleObj.copyElement(elem);
|
2019-08-22 01:02:43 +00:00
|
|
|
|
2020-11-16 23:07:01 +00:00
|
|
|
return ruffleObj;
|
2019-08-22 01:02:43 +00:00
|
|
|
}
|
2020-11-12 22:32:53 +00:00
|
|
|
}
|