jsdom
Version:
A JavaScript implementation of many web standards
368 lines (279 loc) • 7.79 kB
JavaScript
"use strict";
const whatwgURL = require("whatwg-url");
const { parseURLToResultingURLRecord } = require("../helpers/document-base-url");
const { asciiCaseInsensitiveMatch } = require("../helpers/strings");
const { navigate } = require("../window/navigation");
exports.implementation = class HTMLHyperlinkElementUtilsImpl {
_htmlHyperlinkElementUtilsSetup() {
this.url = null;
}
// https://html.spec.whatwg.org/multipage/links.html#cannot-navigate
_cannotNavigate() {
// TODO: Correctly check if the document is fully active
return this._localName !== "a" && !this.isConnected;
}
// https://html.spec.whatwg.org/multipage/semantics.html#get-an-element's-target
_getAnElementsTarget() {
if (this.hasAttributeNS(null, "target")) {
return this.getAttributeNS(null, "target");
}
const baseEl = this._ownerDocument.querySelector("base[target]");
if (baseEl) {
return baseEl.getAttributeNS(null, "target");
}
return "";
}
// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
_chooseABrowsingContext(name, current) {
let chosen = null;
if (name === "" || asciiCaseInsensitiveMatch(name, "_self")) {
chosen = current;
} else if (asciiCaseInsensitiveMatch(name, "_parent")) {
chosen = current.parent;
} else if (asciiCaseInsensitiveMatch(name, "_top")) {
chosen = current.top;
} else if (!asciiCaseInsensitiveMatch(name, "_blank")) {
// https://github.com/whatwg/html/issues/1440
}
// TODO: Create new browsing context, handle noopener
return chosen;
}
// https://html.spec.whatwg.org/multipage/links.html#following-hyperlinks-2
_followAHyperlink() {
if (this._cannotNavigate()) {
return;
}
const source = this._ownerDocument._defaultView;
let targetAttributeValue = "";
if (this._localName === "a" || this._localName === "area") {
targetAttributeValue = this._getAnElementsTarget();
}
const noopener = this.relList.contains("noreferrer") || this.relList.contains("noopener");
const target = this._chooseABrowsingContext(targetAttributeValue, source, noopener);
if (target === null) {
return;
}
const url = parseURLToResultingURLRecord(this.href, this._ownerDocument);
if (url === null) {
return;
}
// TODO: Handle hyperlink suffix and referrerpolicy
setTimeout(() => {
navigate(target, url, {});
}, 0);
}
toString() {
return this.href;
}
get href() {
reinitializeURL(this);
const { url } = this;
if (url === null) {
const href = this.getAttributeNS(null, "href");
return href === null ? "" : href;
}
return whatwgURL.serializeURL(url);
}
set href(v) {
this.setAttributeNS(null, "href", v);
}
get origin() {
reinitializeURL(this);
if (this.url === null) {
return "";
}
return whatwgURL.serializeURLOrigin(this.url);
}
get protocol() {
reinitializeURL(this);
if (this.url === null) {
return ":";
}
return this.url.scheme + ":";
}
set protocol(v) {
reinitializeURL(this);
if (this.url === null) {
return;
}
whatwgURL.basicURLParse(v + ":", { url: this.url, stateOverride: "scheme start" });
updateHref(this);
}
get username() {
reinitializeURL(this);
if (this.url === null) {
return "";
}
return this.url.username;
}
set username(v) {
reinitializeURL(this);
const { url } = this;
if (url === null || whatwgURL.cannotHaveAUsernamePasswordPort(url)) {
return;
}
whatwgURL.setTheUsername(url, v);
updateHref(this);
}
get password() {
reinitializeURL(this);
const { url } = this;
if (url === null) {
return "";
}
return url.password;
}
set password(v) {
reinitializeURL(this);
const { url } = this;
if (url === null || whatwgURL.cannotHaveAUsernamePasswordPort(url)) {
return;
}
whatwgURL.setThePassword(url, v);
updateHref(this);
}
get host() {
reinitializeURL(this);
const { url } = this;
if (url === null || url.host === null) {
return "";
}
if (url.port === null) {
return whatwgURL.serializeHost(url.host);
}
return whatwgURL.serializeHost(url.host) + ":" + whatwgURL.serializeInteger(url.port);
}
set host(v) {
reinitializeURL(this);
const { url } = this;
if (url === null || whatwgURL.hasAnOpaquePath(url)) {
return;
}
whatwgURL.basicURLParse(v, { url, stateOverride: "host" });
updateHref(this);
}
get hostname() {
reinitializeURL(this);
const { url } = this;
if (url === null || url.host === null) {
return "";
}
return whatwgURL.serializeHost(url.host);
}
set hostname(v) {
reinitializeURL(this);
const { url } = this;
if (url === null || whatwgURL.hasAnOpaquePath(url)) {
return;
}
whatwgURL.basicURLParse(v, { url, stateOverride: "hostname" });
updateHref(this);
}
get port() {
reinitializeURL(this);
const { url } = this;
if (url === null || url.port === null) {
return "";
}
return whatwgURL.serializeInteger(url.port);
}
set port(v) {
reinitializeURL(this);
const { url } = this;
if (url === null || whatwgURL.cannotHaveAUsernamePasswordPort(url)) {
return;
}
if (v === "") {
url.port = null;
} else {
whatwgURL.basicURLParse(v, { url, stateOverride: "port" });
}
updateHref(this);
}
get pathname() {
reinitializeURL(this);
const { url } = this;
if (url === null) {
return "";
}
return whatwgURL.serializePath(url);
}
set pathname(v) {
reinitializeURL(this);
const { url } = this;
if (url === null || whatwgURL.hasAnOpaquePath(url)) {
return;
}
url.path = [];
whatwgURL.basicURLParse(v, { url, stateOverride: "path start" });
updateHref(this);
}
get search() {
reinitializeURL(this);
const { url } = this;
if (url === null || url.query === null || url.query === "") {
return "";
}
return "?" + url.query;
}
set search(v) {
reinitializeURL(this);
const { url } = this;
if (url === null) {
return;
}
if (v === "") {
url.query = null;
} else {
const input = v[0] === "?" ? v.substring(1) : v;
url.query = "";
whatwgURL.basicURLParse(input, {
url,
stateOverride: "query",
encodingOverride: this._ownerDocument.charset
});
}
updateHref(this);
}
get hash() {
reinitializeURL(this);
const { url } = this;
if (url === null || url.fragment === null || url.fragment === "") {
return "";
}
return "#" + url.fragment;
}
set hash(v) {
reinitializeURL(this);
const { url } = this;
if (url === null) {
return;
}
if (v === "") {
url.fragment = null;
} else {
const input = v[0] === "#" ? v.substring(1) : v;
url.fragment = "";
whatwgURL.basicURLParse(input, { url, stateOverride: "fragment" });
}
updateHref(this);
}
};
function reinitializeURL(hheu) {
if (hheu.url !== null && hheu.url.scheme === "blob" && whatwgURL.hasAnOpaquePath(hheu.url)) {
return;
}
setTheURL(hheu);
}
function setTheURL(hheu) {
const href = hheu.getAttributeNS(null, "href");
if (href === null) {
hheu.url = null;
return;
}
const parsed = parseURLToResultingURLRecord(href, hheu._ownerDocument);
hheu.url = parsed === null ? null : parsed;
}
function updateHref(hheu) {
hheu.setAttributeNS(null, "href", whatwgURL.serializeURL(hheu.url));
}