TheBug95 commited on
Commit
0377502
·
1 Parent(s): 7f69fd6

Arreglo de saltos y proteccion contra descarga de imagenes

Browse files
interface/components/image_protection.py CHANGED
@@ -3,18 +3,20 @@
3
  Injects CSS and JavaScript into the Streamlit page to prevent users from
4
  downloading, dragging, or otherwise saving the confidential medical images.
5
 
6
- APPROACH:
7
- Streamlit's st.markdown(unsafe_allow_html=True) renders <style> tags but
8
- STRIPS <script> tags. To run JS without components.html (which creates
9
- iframes that cause layout shifts on HF Spaces), we use the classic
10
- <img src=x onerror="…"> trick the onerror handler executes inline JS
11
- directly in the main Streamlit DOM, with no iframe.
 
 
12
 
13
  Protection layers (defence-in-depth):
14
  1. CSS: pointer-events:none, user-select:none on <img>.
15
  2. CSS: transparent ::after overlay on stImage containers.
16
  3. CSS: -webkit-touch-callout:none for mobile.
17
- 4. JS: contextmenu blocked on entire document.
18
  5. JS: Ctrl+S / Ctrl+U / Ctrl+P / F12 / DevTools shortcuts blocked.
19
  6. JS: dragstart blocked for images.
20
  7. JS: MutationObserver re-applies draggable=false to new images.
@@ -22,11 +24,11 @@ Protection layers (defence-in-depth):
22
 
23
  import streamlit as st
24
 
25
- # ── CSS + JS injected via st.markdown ────────────────────────────────────────
26
- _PROTECTION_HTML = """
27
  <style>
28
  /* Layer 1: Disable ALL interaction on <img> tags */
29
- img:not([data-protection-trigger]) {
30
  pointer-events: none !important;
31
  user-select: none !important;
32
  -webkit-user-select: none !important;
@@ -56,70 +58,107 @@ img:not([data-protection-trigger]) {
56
  user-drag: none !important;
57
  }
58
 
59
- /* Hide the trigger pixel completely */
60
- img[data-protection-trigger] {
61
- display: none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
  </style>
 
64
 
65
- <!-- JS via onerror trick — runs directly in Streamlit DOM, no iframe -->
66
- <img data-protection-trigger src="x" onerror="
67
- (function(doc){
68
- if(doc.__ophthalmo_protection__) return;
69
- doc.__ophthalmo_protection__=true;
70
-
71
- function block(e){e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();return false;}
72
-
73
- doc.addEventListener('contextmenu',function(e){return block(e);},true);
74
-
75
- doc.addEventListener('keydown',function(e){
76
- var dominated=false;
77
- var ctrl=e.ctrlKey||e.metaKey;
78
- var key=e.key?e.key.toLowerCase():'';
79
- if(ctrl&&key==='s')dominated=true;
80
- if(ctrl&&key==='u')dominated=true;
81
- if(ctrl&&key==='p')dominated=true;
82
- if(e.keyCode===123)dominated=true;
83
- if(ctrl&&e.shiftKey&&key==='i')dominated=true;
84
- if(ctrl&&e.shiftKey&&key==='j')dominated=true;
85
- if(ctrl&&e.shiftKey&&key==='c')dominated=true;
86
- if(dominated)return block(e);
87
- },true);
88
-
89
- doc.addEventListener('dragstart',function(e){
90
- if(e.target&&e.target.tagName==='IMG')return block(e);
91
- },true);
92
-
93
- function lockImgs(root){
94
- var imgs=root.querySelectorAll?root.querySelectorAll('img:not([data-protection-trigger])'):[];
95
- for(var i=0;i<imgs.length;i++){
96
- imgs[i].setAttribute('draggable','false');
97
- imgs[i].ondragstart=function(){return false;};
98
- imgs[i].oncontextmenu=function(){return false;};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
  }
101
  lockImgs(doc);
102
 
103
- var t=null;
104
- new MutationObserver(function(muts){
105
- if(t)return;
106
- t=setTimeout(function(){
107
- t=null;
108
  lockImgs(doc);
109
- },200);
110
- }).observe(doc.body,{childList:true,subtree:true});
111
 
112
- })(document);
113
- " />
114
  """
115
 
116
 
117
  def inject_image_protection():
118
  """Inject CSS + JS image-protection layers into the page.
119
 
120
- Uses st.markdown only NO components.html iframes — so there are
121
- zero layout shifts. The JS guard (doc.__ophthalmo_protection__)
122
- ensures listeners are attached only once per page lifecycle even
123
- though st.markdown re-renders on every Streamlit rerun.
124
  """
125
- st.markdown(_PROTECTION_HTML, unsafe_allow_html=True)
 
 
 
 
 
3
  Injects CSS and JavaScript into the Streamlit page to prevent users from
4
  downloading, dragging, or otherwise saving the confidential medical images.
5
 
6
+ APPROACH (HF Spaces compatible):
7
+ 1. CSS via st.markdown(unsafe_allow_html=True) — Streamlit renders <style>
8
+ natively. Handles pointer-events, overlays, drag prevention.
9
+ 2. JS via st.html() Streamlit >= 1.33 renders raw HTML (including
10
+ <script>) inside a tiny srcdoc iframe. From there we reach the real
11
+ Streamlit DOM via window.parent.document (same-origin).
12
+ 3. Additional CSS hides the st.html wrapper divs ([data-testid="stHtml"])
13
+ so the iframe has ZERO visual footprint — no layout shifts.
14
 
15
  Protection layers (defence-in-depth):
16
  1. CSS: pointer-events:none, user-select:none on <img>.
17
  2. CSS: transparent ::after overlay on stImage containers.
18
  3. CSS: -webkit-touch-callout:none for mobile.
19
+ 4. JS: contextmenu blocked on entire parent document.
20
  5. JS: Ctrl+S / Ctrl+U / Ctrl+P / F12 / DevTools shortcuts blocked.
21
  6. JS: dragstart blocked for images.
22
  7. JS: MutationObserver re-applies draggable=false to new images.
 
24
 
25
  import streamlit as st
26
 
27
+ # ── CSS via st.markdown ──────────────────────────────────────────────────────
28
+ _PROTECTION_CSS = """
29
  <style>
30
  /* Layer 1: Disable ALL interaction on <img> tags */
31
+ img {
32
  pointer-events: none !important;
33
  user-select: none !important;
34
  -webkit-user-select: none !important;
 
58
  user-drag: none !important;
59
  }
60
 
61
+ /* ── Hide st.html() wrappers to prevent ANY layout shift ──────────────── */
62
+ [data-testid="stHtml"] {
63
+ height: 0 !important;
64
+ min-height: 0 !important;
65
+ max-height: 0 !important;
66
+ overflow: hidden !important;
67
+ margin: 0 !important;
68
+ padding: 0 !important;
69
+ line-height: 0 !important;
70
+ font-size: 0 !important;
71
+ border: none !important;
72
+ }
73
+ [data-testid="stHtml"] iframe {
74
+ height: 0 !important;
75
+ min-height: 0 !important;
76
+ border: none !important;
77
+ display: block !important;
78
  }
79
  </style>
80
+ """
81
 
82
+ # ── JS via st.html() — runs inside srcdoc iframe, reaches parent DOM ─────────
83
+ _PROTECTION_JS = """
84
+ <script>
85
+ (function () {
86
+ var doc;
87
+ try { doc = window.parent.document; } catch(e) { doc = document; }
88
+
89
+ // Guard: only attach once per page lifecycle
90
+ if (doc.__ophthalmo_protection__) return;
91
+ doc.__ophthalmo_protection__ = true;
92
+
93
+ function block(e) {
94
+ e.preventDefault();
95
+ e.stopPropagation();
96
+ e.stopImmediatePropagation();
97
+ return false;
98
+ }
99
+
100
+ // ─�� Block context menu (right-click) on ENTIRE page ─────────────────
101
+ doc.addEventListener('contextmenu', function (e) {
102
+ return block(e);
103
+ }, true);
104
+
105
+ // ── Block keyboard shortcuts ────────────────────────────────────────
106
+ doc.addEventListener('keydown', function (e) {
107
+ var dominated = false;
108
+ var ctrl = e.ctrlKey || e.metaKey;
109
+ var key = e.key ? e.key.toLowerCase() : '';
110
+
111
+ if (ctrl && key === 's') dominated = true; // Save page
112
+ if (ctrl && key === 'u') dominated = true; // View source
113
+ if (ctrl && key === 'p') dominated = true; // Print
114
+ if (e.keyCode === 123) dominated = true; // F12
115
+ if (ctrl && e.shiftKey && key === 'i') dominated = true; // Inspector
116
+ if (ctrl && e.shiftKey && key === 'j') dominated = true; // Console
117
+ if (ctrl && e.shiftKey && key === 'c') dominated = true; // Picker
118
+
119
+ if (dominated) return block(e);
120
+ }, true);
121
+
122
+ // ── Block drag-and-drop of images ───────────────────────────────────
123
+ doc.addEventListener('dragstart', function (e) {
124
+ if (e.target && e.target.tagName === 'IMG') return block(e);
125
+ }, true);
126
+
127
+ // ── MutationObserver — lock new images as they appear ───────────────
128
+ function lockImgs(root) {
129
+ var imgs = root.querySelectorAll ? root.querySelectorAll('img') : [];
130
+ for (var i = 0; i < imgs.length; i++) {
131
+ imgs[i].setAttribute('draggable', 'false');
132
+ imgs[i].ondragstart = function () { return false; };
133
+ imgs[i].oncontextmenu = function () { return false; };
134
  }
135
  }
136
  lockImgs(doc);
137
 
138
+ var timer = null;
139
+ new MutationObserver(function () {
140
+ if (timer) return;
141
+ timer = setTimeout(function () {
142
+ timer = null;
143
  lockImgs(doc);
144
+ }, 250);
145
+ }).observe(doc.body, { childList: true, subtree: true });
146
 
147
+ })();
148
+ </script>
149
  """
150
 
151
 
152
  def inject_image_protection():
153
  """Inject CSS + JS image-protection layers into the page.
154
 
155
+ - CSS via st.markdown (always re-injected per rerun, as Streamlit requires).
156
+ - JS via st.html (rendered with <script> support; internal guard
157
+ prevents duplicate listeners across reruns).
158
+ - The CSS also hides st.html wrappers to prevent layout shifts.
159
  """
160
+ # 1) CSS protection + hide st.html wrappers
161
+ st.markdown(_PROTECTION_CSS, unsafe_allow_html=True)
162
+
163
+ # 2) JS protection (right-click, keyboard shortcuts, drag, observer)
164
+ st.html(_PROTECTION_JS)
interface/main.py CHANGED
@@ -250,19 +250,21 @@ with st.spinner(t("loading_whisper", model=selected_model)):
250
  model = load_whisper_model(selected_model)
251
  # ── BROWSER CLOSE GUARD (beforeunload) ───────────────────────────────────
252
  # Warn the user when they try to close/reload the tab with data in session.
253
- # Uses img onerror trick to avoid creating an iframe (which causes layout shifts).
254
  if sm.has_undownloaded_data() and not st.session_state.get("session_downloaded", False):
255
- st.markdown(
256
- """<img src="x" style="display:none!important" onerror="
257
  (function(){
258
- if(window.__beforeunload_set__)return;
259
- window.__beforeunload_set__=true;
260
- window.addEventListener('beforeunload',function(e){
261
- e.preventDefault();e.returnValue='';
 
 
 
262
  });
263
  })();
264
- " />""",
265
- unsafe_allow_html=True,
266
  )
267
  # ── MAIN CONTENT ─────────────────────────────────────────────────────────────
268
  st.title(f"{config.APP_ICON} {config.APP_TITLE}")
 
250
  model = load_whisper_model(selected_model)
251
  # ── BROWSER CLOSE GUARD (beforeunload) ───────────────────────────────────
252
  # Warn the user when they try to close/reload the tab with data in session.
253
+ # Uses st.html() wrapper is hidden by the protection CSS.
254
  if sm.has_undownloaded_data() and not st.session_state.get("session_downloaded", False):
255
+ st.html(
256
+ """<script>
257
  (function(){
258
+ var doc;
259
+ try { doc = window.parent.document; } catch(e) { return; }
260
+ if (doc.__beforeunload_set__) return;
261
+ doc.__beforeunload_set__ = true;
262
+ window.parent.addEventListener('beforeunload', function(e) {
263
+ e.preventDefault();
264
+ e.returnValue = '';
265
  });
266
  })();
267
+ </script>"""
 
268
  )
269
  # ── MAIN CONTENT ─────────────────────────────────────────────────────────────
270
  st.title(f"{config.APP_ICON} {config.APP_TITLE}")