Merge branch 'pr/3371'
This commit is contained in:
commit
aa3970c83e
@ -54,3 +54,5 @@ rules:
|
|||||||
function-paren-newline: "off"
|
function-paren-newline: "off"
|
||||||
multiline-comment-style: "off"
|
multiline-comment-style: "off"
|
||||||
no-bitwise: "off"
|
no-bitwise: "off"
|
||||||
|
no-ternary: "off"
|
||||||
|
max-lines: "off"
|
||||||
|
@ -40,7 +40,54 @@ window._qutebrowser.webelem = (function() {
|
|||||||
const funcs = {};
|
const funcs = {};
|
||||||
const elements = [];
|
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) {
|
if (!elem) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -48,21 +95,7 @@ window._qutebrowser.webelem = (function() {
|
|||||||
const id = elements.length;
|
const id = elements.length;
|
||||||
elements[id] = elem;
|
elements[id] = elem;
|
||||||
|
|
||||||
// With older Chromium versions (and QtWebKit), InvalidStateError will
|
const caret_position = get_caret_position(elem, frame);
|
||||||
// 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 out = {
|
const out = {
|
||||||
"id": id,
|
"id": id,
|
||||||
@ -115,16 +148,13 @@ window._qutebrowser.webelem = (function() {
|
|||||||
out.attributes = attributes;
|
out.attributes = attributes;
|
||||||
|
|
||||||
const client_rects = elem.getClientRects();
|
const client_rects = elem.getClientRects();
|
||||||
|
const frame_offset_rect = get_frame_offset(frame);
|
||||||
|
|
||||||
for (let k = 0; k < client_rects.length; ++k) {
|
for (let k = 0; k < client_rects.length; ++k) {
|
||||||
const rect = client_rects[k];
|
const rect = client_rects[k];
|
||||||
out.rects.push({
|
out.rects.push(
|
||||||
"top": rect.top,
|
add_offset_rect(rect, frame_offset_rect)
|
||||||
"right": rect.right,
|
);
|
||||||
"bottom": rect.bottom,
|
|
||||||
"left": rect.left,
|
|
||||||
"height": rect.height,
|
|
||||||
"width": rect.width,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log(JSON.stringify(out));
|
// console.log(JSON.stringify(out));
|
||||||
@ -132,9 +162,7 @@ window._qutebrowser.webelem = (function() {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_visible(elem) {
|
function is_visible(elem, frame = null) {
|
||||||
// FIXME:qtwebengine Handle frames and iframes
|
|
||||||
|
|
||||||
// Adopted from vimperator:
|
// Adopted from vimperator:
|
||||||
// https://github.com/vimperator/vimperator-labs/blob/vimperator-3.14.0/common/content/hints.js#L259-L285
|
// 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
|
// 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
|
// https://github.com/1995eaton/chromium-vim/blob/1.2.85/content_scripts/dom.js#L74-L134
|
||||||
|
|
||||||
const win = elem.ownerDocument.defaultView;
|
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 ||
|
if (!rect ||
|
||||||
rect.top > window.innerHeight ||
|
rect.top > window.innerHeight ||
|
||||||
@ -174,8 +203,20 @@ window._qutebrowser.webelem = (function() {
|
|||||||
return true;
|
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) => {
|
funcs.find_css = (selector, only_visible) => {
|
||||||
const elems = document.querySelectorAll(selector);
|
const elems = document.querySelectorAll(selector);
|
||||||
|
const subelem_frames = window.frames;
|
||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
for (let i = 0; i < elems.length; ++i) {
|
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;
|
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) => {
|
funcs.find_id = (id) => {
|
||||||
const elem = document.getElementById(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 = () => {
|
funcs.find_focused = () => {
|
||||||
const elem = document.activeElement;
|
const elem = document.activeElement;
|
||||||
|
|
||||||
@ -201,26 +298,52 @@ window._qutebrowser.webelem = (function() {
|
|||||||
return null;
|
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);
|
return serialize_elem(elem);
|
||||||
};
|
};
|
||||||
|
|
||||||
funcs.find_at_pos = (x, y) => {
|
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);
|
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);
|
return serialize_elem(elem);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function for returning a selection to python (so we can click it)
|
// Function for returning a selection to python (so we can click it)
|
||||||
funcs.find_selected_link = () => {
|
funcs.find_selected_link = () => {
|
||||||
const elem = window.getSelection().anchorNode;
|
const elem = window.getSelection().anchorNode;
|
||||||
if (!elem) {
|
if (elem) {
|
||||||
return null;
|
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) => {
|
funcs.set_value = (id, value) => {
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<!--
|
<!-- target: hello.txt -->
|
||||||
target: hello.txt
|
|
||||||
qtwebengine_todo: Doesn't seem to work?
|
|
||||||
-->
|
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<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>
|
<iframe id="my-iframe"></iframe>
|
||||||
<p>
|
<p>
|
||||||
A <a href="/data/hello.txt" target="my-iframe">link</a> to be opened in the iframe above.
|
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>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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
|
Then the javascript message "contents: existingnew" should be logged
|
||||||
|
|
||||||
### iframes
|
### iframes
|
||||||
|
|
||||||
@qtwebengine_todo: Hinting in iframes is not implemented yet
|
|
||||||
Scenario: Using :follow-hint inside an iframe
|
Scenario: Using :follow-hint inside an iframe
|
||||||
When I open data/hints/iframe.html
|
When I open data/hints/iframe.html
|
||||||
And I hint with args "links normal" and follow a
|
And I hint with args "links normal" and follow a
|
||||||
Then "navigation request: url http://localhost:*/data/hello.txt, type NavigationTypeLinkClicked, *" should be logged
|
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
|
Scenario: Using :follow-hint inside an iframe button
|
||||||
@xfail_norun
|
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
|
Scenario: Using :follow-hint inside a scrolled iframe
|
||||||
When I open data/hints/iframe_scroll.html
|
When I open data/hints/iframe_scroll.html
|
||||||
And I hint with args "all normal" and follow a
|
And I hint with args "all normal" and follow a
|
||||||
And I run :scroll bottom
|
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
|
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
|
Scenario: Opening a link inside a specific iframe
|
||||||
When I open data/hints/iframe_target.html
|
When I open data/hints/iframe_target.html
|
||||||
And I hint with args "links normal" and follow a
|
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
|
Scenario: Opening a link with specific target frame in a new tab
|
||||||
When I open data/hints/iframe_target.html
|
When I open data/hints/iframe_target.html
|
||||||
And I run :tab-only
|
And I run :tab-only
|
||||||
And I hint with args "links tab" and follow a
|
And I hint with args "links tab" and follow s
|
||||||
And I wait until data/hello.txt is loaded
|
And I wait until data/hello2.txt is loaded
|
||||||
Then the following tabs should be open:
|
Then the following tabs should be open:
|
||||||
- data/hints/iframe_target.html
|
- data/hints/iframe_target.html
|
||||||
- data/hello.txt (active)
|
- data/hello2.txt (active)
|
||||||
|
|
||||||
Scenario: Clicking on iframe with :hint all current
|
Scenario: Clicking on iframe with :hint all current
|
||||||
When I open data/hints/iframe.html
|
When I open data/hints/iframe.html
|
||||||
|
@ -242,3 +242,24 @@ Feature: Searching on a page
|
|||||||
Then the following tabs should be open:
|
Then the following tabs should be open:
|
||||||
- data/search.html
|
- data/search.html
|
||||||
- data/hello.txt (active)
|
- 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