Skip to content

Commit 3102a33

Browse files
committed
Introduce HyperlinkElement and HyperlinkElementSupport to share some functionality between anchor and area tags.
This brings also the area tag to the same support level than anchor
1 parent 716576b commit 3102a33

5 files changed

Lines changed: 1632 additions & 874 deletions

File tree

src/main/java/org/htmlunit/html/HtmlAnchor.java

Lines changed: 8 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,16 @@
1414
*/
1515
package org.htmlunit.html;
1616

17-
import static org.htmlunit.BrowserVersionFeatures.ANCHOR_SEND_PING_REQUEST;
18-
import static org.htmlunit.BrowserVersionFeatures.FORM_SUBMISSION_HEADER_CACHE_CONTROL_MAX_AGE;
19-
2017
import java.io.IOException;
2118
import java.net.MalformedURLException;
2219
import java.net.URL;
23-
import java.util.Locale;
2420
import java.util.Map;
2521

26-
import org.apache.commons.logging.Log;
27-
import org.apache.commons.logging.LogFactory;
28-
import org.htmlunit.BrowserVersion;
29-
import org.htmlunit.FormEncodingType;
30-
import org.htmlunit.HttpHeader;
31-
import org.htmlunit.HttpMethod;
3222
import org.htmlunit.Page;
3323
import org.htmlunit.SgmlPage;
34-
import org.htmlunit.WebClient;
35-
import org.htmlunit.WebRequest;
3624
import org.htmlunit.WebWindow;
3725
import org.htmlunit.javascript.host.event.Event;
3826
import org.htmlunit.javascript.host.html.HTMLElement;
39-
import org.htmlunit.protocol.javascript.JavaScriptURLConnection;
40-
import org.htmlunit.util.ArrayUtils;
41-
import org.htmlunit.util.StringUtils;
42-
import org.htmlunit.util.UrlUtils;
4327

4428
/**
4529
* Wrapper for the HTML element "a".
@@ -53,9 +37,7 @@
5337
* @author Frank Danek
5438
* @author Lai Quang Duong
5539
*/
56-
public class HtmlAnchor extends HtmlElement {
57-
58-
private static final Log LOG = LogFactory.getLog(HtmlAnchor.class);
40+
public class HtmlAnchor extends HtmlElement implements HyperlinkElement {
5941

6042
/** The HTML tag represented by this element. */
6143
public static final String TAG_NAME = "a";
@@ -108,145 +90,7 @@ public <P extends Page> P click(final Event event,
10890
*/
10991
protected void doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey, final String hrefSuffix)
11092
throws IOException {
111-
final String href = (getHrefAttribute() + hrefSuffix).trim();
112-
if (LOG.isDebugEnabled()) {
113-
final String w = getPage().getEnclosingWindow().getName();
114-
LOG.debug("do click action in window '" + w + "', using href '" + href + "'");
115-
}
116-
if (ATTRIBUTE_NOT_DEFINED == getHrefAttribute()) {
117-
return;
118-
}
119-
final String downloadAttribute = getDownloadAttribute();
120-
HtmlPage page = (HtmlPage) getPage();
121-
if (StringUtils.startsWithIgnoreCase(href, JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
122-
final StringBuilder builder = new StringBuilder(href.length());
123-
builder.append(JavaScriptURLConnection.JAVASCRIPT_PREFIX);
124-
for (int i = JavaScriptURLConnection.JAVASCRIPT_PREFIX.length(); i < href.length(); i++) {
125-
final char ch = href.charAt(i);
126-
if (ch == '%' && i + 2 < href.length()) {
127-
final char ch1 = Character.toUpperCase(href.charAt(i + 1));
128-
final char ch2 = Character.toUpperCase(href.charAt(i + 2));
129-
if ((Character.isDigit(ch1) || ch1 >= 'A' && ch1 <= 'F')
130-
&& (Character.isDigit(ch2) || ch2 >= 'A' && ch2 <= 'F')) {
131-
builder.append((char) Integer.parseInt(href.substring(i + 1, i + 3), 16));
132-
i += 2;
133-
continue;
134-
}
135-
}
136-
builder.append(ch);
137-
}
138-
139-
final String target;
140-
if (shiftKey || ctrlKey || ATTRIBUTE_NOT_DEFINED != downloadAttribute) {
141-
target = WebClient.TARGET_BLANK;
142-
}
143-
else {
144-
target = page.getResolvedTarget(getTargetAttribute());
145-
}
146-
final WebWindow win = page.getWebClient().openTargetWindow(page.getEnclosingWindow(),
147-
target, WebClient.TARGET_SELF);
148-
Page enclosedPage = win.getEnclosedPage();
149-
if (enclosedPage == null) {
150-
win.getWebClient().getPage(win, WebRequest.newAboutBlankRequest());
151-
enclosedPage = win.getEnclosedPage();
152-
}
153-
if (enclosedPage != null && enclosedPage.isHtmlPage()) {
154-
page = (HtmlPage) enclosedPage;
155-
page.executeJavaScript(builder.toString(), "javascript url", getStartLineNumber());
156-
}
157-
return;
158-
}
159-
160-
final URL url = getTargetUrl(href, page);
161-
162-
final URL pageUrl = page.getUrl();
163-
164-
final WebClient webClient = page.getWebClient();
165-
final BrowserVersion browser = webClient.getBrowserVersion();
166-
if (ATTRIBUTE_NOT_DEFINED != getPingAttribute() && browser.hasFeature(ANCHOR_SEND_PING_REQUEST)) {
167-
final URL pingUrl = getTargetUrl(getPingAttribute(), page);
168-
final WebRequest pingRequest = new WebRequest(pingUrl, HttpMethod.POST);
169-
pingRequest.setAdditionalHeader(HttpHeader.ACCEPT_ENCODING, browser.getAcceptEncodingHeader());
170-
pingRequest.setAdditionalHeader(HttpHeader.PING_FROM, pageUrl.toExternalForm());
171-
pingRequest.setAdditionalHeader(HttpHeader.PING_TO, url.toExternalForm());
172-
pingRequest.setEncodingType(FormEncodingType.TEXT_PLAIN); // text/ping
173-
try {
174-
pingRequest.setAdditionalHeader(HttpHeader.ORIGIN,
175-
UrlUtils.getUrlWithProtocolAndAuthority(pageUrl).toExternalForm());
176-
}
177-
catch (final MalformedURLException e) {
178-
if (LOG.isInfoEnabled()) {
179-
LOG.info("Invalid origin url '" + pageUrl + "'");
180-
}
181-
}
182-
if (browser.hasFeature(FORM_SUBMISSION_HEADER_CACHE_CONTROL_MAX_AGE)) {
183-
pingRequest.setAdditionalHeader(HttpHeader.CACHE_CONTROL, "max-age=0");
184-
}
185-
pingRequest.setRequestBody("PING");
186-
187-
// Sec-Fetch-* support (https://www.w3.org/TR/fetch-metadata/):
188-
// hyperlink-auditing (ping) requests have no destination and are always no-cors
189-
pingRequest.setFetchDestination(WebRequest.FetchDestination.EMPTY);
190-
pingRequest.setFetchModeOverride(WebRequest.FetchMode.NO_CORS);
191-
pingRequest.setRequestingUrl(pageUrl);
192-
193-
try {
194-
webClient.loadWebResponse(pingRequest);
195-
}
196-
catch (final Exception e) {
197-
// ignore
198-
}
199-
}
200-
201-
final WebRequest webRequest = new WebRequest(url, browser.getHtmlAcceptHeader(),
202-
browser.getAcceptEncodingHeader());
203-
// use the page encoding even if this is a GET requests
204-
webRequest.setCharset(page.getCharset());
205-
206-
// Sec-Fetch-* support (https://www.w3.org/TR/fetch-metadata/):
207-
// this is a top-level navigation, initiated by the page containing the link.
208-
// The initiator must be set regardless of "noreferrer", since Sec-Fetch-Site
209-
// reflects the true relationship between initiator and target even when the
210-
// Referer header itself is suppressed.
211-
// TODO: userActivation is hardcoded to true here because doClickStateUpdate()
212-
// currently has no way to tell a real (WebDriver-simulated) click apart from a
213-
// script-triggered one (e.g. anchor.click()); real browsers only send
214-
// Sec-Fetch-User for the former. Once the click/event dispatch path exposes a
215-
// trusted/user-gesture flag, thread it through instead of hardcoding this.
216-
webRequest.markAsNavigation(page.getUrl(), true);
217-
218-
if (!relContainsNoreferrer()) {
219-
webRequest.setRefererHeader(page.getUrl());
220-
}
221-
222-
if (LOG.isDebugEnabled()) {
223-
LOG.debug(
224-
"Getting page for " + url.toExternalForm()
225-
+ ", derived from href '" + href
226-
+ "', using the originating URL "
227-
+ page.getUrl());
228-
}
229-
230-
final String target;
231-
if (shiftKey || ctrlKey
232-
|| (webClient.getAttachmentHandler() == null
233-
&& ATTRIBUTE_NOT_DEFINED != downloadAttribute)) {
234-
target = WebClient.TARGET_BLANK;
235-
}
236-
else {
237-
target = page.getResolvedTarget(getTargetAttribute());
238-
}
239-
page.getWebClient().download(page.getEnclosingWindow(), target, webRequest,
240-
true, (ATTRIBUTE_NOT_DEFINED == downloadAttribute) ? null : downloadAttribute, "Link click");
241-
}
242-
243-
private boolean relContainsNoreferrer() {
244-
String rel = getRelAttribute();
245-
if (rel != null) {
246-
rel = rel.toLowerCase(Locale.ROOT);
247-
return ArrayUtils.contains(StringUtils.splitAtBlank(rel), "noreferrer");
248-
}
249-
return false;
93+
HyperlinkElementSupport.doClickStateUpdate(this, shiftKey, ctrlKey, hrefSuffix);
25094
}
25195

25296
/**
@@ -258,12 +102,7 @@ private boolean relContainsNoreferrer() {
258102
* @throws MalformedURLException if an IO error occurs
259103
*/
260104
public static URL getTargetUrl(final String href, final HtmlPage page) throws MalformedURLException {
261-
URL url = page.getFullyQualifiedUrl(href);
262-
// fix for empty url
263-
if (StringUtils.isEmptyOrNull(href)) {
264-
url = UrlUtils.getUrlWithNewRef(url, null);
265-
}
266-
return url;
105+
return HyperlinkElementSupport.getTargetUrl(href, page);
267106
}
268107

269108
/**
@@ -315,6 +154,7 @@ public final String getNameAttribute() {
315154
*
316155
* @return the value of the attribute {@code href} or an empty string if that attribute isn't defined
317156
*/
157+
@Override
318158
public final String getHrefAttribute() {
319159
return getAttributeDirect("href").trim();
320160
}
@@ -337,6 +177,7 @@ public final String getHrefLangAttribute() {
337177
*
338178
* @return the value of the attribute {@code rel} or an empty string if that attribute isn't defined
339179
*/
180+
@Override
340181
public final String getRelAttribute() {
341182
return getAttributeDirect("rel");
342183
}
@@ -425,6 +266,7 @@ public final String getOnBlurAttribute() {
425266
*
426267
* @return the value of the attribute {@code target} or an empty string if that attribute isn't defined
427268
*/
269+
@Override
428270
public final String getTargetAttribute() {
429271
return getAttributeDirect("target");
430272
}
@@ -477,6 +319,7 @@ public boolean handles(final Event event) {
477319
*
478320
* @return the value of the attribute {@code ping}
479321
*/
322+
@Override
480323
public final String getPingAttribute() {
481324
return getAttributeDirect("ping");
482325
}
@@ -486,6 +329,7 @@ public final String getPingAttribute() {
486329
*
487330
* @return the value of the attribute {@code download}
488331
*/
332+
@Override
489333
public final String getDownloadAttribute() {
490334
return getAttributeDirect("download");
491335
}

src/main/java/org/htmlunit/html/HtmlArea.java

Lines changed: 14 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,16 @@
1414
*/
1515
package org.htmlunit.html;
1616

17-
import static org.htmlunit.BrowserVersionFeatures.ANCHOR_SEND_PING_REQUEST;
1817
import static org.htmlunit.BrowserVersionFeatures.CSS_DISPLAY_BLOCK;
1918

2019
import java.io.IOException;
21-
import java.net.MalformedURLException;
22-
import java.net.URL;
2320
import java.util.Arrays;
24-
import java.util.Locale;
2521
import java.util.Map;
2622

2723
import org.apache.commons.logging.Log;
2824
import org.apache.commons.logging.LogFactory;
29-
import org.htmlunit.BrowserVersion;
30-
import org.htmlunit.HttpHeader;
31-
import org.htmlunit.HttpMethod;
3225
import org.htmlunit.SgmlPage;
33-
import org.htmlunit.WebClient;
34-
import org.htmlunit.WebRequest;
35-
import org.htmlunit.WebWindow;
3626
import org.htmlunit.javascript.host.event.Event;
37-
import org.htmlunit.protocol.javascript.JavaScriptURLConnection;
38-
import org.htmlunit.util.ArrayUtils;
3927
import org.htmlunit.util.StringUtils;
4028
import org.htmlunit.util.geometry.Circle2D;
4129
import org.htmlunit.util.geometry.Polygon2D;
@@ -53,7 +41,7 @@
5341
* @author Frank Danek
5442
* @author Ronald Brill
5543
*/
56-
public class HtmlArea extends HtmlElement {
44+
public class HtmlArea extends HtmlElement implements HyperlinkElement {
5745
private static final Log LOG = LogFactory.getLog(HtmlArea.class);
5846

5947
/** The HTML tag represented by this element. */
@@ -80,60 +68,7 @@ public class HtmlArea extends HtmlElement {
8068
*/
8169
@Override
8270
protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
83-
final HtmlPage enclosingPage = (HtmlPage) getPage();
84-
final WebClient webClient = enclosingPage.getWebClient();
85-
86-
final String href = getHrefAttribute().trim();
87-
if (!href.isEmpty()) {
88-
final HtmlPage page = (HtmlPage) getPage();
89-
if (StringUtils.startsWithIgnoreCase(href, JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
90-
page.executeJavaScript(
91-
href, "javascript url", getStartLineNumber());
92-
return false;
93-
}
94-
final URL url;
95-
try {
96-
url = enclosingPage.getFullyQualifiedUrl(getHrefAttribute());
97-
}
98-
catch (final MalformedURLException e) {
99-
throw new IllegalStateException(
100-
"Not a valid url: " + getHrefAttribute(), e);
101-
}
102-
103-
final BrowserVersion browser = webClient.getBrowserVersion();
104-
if (ATTRIBUTE_NOT_DEFINED != getPingAttribute() && browser.hasFeature(ANCHOR_SEND_PING_REQUEST)) {
105-
final URL pingUrl = enclosingPage.getFullyQualifiedUrl(getPingAttribute());
106-
final WebRequest pingRequest = new WebRequest(pingUrl, HttpMethod.POST);
107-
pingRequest.setAdditionalHeader(HttpHeader.PING_FROM, page.getUrl().toExternalForm());
108-
pingRequest.setAdditionalHeader(HttpHeader.PING_TO, url.toExternalForm());
109-
pingRequest.setRequestBody("PING");
110-
111-
// Sec-Fetch-* support (https://www.w3.org/TR/fetch-metadata/):
112-
// hyperlink-auditing (ping) requests have no destination and are always no-cors
113-
pingRequest.setFetchDestination(WebRequest.FetchDestination.EMPTY);
114-
pingRequest.setFetchModeOverride(WebRequest.FetchMode.NO_CORS);
115-
pingRequest.setRequestingUrl(page.getUrl());
116-
117-
webClient.loadWebResponse(pingRequest);
118-
}
119-
120-
// rel="noreferrer" suppresses the Referer header only; Sec-Fetch-Site
121-
// (set below via markAsNavigation) still reflects the true initiator
122-
// relationship regardless.
123-
final WebRequest request = new WebRequest(url, page.getCharset(),
124-
relContainsNoreferrer() ? null : page.getUrl());
125-
126-
// Sec-Fetch-* support (https://www.w3.org/TR/fetch-metadata/):
127-
// this is a top-level navigation, initiated by the page containing the
128-
// image map. TODO: userActivation is hardcoded to true here for the same
129-
// reason as HtmlAnchor#doClickStateUpdate() - this method has no way to
130-
// tell a real click apart from a script-triggered one (e.g. area.click()).
131-
request.markAsNavigation(page.getUrl(), true);
132-
133-
final WebWindow webWindow = enclosingPage.getEnclosingWindow();
134-
final String target = enclosingPage.getResolvedTarget(getTargetAttribute());
135-
webClient.getPage(webClient.openTargetWindow(webWindow, target, WebClient.TARGET_SELF), request);
136-
}
71+
HyperlinkElementSupport.doClickStateUpdate(this, shiftKey, ctrlKey, "");
13772
return false;
13873
}
13974

@@ -166,6 +101,7 @@ public final String getCoordsAttribute() {
166101
*
167102
* @return the value of the attribute {@code href} or an empty string if that attribute isn't defined
168103
*/
104+
@Override
169105
public final String getHrefAttribute() {
170106
return getAttributeDirect("href");
171107
}
@@ -243,6 +179,7 @@ public final String getOnBlurAttribute() {
243179
*
244180
* @return the value of the attribute {@code target} or an empty string if that attribute isn't defined
245181
*/
182+
@Override
246183
public final String getTargetAttribute() {
247184
return getAttributeDirect("target");
248185
}
@@ -254,6 +191,7 @@ public final String getTargetAttribute() {
254191
*
255192
* @return the value of the attribute {@code rel} or an empty string if that attribute isn't defined
256193
*/
194+
@Override
257195
public final String getRelAttribute() {
258196
return getAttributeDirect("rel");
259197
}
@@ -263,17 +201,19 @@ public final String getRelAttribute() {
263201
*
264202
* @return the value of the attribute {@code ping}
265203
*/
204+
@Override
266205
public final String getPingAttribute() {
267206
return getAttributeDirect("ping");
268207
}
269208

270-
private boolean relContainsNoreferrer() {
271-
String rel = getRelAttribute();
272-
if (rel != null) {
273-
rel = rel.toLowerCase(Locale.ROOT);
274-
return ArrayUtils.contains(StringUtils.splitAtBlank(rel), "noreferrer");
275-
}
276-
return false;
209+
/**
210+
* Returns the value of the attribute {@code download}.
211+
*
212+
* @return the value of the attribute {@code download}
213+
*/
214+
@Override
215+
public final String getDownloadAttribute() {
216+
return getAttributeDirect("download");
277217
}
278218

279219
/**

0 commit comments

Comments
 (0)