Skip to content

Commit

Permalink
Merge pull request #918 from capricorn86/task/849-disablejavascriptfi…
Browse files Browse the repository at this point in the history
…leloading-and-script-events

#849@minor: Adds support for triggering an error event when attemptin…
  • Loading branch information
capricorn86 committed May 13, 2023
2 parents 0744805 + b02520b commit 12a9f81
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 355 deletions.
88 changes: 5 additions & 83 deletions packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts
@@ -1,14 +1,13 @@
import IAttr from '../attr/IAttr';
import CSSStyleSheet from '../../css/CSSStyleSheet';
import ResourceFetch from '../../fetch/ResourceFetch';
import HTMLElement from '../html-element/HTMLElement';
import Document from '../document/Document';
import IHTMLLinkElement from './IHTMLLinkElement';
import Event from '../../event/Event';
import ErrorEvent from '../../event/events/ErrorEvent';
import INode from '../../nodes/node/INode';
import DOMTokenList from '../../dom-token-list/DOMTokenList';
import IDOMTokenList from '../../dom-token-list/IDOMTokenList';
import HTMLLinkElementUtility from './HTMLLinkElementUtility';

/**
* HTML Link Element.
Expand Down Expand Up @@ -184,51 +183,13 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle
*/
public override setAttributeNode(attribute: IAttr): IAttr {
const replacedAttribute = super.setAttributeNode(attribute);
const rel = this.getAttribute('rel');
const href = this.getAttribute('href');

if (attribute.name === 'rel' && this._relList) {
this._relList._updateIndices();
}

if (
(attribute.name === 'rel' || attribute.name === 'href') &&
href !== null &&
rel &&
rel.toLowerCase() === 'stylesheet' &&
this.isConnected &&
!this.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading
) {
(<Document>this.ownerDocument)._readyStateManager.startTask();
ResourceFetch.fetch(this.ownerDocument, href)
.then((code) => {
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(code);
(<CSSStyleSheet>this.sheet) = styleSheet;
this.dispatchEvent(new Event('load'));
(<Document>this.ownerDocument)._readyStateManager.endTask();
})
.catch((error) => {
this.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
this.ownerDocument.defaultView.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
(<Document>this.ownerDocument)._readyStateManager.endTask();
if (
!this['_listeners']['error'] &&
!this.ownerDocument.defaultView['_listeners']['error']
) {
this.ownerDocument.defaultView.console.error(error);
}
});
if (attribute.name === 'rel' || attribute.name === 'href') {
HTMLLinkElementUtility.loadExternalStylesheet(this);
}

return replacedAttribute;
Expand Down Expand Up @@ -256,47 +217,8 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle

super._connectToNode(parentNode);

if (
isParentConnected &&
isConnected !== isParentConnected &&
this._evaluateCSS &&
!this.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading
) {
const href = this.getAttribute('href');
const rel = this.getAttribute('rel');

if (href !== null && rel && rel.toLowerCase() === 'stylesheet') {
(<Document>this.ownerDocument)._readyStateManager.startTask();
ResourceFetch.fetch(this.ownerDocument, href)
.then((code) => {
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(code);
(<CSSStyleSheet>this.sheet) = styleSheet;
this.dispatchEvent(new Event('load'));
(<Document>this.ownerDocument)._readyStateManager.endTask();
})
.catch((error) => {
this.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
this.ownerDocument.defaultView.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
(<Document>this.ownerDocument)._readyStateManager.endTask();
if (
!this['_listeners']['error'] &&
!this.ownerDocument.defaultView['_listeners']['error']
) {
this.ownerDocument.defaultView.console.error(error);
}
});
}
if (isParentConnected && isConnected !== isParentConnected && this._evaluateCSS) {
HTMLLinkElementUtility.loadExternalStylesheet(this);
}
}
}
@@ -0,0 +1,82 @@
import Document from '../document/Document';
import Event from '../../event/Event';
import ErrorEvent from '../../event/events/ErrorEvent';
import ResourceFetch from '../../fetch/ResourceFetch';
import HTMLLinkElement from './HTMLLinkElement';
import CSSStyleSheet from '../../css/CSSStyleSheet';
import DOMException from '../../exception/DOMException';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum';

/**
* Helper class for getting the URL relative to a Location object.
*/
export default class HTMLLinkElementUtility {
/**
* Returns a URL relative to the given Location object.
*
* @param options Options.
* @param options.element Element.
* @param element
*/
public static async loadExternalStylesheet(element: HTMLLinkElement): Promise<void> {
const href = element.getAttribute('href');
const rel = element.getAttribute('rel');

if (href !== null && rel && rel.toLowerCase() === 'stylesheet' && element.isConnected) {
if (element.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading) {
this.onError(
element,
new DOMException(
`Failed to load external stylesheet "${href}". CSS file loading is disabled.`,
DOMExceptionNameEnum.notSupportedError
)
);
return;
}

(<Document>element.ownerDocument)._readyStateManager.startTask();

let code: string;
try {
code = await ResourceFetch.fetch(element.ownerDocument, href);
} catch (error) {
this.onError(element, error);
return;
}

const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(code);
(<CSSStyleSheet>element.sheet) = styleSheet;
element.dispatchEvent(new Event('load'));
(<Document>element.ownerDocument)._readyStateManager.endTask();
}
}

/**
* Triggered when an error occurs.
*
* @param element Element.
* @param error Error.
*/
private static onError(element: HTMLLinkElement, error: Error): void {
element.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
element.ownerDocument.defaultView.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
(<Document>element.ownerDocument)._readyStateManager.endTask();
if (
!element['_listeners']['error'] &&
!element.ownerDocument.defaultView['_listeners']['error']
) {
element.ownerDocument.defaultView.console.error(error);
}
}
}
@@ -1,7 +1,7 @@
import IAttr from '../attr/IAttr';
import HTMLElement from '../html-element/HTMLElement';
import IHTMLScriptElement from './IHTMLScriptElement';
import ScriptUtility from './ScriptUtility';
import HTMLScriptElementUtility from './HTMLScriptElementUtility';
import Event from '../../event/Event';
import ErrorEvent from '../../event/events/ErrorEvent';
import INode from '../../nodes/node/INode';
Expand Down Expand Up @@ -162,7 +162,7 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip
const replacedAttribute = super.setAttributeNode(attribute);

if (attribute.name === 'src' && attribute.value !== null && this.isConnected) {
ScriptUtility.loadExternalScript(this);
HTMLScriptElementUtility.loadExternalScript(this);
}

return replacedAttribute;
Expand Down Expand Up @@ -192,7 +192,7 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip
const src = this.getAttribute('src');

if (src !== null) {
ScriptUtility.loadExternalScript(this);
HTMLScriptElementUtility.loadExternalScript(this);
} else if (!this.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptEvaluation) {
const textContent = this.textContent;
const type = this.getAttribute('type');
Expand Down
@@ -0,0 +1,94 @@
import Document from '../document/Document';
import Event from '../../event/Event';
import ErrorEvent from '../../event/events/ErrorEvent';
import DOMException from '../../exception/DOMException';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum';
import ResourceFetch from '../../fetch/ResourceFetch';
import HTMLScriptElement from './HTMLScriptElement';

/**
* Helper class for getting the URL relative to a Location object.
*/
export default class HTMLScriptElementUtility {
/**
* Returns a URL relative to the given Location object.
*
* @param options Options.
* @param options.element Element.
* @param element
*/
public static async loadExternalScript(element: HTMLScriptElement): Promise<void> {
const src = element.getAttribute('src');
const async = element.getAttribute('async') !== null;

if (
element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptFileLoading ||
element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptEvaluation
) {
this.onError(
element,
new DOMException(
`Failed to load external script "${src}". JavaScript file loading is disabled.`,
DOMExceptionNameEnum.notSupportedError
)
);
return;
}

if (async) {
(<Document>element.ownerDocument)._readyStateManager.startTask();

let code = null;

try {
code = await ResourceFetch.fetch(element.ownerDocument, src);
} catch (error) {
this.onError(element, error);
return;
}

element.ownerDocument.defaultView.eval(code);
element.dispatchEvent(new Event('load'));
(<Document>element.ownerDocument)._readyStateManager.endTask();
} else {
let code = null;

try {
code = ResourceFetch.fetchSync(element.ownerDocument, src);
} catch (error) {
this.onError(element, error);
return;
}

element.ownerDocument.defaultView.eval(code);
element.dispatchEvent(new Event('load'));
}
}

/**
* Triggered when an error occurs.
*
* @param element Element.
* @param error Error.
*/
private static onError(element: HTMLScriptElement, error: Error): void {
element.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
element.ownerDocument.defaultView.dispatchEvent(
new ErrorEvent('error', {
message: error.message,
error
})
);
if (
!element['_listeners']['error'] &&
!element.ownerDocument.defaultView['_listeners']['error']
) {
element.ownerDocument.defaultView.console.error(error);
}
}
}

0 comments on commit 12a9f81

Please sign in to comment.