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