Merge branch 'pr/3371'
This commit is contained in:
commit
aa3970c83e
@ -54,3 +54,5 @@ rules:
|
||||
function-paren-newline: "off"
|
||||
multiline-comment-style: "off"
|
||||
no-bitwise: "off"
|
||||
no-ternary: "off"
|
||||
max-lines: "off"
|
||||
|
@ -40,7 +40,54 @@ window._qutebrowser.webelem = (function() {
|
||||
const funcs = {};
|
||||
const elements = [];
|
||||
|
||||
function serialize_elem(elem) {
|
||||
function get_frame_offset(frame) {
|
||||
if (frame === null) {
|
||||
// Dummy object with zero offset
|
||||
return {
|
||||
"top": 0,
|
||||
"right": 0,
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
};
|
||||
}
|
||||
return frame.frameElement.getBoundingClientRect();
|
||||
}
|
||||
|
||||
// Add an offset rect to a base rect, for use with frames
|
||||
function add_offset_rect(base, offset) {
|
||||
return {
|
||||
"top": base.top + offset.top,
|
||||
"left": base.left + offset.left,
|
||||
"bottom": base.bottom + offset.top,
|
||||
"right": base.right + offset.left,
|
||||
"height": base.height,
|
||||
"width": base.width,
|
||||
};
|
||||
}
|
||||
|
||||
function get_caret_position(elem, frame) {
|
||||
// With older Chromium versions (and QtWebKit), InvalidStateError will
|
||||
// be thrown if elem doesn't have selectionStart.
|
||||
// With newer Chromium versions (>= Qt 5.10), we get null.
|
||||
try {
|
||||
return elem.selectionStart;
|
||||
} catch (err) {
|
||||
if (err instanceof (frame
|
||||
? frame.DOMException
|
||||
: DOMException) &&
|
||||
err.name === "InvalidStateError") {
|
||||
// nothing to do, caret_position is already null
|
||||
} else {
|
||||
// not the droid we're looking for
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function serialize_elem(elem, frame = null) {
|
||||
if (!elem) {
|
||||
return null;
|
||||
}
|
||||
@ -48,21 +95,7 @@ window._qutebrowser.webelem = (function() {
|
||||
const id = elements.length;
|
||||
elements[id] = elem;
|
||||
|
||||
// With older Chromium versions (and QtWebKit), InvalidStateError will
|
||||
// be thrown if elem doesn't have selectionStart.
|
||||
// With newer Chromium versions (>= Qt 5.10), we get null.
|
||||
let caret_position = null;
|
||||
try {
|
||||
caret_position = elem.selectionStart;
|
||||
} catch (err) {
|
||||
if (err instanceof DOMException &&
|
||||
err.name === "InvalidStateError") {
|
||||
// nothing to do, caret_position is already null
|
||||
} else {
|
||||
// not the droid we're looking for
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
const caret_position = get_caret_position(elem, frame);
|
||||
|
||||
const out = {
|
||||
"id": id,
|
||||
@ -115,16 +148,13 @@ window._qutebrowser.webelem = (function() {
|
||||
out.attributes = attributes;
|
||||
|
||||
const client_rects = elem.getClientRects();
|
||||
const frame_offset_rect = get_frame_offset(frame);
|
||||
|
||||
for (let k = 0; k < client_rects.length; ++k) {
|
||||
const rect = client_rects[k];
|
||||
out.rects.push({
|
||||
"top": rect.top,
|
||||
"right": rect.right,
|
||||
"bottom": rect.bottom,
|
||||
"left": rect.left,
|
||||
"height": rect.height,
|
||||
"width": rect.width,
|
||||
});
|
||||
out.rects.push(
|
||||
add_offset_rect(rect, frame_offset_rect)
|
||||
);
|
||||
}
|
||||
|
||||
// console.log(JSON.stringify(out));
|
||||
@ -132,9 +162,7 @@ window._qutebrowser.webelem = (function() {
|
||||
return out;
|
||||
}
|
||||
|
||||
function is_visible(elem) {
|
||||
// FIXME:qtwebengine Handle frames and iframes
|
||||
|
||||
function is_visible(elem, frame = null) {
|
||||
// Adopted from vimperator:
|
||||
// https://github.com/vimperator/vimperator-labs/blob/vimperator-3.14.0/common/content/hints.js#L259-L285
|
||||
// FIXME:qtwebengine we might need something more sophisticated like
|
||||
@ -142,7 +170,8 @@ window._qutebrowser.webelem = (function() {
|
||||
// https://github.com/1995eaton/chromium-vim/blob/1.2.85/content_scripts/dom.js#L74-L134
|
||||
|
||||
const win = elem.ownerDocument.defaultView;
|
||||
let rect = elem.getBoundingClientRect();
|
||||
const offset_rect = get_frame_offset(frame);
|
||||
let rect = add_offset_rect(elem.getBoundingClientRect(), offset_rect);
|
||||
|
||||
if (!rect ||
|
||||
rect.top > window.innerHeight ||
|
||||
@ -174,8 +203,20 @@ window._qutebrowser.webelem = (function() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if the iframe is accessible without
|
||||
// cross domain errors, else false.
|
||||
function iframe_same_domain(frame) {
|
||||
try {
|
||||
frame.document; // eslint-disable-line no-unused-expressions
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
funcs.find_css = (selector, only_visible) => {
|
||||
const elems = document.querySelectorAll(selector);
|
||||
const subelem_frames = window.frames;
|
||||
const out = [];
|
||||
|
||||
for (let i = 0; i < elems.length; ++i) {
|
||||
@ -184,14 +225,70 @@ window._qutebrowser.webelem = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into frames and add them
|
||||
for (let i = 0; i < subelem_frames.length; i++) {
|
||||
if (iframe_same_domain(subelem_frames[i])) {
|
||||
const frame = subelem_frames[i];
|
||||
const subelems = frame.document.
|
||||
querySelectorAll(selector);
|
||||
for (let elem_num = 0; elem_num < subelems.length; ++elem_num) {
|
||||
if (!only_visible ||
|
||||
is_visible(subelems[elem_num], frame)) {
|
||||
out.push(serialize_elem(subelems[elem_num], frame));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
// Runs a function in a frame until the result is not null, then return
|
||||
function run_frames(func) {
|
||||
for (let i = 0; i < window.frames.length; ++i) {
|
||||
const frame = window.frames[i];
|
||||
if (iframe_same_domain(frame)) {
|
||||
const result = func(frame);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
funcs.find_id = (id) => {
|
||||
const elem = document.getElementById(id);
|
||||
return serialize_elem(elem);
|
||||
if (elem) {
|
||||
return serialize_elem(elem);
|
||||
}
|
||||
|
||||
const serialized_elem = run_frames((frame) => {
|
||||
const element = frame.window.document.getElementById(id);
|
||||
return serialize_elem(element, frame);
|
||||
});
|
||||
|
||||
if (serialized_elem) {
|
||||
return serialized_elem;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Check if elem is an iframe, and if so, return the result of func on it.
|
||||
// If no iframes match, return null
|
||||
function call_if_frame(elem, func) {
|
||||
// Check if elem is a frame, and if so, call func on the window
|
||||
if ("contentWindow" in elem) {
|
||||
const frame = elem.contentWindow;
|
||||
if (iframe_same_domain(frame) &&
|
||||
"frameElement" in elem.contentWindow) {
|
||||
return func(frame);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
funcs.find_focused = () => {
|
||||
const elem = document.activeElement;
|
||||
|
||||
@ -201,26 +298,52 @@ window._qutebrowser.webelem = (function() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if we got an iframe, and if so, recurse inside of it
|
||||
const frame_elem = call_if_frame(elem,
|
||||
(frame) => serialize_elem(frame.document.activeElement, frame));
|
||||
|
||||
if (frame_elem !== null) {
|
||||
return frame_elem;
|
||||
}
|
||||
return serialize_elem(elem);
|
||||
};
|
||||
|
||||
funcs.find_at_pos = (x, y) => {
|
||||
// FIXME:qtwebengine
|
||||
// If the element at the specified point belongs to another document
|
||||
// (for example, an iframe's subdocument), the subdocument's parent
|
||||
// element is returned (the iframe itself).
|
||||
|
||||
const elem = document.elementFromPoint(x, y);
|
||||
|
||||
|
||||
// Check if we got an iframe, and if so, recurse inside of it
|
||||
const frame_elem = call_if_frame(elem,
|
||||
(frame) => {
|
||||
// Subtract offsets due to being in an iframe
|
||||
const frame_offset_rect =
|
||||
frame.frameElement.getBoundingClientRect();
|
||||
return serialize_elem(frame.document.
|
||||
elementFromPoint(x - frame_offset_rect.left,
|
||||
y - frame_offset_rect.top), frame);
|
||||
});
|
||||
|
||||
if (frame_elem !== null) {
|
||||
return frame_elem;
|
||||
}
|
||||
return serialize_elem(elem);
|
||||
};
|
||||
|
||||
// Function for returning a selection to python (so we can click it)
|
||||
funcs.find_selected_link = () => {
|
||||
const elem = window.getSelection().anchorNode;
|
||||
if (!elem) {
|
||||
return null;
|
||||
if (elem) {
|
||||
return serialize_elem(elem.parentNode);
|
||||
}
|
||||
return serialize_elem(elem.parentNode);
|
||||
|
||||
const serialized_frame_elem = run_frames((frame) => {
|
||||
const node = frame.window.getSelection().anchorNode;
|
||||
if (node) {
|
||||
return serialize_elem(node.parentNode, frame);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
return serialized_frame_elem;
|
||||
};
|
||||
|
||||
funcs.set_value = (id, value) => {
|
||||
|
@ -1,9 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<!--
|
||||
target: hello.txt
|
||||
qtwebengine_todo: Doesn't seem to work?
|
||||
-->
|
||||
<!-- target: hello.txt -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
|
21
tests/end2end/data/hints/html/wrapped_button.html
Normal file
21
tests/end2end/data/hints/html/wrapped_button.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<!-- target: hello.txt -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Button link wrapped across multiple lines</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="width: 20em;">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis <button id="myButton" class="float-left submit-button" >nostrud exercitation</button> ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
document.getElementById("myButton").onclick = function () {
|
||||
location.href = "/data/hello.txt";
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
11
tests/end2end/data/hints/iframe_button.html
Normal file
11
tests/end2end/data/hints/iframe_button.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hinting a button inside an iframe</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe style="margin: 50px;" src="/data/hints/html/wrapped_button.html"></iframe>
|
||||
</body>
|
||||
</html>
|
11
tests/end2end/data/hints/iframe_input.html
Normal file
11
tests/end2end/data/hints/iframe_input.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hinting an input field inside an iframe</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe style="margin: 50px;" src="/data/hints/input.html"></iframe>
|
||||
</body>
|
||||
</html>
|
@ -9,6 +9,7 @@
|
||||
<iframe id="my-iframe"></iframe>
|
||||
<p>
|
||||
A <a href="/data/hello.txt" target="my-iframe">link</a> to be opened in the iframe above.
|
||||
A <a href="/data/hello2.txt" target="my-iframe">second link</a> for the iframe.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
11
tests/end2end/data/iframe_search.html
Normal file
11
tests/end2end/data/iframe_search.html
Normal file
@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Searching inside an iframe</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe style="margin: 50px;" src="/data/search.html"></iframe>
|
||||
</body>
|
||||
</html>
|
@ -204,23 +204,32 @@ Feature: Using hints
|
||||
Then the javascript message "contents: existingnew" should be logged
|
||||
|
||||
### iframes
|
||||
|
||||
@qtwebengine_todo: Hinting in iframes is not implemented yet
|
||||
Scenario: Using :follow-hint inside an iframe
|
||||
When I open data/hints/iframe.html
|
||||
And I hint with args "links normal" and follow a
|
||||
Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, *" should be logged
|
||||
|
||||
### FIXME currently skipped, see https://github.com/qutebrowser/qutebrowser/issues/1525
|
||||
@xfail_norun
|
||||
Scenario: Using :follow-hint inside an iframe button
|
||||
When I open data/hints/iframe_button.html
|
||||
And I hint with args "all normal" and follow s
|
||||
Then "navigation request: url http://localhost:*/data/hello.txt, *" should be logged
|
||||
|
||||
Scenario: Hinting inputs in an iframe without type
|
||||
When I open data/hints/iframe_input.html
|
||||
And I hint with args "inputs" and follow a
|
||||
And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log
|
||||
And I run :leave-mode
|
||||
# The actual check is already done above
|
||||
Then no crash should happen
|
||||
|
||||
@flaky # FIXME https://github.com/qutebrowser/qutebrowser/issues/1525
|
||||
Scenario: Using :follow-hint inside a scrolled iframe
|
||||
When I open data/hints/iframe_scroll.html
|
||||
And I hint with args "all normal" and follow a
|
||||
And I run :scroll bottom
|
||||
And I hint wht args "links normal" and follow a
|
||||
And I hint with args "links normal" and follow a
|
||||
Then "navigation request: url http://localhost:*/data/hello2.txt, type NavigationTypeLinkClicked, *" should be logged
|
||||
|
||||
@qtwebengine_skip: Opens in new tab due to Chromium bug
|
||||
Scenario: Opening a link inside a specific iframe
|
||||
When I open data/hints/iframe_target.html
|
||||
And I hint with args "links normal" and follow a
|
||||
@ -229,11 +238,11 @@ Feature: Using hints
|
||||
Scenario: Opening a link with specific target frame in a new tab
|
||||
When I open data/hints/iframe_target.html
|
||||
And I run :tab-only
|
||||
And I hint with args "links tab" and follow a
|
||||
And I wait until data/hello.txt is loaded
|
||||
And I hint with args "links tab" and follow s
|
||||
And I wait until data/hello2.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- data/hints/iframe_target.html
|
||||
- data/hello.txt (active)
|
||||
- data/hello2.txt (active)
|
||||
|
||||
Scenario: Clicking on iframe with :hint all current
|
||||
When I open data/hints/iframe.html
|
||||
|
@ -242,3 +242,24 @@ Feature: Searching on a page
|
||||
Then the following tabs should be open:
|
||||
- data/search.html
|
||||
- data/hello.txt (active)
|
||||
|
||||
@qtwebkit_skip: Not supported in qtwebkit
|
||||
Scenario: Follow a searched link in an iframe
|
||||
When I open data/iframe_search.html
|
||||
And I run :tab-only
|
||||
And I run :search follow
|
||||
And I wait for "search found follow" in the log
|
||||
And I run :follow-selected
|
||||
Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, is_main_frame False" should be logged
|
||||
|
||||
@qtwebkit_skip: Not supported in qtwebkit
|
||||
Scenario: Follow a tabbed searched link in an iframe
|
||||
When I open data/iframe_search.html
|
||||
And I run :tab-only
|
||||
And I run :search follow
|
||||
And I wait for "search found follow" in the log
|
||||
And I run :follow-selected -t
|
||||
And I wait until data/hello.txt is loaded
|
||||
Then the following tabs should be open:
|
||||
- data/iframe_search.html
|
||||
- data/hello.txt (active)
|
||||
|
Loading…
Reference in New Issue
Block a user