Merge branch 'pr/3371'

This commit is contained in:
Florian Bruhin 2018-02-07 18:26:19 +01:00
commit aa3970c83e
10 changed files with 257 additions and 50 deletions

View File

@ -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"

View File

@ -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) => {

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View File

@ -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

View File

@ -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)