Spaces:
Sleeping
Sleeping
File size: 6,118 Bytes
b0c3a57 0377502 b0c3a57 7f69fd6 0377502 7f69fd6 b0c3a57 0377502 b0c3a57 0377502 b0c3a57 0377502 7f69fd6 0377502 b0c3a57 0377502 b0c3a57 7f69fd6 0377502 7f69fd6 0377502 7f69fd6 0377502 b0c3a57 7f69fd6 b0c3a57 0377502 b0c3a57 0377502 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
"""OphthalmoCapture — Image Protection Layer
Injects CSS and JavaScript into the Streamlit page to prevent users from
downloading, dragging, or otherwise saving the confidential medical images.
APPROACH (HF Spaces compatible):
1. CSS via st.markdown(unsafe_allow_html=True) — Streamlit renders <style>
natively. Handles pointer-events, overlays, drag prevention.
2. JS via st.html() — Streamlit >= 1.33 renders raw HTML (including
<script>) inside a tiny srcdoc iframe. From there we reach the real
Streamlit DOM via window.parent.document (same-origin).
3. Additional CSS hides the st.html wrapper divs ([data-testid="stHtml"])
so the iframe has ZERO visual footprint — no layout shifts.
Protection layers (defence-in-depth):
1. CSS: pointer-events:none, user-select:none on <img>.
2. CSS: transparent ::after overlay on stImage containers.
3. CSS: -webkit-touch-callout:none for mobile.
4. JS: contextmenu blocked on entire parent document.
5. JS: Ctrl+S / Ctrl+U / Ctrl+P / F12 / DevTools shortcuts blocked.
6. JS: dragstart blocked for images.
7. JS: MutationObserver re-applies draggable=false to new images.
"""
import streamlit as st
# ── CSS via st.markdown ──────────────────────────────────────────────────────
_PROTECTION_CSS = """
<style>
/* Layer 1: Disable ALL interaction on <img> tags */
img {
pointer-events: none !important;
user-select: none !important;
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
-webkit-user-drag: none !important;
-webkit-touch-callout: none !important;
}
/* Layer 2: Transparent overlay on every Streamlit image container */
[data-testid="stImage"] {
position: relative !important;
}
[data-testid="stImage"]::after {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
z-index: 10;
background: transparent;
pointer-events: auto !important;
cursor: default;
}
/* Layer 3: Extra drag prevention */
[data-testid="stImage"] img {
-webkit-user-drag: none !important;
user-drag: none !important;
}
/* ── Hide st.html() wrappers to prevent ANY layout shift ──────────────── */
[data-testid="stHtml"] {
height: 0 !important;
min-height: 0 !important;
max-height: 0 !important;
overflow: hidden !important;
margin: 0 !important;
padding: 0 !important;
line-height: 0 !important;
font-size: 0 !important;
border: none !important;
}
[data-testid="stHtml"] iframe {
height: 0 !important;
min-height: 0 !important;
border: none !important;
display: block !important;
}
</style>
"""
# ── JS via st.html() — runs inside srcdoc iframe, reaches parent DOM ─────────
_PROTECTION_JS = """
<script>
(function () {
var doc;
try { doc = window.parent.document; } catch(e) { doc = document; }
// Guard: only attach once per page lifecycle
if (doc.__ophthalmo_protection__) return;
doc.__ophthalmo_protection__ = true;
function block(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return false;
}
// ── Block context menu (right-click) on ENTIRE page ─────────────────
doc.addEventListener('contextmenu', function (e) {
return block(e);
}, true);
// ── Block keyboard shortcuts ────────────────────────────────────────
doc.addEventListener('keydown', function (e) {
var dominated = false;
var ctrl = e.ctrlKey || e.metaKey;
var key = e.key ? e.key.toLowerCase() : '';
if (ctrl && key === 's') dominated = true; // Save page
if (ctrl && key === 'u') dominated = true; // View source
if (ctrl && key === 'p') dominated = true; // Print
if (e.keyCode === 123) dominated = true; // F12
if (ctrl && e.shiftKey && key === 'i') dominated = true; // Inspector
if (ctrl && e.shiftKey && key === 'j') dominated = true; // Console
if (ctrl && e.shiftKey && key === 'c') dominated = true; // Picker
if (dominated) return block(e);
}, true);
// ── Block drag-and-drop of images ───────────────────────────────────
doc.addEventListener('dragstart', function (e) {
if (e.target && e.target.tagName === 'IMG') return block(e);
}, true);
// ── MutationObserver — lock new images as they appear ───────────────
function lockImgs(root) {
var imgs = root.querySelectorAll ? root.querySelectorAll('img') : [];
for (var i = 0; i < imgs.length; i++) {
imgs[i].setAttribute('draggable', 'false');
imgs[i].ondragstart = function () { return false; };
imgs[i].oncontextmenu = function () { return false; };
}
}
lockImgs(doc);
var timer = null;
new MutationObserver(function () {
if (timer) return;
timer = setTimeout(function () {
timer = null;
lockImgs(doc);
}, 250);
}).observe(doc.body, { childList: true, subtree: true });
})();
</script>
"""
def inject_image_protection():
"""Inject CSS + JS image-protection layers into the page.
- CSS via st.markdown (always re-injected per rerun, as Streamlit requires).
- JS via st.html (rendered with <script> support; internal guard
prevents duplicate listeners across reruns).
- The CSS also hides st.html wrappers to prevent layout shifts.
"""
# 1) CSS protection + hide st.html wrappers
st.markdown(_PROTECTION_CSS, unsafe_allow_html=True)
# 2) JS protection (right-click, keyboard shortcuts, drag, observer)
st.html(_PROTECTION_JS)
|