Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#849@minor: Adds support for triggering an error event when attemptin… #918

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading