Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions photomap/backend/photomap_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from starlette.middleware.base import BaseHTTPMiddleware

from photomap.backend.args import get_args, get_version
from photomap.backend.config import get_config_manager
Expand Down Expand Up @@ -46,6 +47,23 @@

app.include_router(curation_router, prefix="/api/curation", tags=["curation"])


class IECompatibilityMiddleware(BaseHTTPMiddleware):
"""Add X-UA-Compatible header to every response.

This prevents Microsoft Edge and Internet Explorer from switching into
IE Compatibility Mode, which breaks Swiper v11 (SCRIPT1028) and causes
various HTML parsing errors (HTML1416, HTML1500).
"""

async def dispatch(self, request: Request, call_next):
response = await call_next(request)
response.headers["X-UA-Compatible"] = "IE=edge"
return response


app.add_middleware(IECompatibilityMiddleware)

# Mount static files and templates
static_path = get_package_resource_path("static")
app.mount("/static", StaticFiles(directory=static_path), name="static")
Expand Down
54 changes: 54 additions & 0 deletions photomap/frontend/static/unsupported-browser.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Browser not supported &mdash; PhotoMapAI</title>
<style>
body {
font-family: Arial, sans-serif;
background: #1a1a2e;
color: #e0e0e0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}
.box {
background: #16213e;
border: 1px solid #0f3460;
border-radius: 8px;
padding: 40px;
max-width: 540px;
text-align: center;
}
h1 { color: #e94560; margin-top: 0; }
p { line-height: 1.6; }
a { color: #0f9b8e; }
.steps { text-align: left; margin: 20px 0; padding-left: 20px; }
.steps li { margin: 8px 0; }
</style>
</head>
<body>
<div class="box">
<h1>Browser not supported</h1>
<p>
You are using <strong>Microsoft Edge (Legacy)</strong>, which does not
support the modern JavaScript required by PhotoMapAI.
</p>
<p>Please upgrade to the new <strong>Microsoft Edge (Chromium)</strong>:</p>
<ol class="steps">
<li>
Visit
<a href="https://www.microsoft.com/en-us/edge" target="_blank">
microsoft.com/edge
</a>
</li>
<li>Click <strong>Download</strong> and run the installer</li>
<li>After installation, open PhotoMapAI in the new Edge</li>
</ol>
<p>Chrome and Firefox are also fully supported.</p>
</div>
</body>
</html>
19 changes: 18 additions & 1 deletion photomap/frontend/templates/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,27 @@

<head>
<title id="slideshow_title">PhotoMap</title>
<!-- ... (Head content remains the same) ... -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="icon" type="image/x-icon" href="static/icons/favicon.ico" />

<!-- Detect legacy EdgeHTML (Edge ≤18 / Edge 44) before loading any ES2020 scripts.
window.StyleMedia is unique to EdgeHTML and absent from Chromium-based Edge.
window.location.replace() performs a full navigation so the original page
never finishes loading, which avoids the document.write() race condition. -->
<script>
(function () {
var isLegacyEdge = !!window.StyleMedia;
if (!isLegacyEdge) {
try { Function("x?.y"); } catch (e) { isLegacyEdge = true; }
}
if (isLegacyEdge) {
window.location.replace("/static/unsupported-browser.html");
}
}());
</script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<script src="https://cdn.plot.ly/plotly-3.0.1.min.js" charset="utf-8"></script>
Expand Down
2 changes: 1 addition & 1 deletion photomap/frontend/templates/modules/control-panel.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- HTML template for the "control panel" icons -- settings, delete, fullscreen, play/pause, etc. -->
<!-- HTML template for the "control panel" icons: settings, delete, fullscreen, play/pause, etc. -->

<!-- Control Panel -->
<div id="controlPanel">
Expand Down
2 changes: 1 addition & 1 deletion photomap/frontend/templates/modules/metadata-drawer.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div id="overlayDrawer" class="overlay-drawer">
<div class="drawer-handle">
<svg class="drawer-arrow" viewBox="0 0 18 18">
<path d="M7 14l5-5 5 5z" fill="currentColor" />
<path d="M7 14l5-5 5 5z" fill="currentColor"></path>
</svg>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion photomap/frontend/templates/modules/search-panel.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- HTML template for the search icons -- show/hide semantic map, search by image similarity, search by text -->
<!-- HTML template for the search icons: show/hide semantic map, search by image similarity, search by text -->
<!-- Search Panel -->
<div id="searchPanel">
<div class="search-panel-title">Search</div>
Expand Down
2 changes: 1 addition & 1 deletion photomap/frontend/templates/modules/spinner.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
to="360 25 25"
dur="1s"
repeatCount="indefinite"
/>
></animateTransform>
</circle>
</svg>
</div>
31 changes: 31 additions & 0 deletions tests/backend/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,34 @@ def test_root_route(client):
assert response.template.name == "main.html"
assert "<title id=\"slideshow_title\">PhotoMap</title>" in response.text


def test_ie_compatibility_header_on_html(client):
"""The root page must carry X-UA-Compatible: IE=edge to prevent Edge/IE
from switching into IE Compatibility Mode, which would break Swiper v11
and cause SCRIPT1028 / HTML1416 / HTML1500 console errors."""
response = client.get("/")
assert response.headers.get("x-ua-compatible") == "IE=edge"


def test_ie_compatibility_header_on_api(client):
"""The X-UA-Compatible header should also be present on API responses."""
response = client.get("/api/albums/")
assert response.headers.get("x-ua-compatible") == "IE=edge"


def test_legacy_edge_detection_script_present(client):
"""The root page must include an inline script that detects legacy EdgeHTML
(Edge ≤18 / Edge 44) and redirects to the upgrade-instructions page before
any ES2020 library code runs."""
response = client.get("/")
assert "window.StyleMedia" in response.text
assert "/static/unsupported-browser.html" in response.text


def test_unsupported_browser_page_served(client):
"""The static unsupported-browser page must be served and contain the
upgrade link so that legacy Edge users see the instructions."""
response = client.get("/static/unsupported-browser.html")
assert response.status_code == 200
assert "microsoft.com/edge" in response.text

Loading