|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing an object for testing certain aspects of a web page. |
|
8 """ |
|
9 |
|
10 # |
|
11 # This code was ported from QupZilla. |
|
12 # Copyright (C) David Rosca <nowrep@gmail.com> |
|
13 # |
|
14 |
|
15 from PyQt5.QtCore import QPoint, QRect, QUrl |
|
16 |
|
17 |
|
18 class WebHitTestResult: |
|
19 """ |
|
20 Class implementing an object for testing certain aspects of a web page. |
|
21 """ |
|
22 def __init__(self, page, pos): |
|
23 """ |
|
24 Constructor |
|
25 |
|
26 @param page reference to the web page |
|
27 @type WebBrowserPage |
|
28 @param pos position to be tested |
|
29 @type QPoint |
|
30 """ |
|
31 self.__isNull = True |
|
32 self.__isContentEditable = False |
|
33 self.__isContentSelected = False |
|
34 self.__isMediaPaused = False |
|
35 self.__isMediaMuted = False |
|
36 self.__pos = QPoint(pos) |
|
37 self.__baseUrl = QUrl() |
|
38 self.__alternateText = "" |
|
39 self.__boundingRect = QRect() |
|
40 self.__imageUrl = QUrl() |
|
41 self.__linkTitle = "" |
|
42 self.__linkUrl = QUrl() |
|
43 self.__mediaUrl = QUrl() |
|
44 self.__tagName = "" |
|
45 self.__viewportPos = page.mapToViewport(pos) |
|
46 |
|
47 script = """ |
|
48 (function() {{ |
|
49 var e = document.elementFromPoint({0}, {1}); |
|
50 if (!e) |
|
51 return; |
|
52 function isMediaElement(e) {{ |
|
53 return e.tagName.toLowerCase() == 'audio' || |
|
54 e.tagName.toLowerCase() == 'video'; |
|
55 }} |
|
56 function isEditableElement(e) {{ |
|
57 if (e.isContentEditable) |
|
58 return true; |
|
59 if (e.tagName.toLowerCase() == 'input' || |
|
60 e.tagName.toLowerCase() == 'textarea') |
|
61 return e.getAttribute('readonly') != 'readonly'; |
|
62 return false; |
|
63 }} |
|
64 function isSelected(e) {{ |
|
65 var selection = window.getSelection(); |
|
66 if (selection.type != 'Range') |
|
67 return false; |
|
68 return window.getSelection().containsNode(e, true); |
|
69 }} |
|
70 function attributeStr(e, a) {{ |
|
71 return e.getAttribute(a) || ''; |
|
72 }} |
|
73 var res = {{ |
|
74 baseUrl: document.baseURI, |
|
75 alternateText: e.getAttribute('alt'), |
|
76 boundingRect: '', |
|
77 imageUrl: '', |
|
78 contentEditable: isEditableElement(e), |
|
79 contentSelected: isSelected(e), |
|
80 linkTitle: '', |
|
81 linkUrl: '', |
|
82 mediaUrl: '', |
|
83 mediaPaused: false, |
|
84 mediaMuted: false, |
|
85 tagName: e.tagName.toLowerCase() |
|
86 }}; |
|
87 var r = e.getBoundingClientRect(); |
|
88 res.boundingRect = [r.top, r.left, r.width, r.height]; |
|
89 if (e.tagName.toLowerCase() == 'img') |
|
90 res.imageUrl = attributeStr(e, 'src').trim(); |
|
91 if (e.tagName.toLowerCase() == 'a') {{ |
|
92 res.linkTitle = e.text; |
|
93 res.linkUrl = attributeStr(e, 'href').trim(); |
|
94 }} |
|
95 while (e) {{ |
|
96 if (res.linkTitle == '' && e.tagName.toLowerCase() == 'a') |
|
97 res.linkTitle = e.text; |
|
98 if (res.linkUrl == '' && e.tagName.toLowerCase() == 'a') |
|
99 res.linkUrl = attributeStr(e, 'href').trim(); |
|
100 if (res.mediaUrl == '' && isMediaElement(e)) {{ |
|
101 res.mediaUrl = e.currentSrc; |
|
102 res.mediaPaused = e.paused; |
|
103 res.mediaMuted = e.muted; |
|
104 }} |
|
105 e = e.parentElement; |
|
106 }} |
|
107 return res; |
|
108 }})() |
|
109 """.format(self.__viewportPos.x(), self.__viewportPos.y()) |
|
110 self.__populate(page.url(), page.execJavaScript(script)) |
|
111 |
|
112 def updateWithContextMenuData(self, data): |
|
113 """ |
|
114 Public method to update the hit test data with data from the context |
|
115 menu event. |
|
116 |
|
117 @param data context menu data |
|
118 @type QWebEngineContextMenuData |
|
119 """ |
|
120 from PyQt5.QtWebEngineWidgets import QWebEngineContextMenuData |
|
121 if not data.isValid() or data.position() != self.__pos: |
|
122 return |
|
123 |
|
124 self.__linkTitle = data.linkText() |
|
125 self.__linkUrl = data.linkUrl() |
|
126 self.__isContentEditable = data.isContentEditable() |
|
127 self.__isContentSelected = bool(data.selectedText()) |
|
128 |
|
129 if ( |
|
130 data.mediaType() == |
|
131 QWebEngineContextMenuData.MediaType.MediaTypeImage |
|
132 ): |
|
133 self.__imageUrl = data.mediaUrl() |
|
134 elif data.mediaType() in [ |
|
135 QWebEngineContextMenuData.MediaType.MediaTypeAudio, |
|
136 QWebEngineContextMenuData.MediaType.MediaTypeVideo |
|
137 ]: |
|
138 self.__mediaUrl = data.mediaUrl() |
|
139 |
|
140 def baseUrl(self): |
|
141 """ |
|
142 Public method to get the base URL of the page. |
|
143 |
|
144 @return base URL |
|
145 @rtype QUrl |
|
146 """ |
|
147 return self.__baseUrl |
|
148 |
|
149 def alternateText(self): |
|
150 """ |
|
151 Public method to get the alternate text. |
|
152 |
|
153 @return alternate text |
|
154 @rtype str |
|
155 """ |
|
156 return self.__alternateText |
|
157 |
|
158 def boundingRect(self): |
|
159 """ |
|
160 Public method to get the bounding rectangle. |
|
161 |
|
162 @return bounding rectangle |
|
163 @rtype QRect |
|
164 """ |
|
165 return QRect(self.__boundingRect) |
|
166 |
|
167 def imageUrl(self): |
|
168 """ |
|
169 Public method to get the URL of an image. |
|
170 |
|
171 @return image URL |
|
172 @rtype QUrl |
|
173 """ |
|
174 return self.__imageUrl |
|
175 |
|
176 def isContentEditable(self): |
|
177 """ |
|
178 Public method to check for editable content. |
|
179 |
|
180 @return flag indicating editable content |
|
181 @rtype bool |
|
182 """ |
|
183 return self.__isContentEditable |
|
184 |
|
185 def isContentSelected(self): |
|
186 """ |
|
187 Public method to check for selected content. |
|
188 |
|
189 @return flag indicating selected content |
|
190 @rtype bool |
|
191 """ |
|
192 return self.__isContentSelected |
|
193 |
|
194 def isNull(self): |
|
195 """ |
|
196 Public method to test, if the hit test is empty. |
|
197 |
|
198 @return flag indicating an empty object |
|
199 @rtype bool |
|
200 """ |
|
201 return self.__isNull |
|
202 |
|
203 def linkTitle(self): |
|
204 """ |
|
205 Public method to get the title for a link element. |
|
206 |
|
207 @return title for a link element |
|
208 @rtype str |
|
209 """ |
|
210 return self.__linkTitle |
|
211 |
|
212 def linkUrl(self): |
|
213 """ |
|
214 Public method to get the URL for a link element. |
|
215 |
|
216 @return URL for a link element |
|
217 @rtype QUrl |
|
218 """ |
|
219 return self.__linkUrl |
|
220 |
|
221 def mediaUrl(self): |
|
222 """ |
|
223 Public method to get the URL for a media element. |
|
224 |
|
225 @return URL for a media element |
|
226 @rtype QUrl |
|
227 """ |
|
228 return self.__mediaUrl |
|
229 |
|
230 def mediaPaused(self): |
|
231 """ |
|
232 Public method to check, if a media element is paused. |
|
233 |
|
234 @return flag indicating a paused media element |
|
235 @rtype bool |
|
236 """ |
|
237 return self.__isMediaPaused |
|
238 |
|
239 def mediaMuted(self): |
|
240 """ |
|
241 Public method to check, if a media element is muted. |
|
242 |
|
243 @return flag indicating a muted media element |
|
244 @rtype bool |
|
245 """ |
|
246 return self.__isMediaMuted |
|
247 |
|
248 def pos(self): |
|
249 """ |
|
250 Public method to get the position of the hit test. |
|
251 |
|
252 @return position of hit test |
|
253 @rtype QPoint |
|
254 """ |
|
255 return QPoint(self.__pos) |
|
256 |
|
257 def viewportPos(self): |
|
258 """ |
|
259 Public method to get the viewport position. |
|
260 |
|
261 @return viewport position |
|
262 @rtype QPoint |
|
263 """ |
|
264 return QPoint(self.__viewportPos) |
|
265 |
|
266 def tagName(self): |
|
267 """ |
|
268 Public method to get the name of the tested tag. |
|
269 |
|
270 @return name of the tested tag |
|
271 @rtype str |
|
272 """ |
|
273 return self.__tagName |
|
274 |
|
275 def __populate(self, url, res): |
|
276 """ |
|
277 Private method to populate the object. |
|
278 |
|
279 @param url URL of the tested page |
|
280 @type QUrl |
|
281 @param res dictionary with result data from JavaScript |
|
282 @type dict |
|
283 """ |
|
284 if not res: |
|
285 return |
|
286 |
|
287 self.__baseUrl = QUrl(res["baseUrl"]) |
|
288 self.__alternateText = res["alternateText"] |
|
289 self.__imageUrl = QUrl(res["imageUrl"]) |
|
290 self.__isContentEditable = res["contentEditable"] |
|
291 self.__isContentSelected = res["contentSelected"] |
|
292 self.__linkTitle = res["linkTitle"] |
|
293 self.__linkUrl = QUrl(res["linkUrl"]) |
|
294 self.__mediaUrl = QUrl(res["mediaUrl"]) |
|
295 self.__isMediaPaused = res["mediaPaused"] |
|
296 self.__isMediaMuted = res["mediaMuted"] |
|
297 self.__tagName = res["tagName"] |
|
298 |
|
299 rect = res["boundingRect"] |
|
300 if len(rect) == 4: |
|
301 self.__boundingRect = QRect(int(rect[0]), int(rect[1]), |
|
302 int(rect[2]), int(rect[3])) |
|
303 |
|
304 if not self.__imageUrl.isEmpty(): |
|
305 self.__imageUrl = url.resolved(self.__imageUrl) |
|
306 if not self.__linkUrl.isEmpty(): |
|
307 self.__linkUrl = self.__baseUrl.resolved(self.__linkUrl) |
|
308 if not self.__mediaUrl.isEmpty(): |
|
309 self.__mediaUrl = url.resolved(self.__mediaUrl) |