Services: real download lifecycle + cookie persistence (R4 + R2)
Downloads were captured at the MIME-handoff only; nsExternalAppHandler then failed CreateTransfer and cancelled every one. Registering an @mozilla.org/transfer;1 implementation makes saveToDisk actually run and gives the daemon the nsICancelable that cancelDownload needs. msgDownloadStart/Progress/Finished + cancelDownload -> msgDownloadError DOWNLOAD-LIFECYCLE PASS (build-download2-test.sh) cookies.sqlite present + survives an engine restart COOKIE-PERSISTENCE PASS (build-cookie-test.sh) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JDvWTswwcSHQHVA1gD795L
This commit is contained in:
1 parent
76401efecc
commit
8953eab6de
12 files changed
+975
-16
No files matched your search
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2026 the Jihad Browser project.
|
||||
# Licensed under the Apache License, Version 2.0 (see ../../LICENSE).
|
||||
#
|
||||
# Cookie PERSISTENCE test (cavekit-browser-services R2): a local HTTP server
|
||||
# hands out a PERSISTENT cookie (future Max-Age); the engine is started, shut
|
||||
# down cleanly, and started AGAIN against the same profile dir. The cookie must
|
||||
# come back on the second run and cookies.sqlite must exist in the profile.
|
||||
# This is the desktop reproduction of the device gap logged 2026-07-19 s4.
|
||||
set -uo pipefail
|
||||
DIST=/out/obj-jihad-goanna/dist
|
||||
SRC=/jihad/render/goanna
|
||||
OUT=/out/cookie_test
|
||||
PROF=/out/cookieprofile
|
||||
PORT=${JIHAD_COOKIE_PORT:-8138}
|
||||
[ -e "$DIST/bin/libxul.so" ] || { echo "ERROR: build the engine first"; exit 1; }
|
||||
|
||||
CXX=${CXX:-g++}
|
||||
GTK_CFLAGS=$(pkg-config --cflags gtk+-2.0 glib-2.0); GTK_LIBS=$(pkg-config --libs gtk+-2.0 glib-2.0)
|
||||
CXXFLAGS="-std=gnu++17 -fPIC -fno-rtti -fno-exceptions -O2 -g0"
|
||||
INCS="-include $DIST/include/mozilla-config.h -I$DIST/include -I$DIST/include/nspr $GTK_CFLAGS"
|
||||
|
||||
$CXX $CXXFLAGS $INCS -c "$SRC/EngineHost.cpp" -o /out/EngineHost.o || exit 10
|
||||
$CXX $CXXFLAGS $INCS -c "$SRC/DialogService.cpp" -o /out/DialogService.o || exit 10
|
||||
$CXX $CXXFLAGS $INCS -c "$SRC/DownloadService.cpp" -o /out/DownloadService.o || exit 10
|
||||
$CXX $CXXFLAGS $INCS -c "$SRC/GoannaRenderPage.cpp" -o /out/GoannaRenderPage.o || exit 11
|
||||
$CXX $CXXFLAGS $INCS -c "$SRC/test/cookie_test.cpp" -o /out/cookie_test.o || exit 12
|
||||
$CXX /out/cookie_test.o /out/GoannaRenderPage.o /out/EngineHost.o /out/DialogService.o /out/DownloadService.o \
|
||||
"$DIST/sdk/lib/libxpcomglue_s.a" -L"$DIST/bin" -lxul "$DIST/sdk/lib/libmozglue.a" \
|
||||
-lnspr4 -lplc4 -lplds4 $GTK_LIBS -Wl,-rpath,"$DIST/bin" -ldl -lpthread -o "$OUT" || exit 13
|
||||
echo "== built $OUT =="
|
||||
|
||||
cat > /out/cookiesrv.py <<'PYEOF'
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
PORT = int(sys.argv[1])
|
||||
|
||||
class H(BaseHTTPRequestHandler):
|
||||
protocol_version = "HTTP/1.1"
|
||||
def log_message(self, *a): pass
|
||||
def _html(self, body, extra=None):
|
||||
data = body.encode()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(data)))
|
||||
if extra:
|
||||
for k, v in extra:
|
||||
self.send_header(k, v)
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
def do_GET(self):
|
||||
if self.path.startswith("/set"):
|
||||
# PERSISTENT cookie: a session cookie is not meant to survive a restart,
|
||||
# so only a Max-Age/Expires cookie proves persistence.
|
||||
self._html("<title>SET</title>set",
|
||||
[("Set-Cookie", "jihadpersist=1; Max-Age=3600; Path=/")])
|
||||
elif self.path.startswith("/echo"):
|
||||
c = self.headers.get("Cookie", "")
|
||||
self._html("<title>COOKIE[%s]</title>echo" % c)
|
||||
else:
|
||||
self._html("<title>404</title>no")
|
||||
|
||||
ThreadingHTTPServer.daemon_threads = True
|
||||
ThreadingHTTPServer(("127.0.0.1", PORT), H).serve_forever()
|
||||
PYEOF
|
||||
|
||||
python3 /out/cookiesrv.py "$PORT" &
|
||||
httpd=$!
|
||||
sleep 1
|
||||
|
||||
if ! grep -q 'jihad-embed' "$DIST/bin/goanna.js" 2>/dev/null; then
|
||||
echo 'pref("layers.offmainthreadcomposition.force-disabled", true);' >> "$DIST/bin/goanna.js"
|
||||
fi
|
||||
|
||||
rm -rf "$PROF"
|
||||
export JIHAD_DISABLE_OMTC=1
|
||||
export JIHAD_PROFILE_DIR="$PROF"
|
||||
export LD_LIBRARY_PATH="$DIST/bin"
|
||||
BASE="http://127.0.0.1:$PORT"
|
||||
|
||||
echo "== run 1: set the cookie =="
|
||||
xvfb-run -a -s "-screen 0 1024x768x24" "$OUT" "$DIST/bin" set "$BASE"
|
||||
rc1=$?
|
||||
echo "== profile after run 1 =="
|
||||
ls -l "$PROF" || true
|
||||
|
||||
echo "== run 2: RESTART, cookie must still be sent =="
|
||||
xvfb-run -a -s "-screen 0 1024x768x24" "$OUT" "$DIST/bin" check "$BASE"
|
||||
rc2=$?
|
||||
|
||||
kill "$httpd" 2>/dev/null
|
||||
echo "== run1=$rc1 run2=$rc2 =="
|
||||
if [ "$rc1" = 0 ] && [ "$rc2" = 0 ]; then
|
||||
echo "COOKIE-PERSISTENCE PASS"; exit 0
|
||||
else
|
||||
echo "COOKIE-PERSISTENCE FAIL"; exit 1
|
||||
fi
|
||||
Executable
+128
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2026 the Jihad Browser project.
|
||||
# Licensed under the Apache License, Version 2.0 (see ../../LICENSE).
|
||||
#
|
||||
# Download LIFECYCLE test (cavekit-browser-services R4): builds the real
|
||||
# jihad-browserserver daemon + a YAP adapter stand-in and drives two downloads
|
||||
# from a local HTTP server, asserting the frozen daemon->adapter messages:
|
||||
# msgDownloadStart (0x2010) -> msgDownloadProgress (0x2011) ->
|
||||
# msgDownloadFinished (0x2013, mime + temp path), and
|
||||
# cancelDownload (0x1015) aborting an in-progress download (msgDownloadError,
|
||||
# never msgDownloadFinished).
|
||||
# Sibling of build-download-test.sh, which covers only the MIME handoff capture.
|
||||
set -uo pipefail
|
||||
DIST=/out/obj-jihad-goanna/dist
|
||||
R=/jihad/render
|
||||
BS=/jihad/render/browserserver
|
||||
[ -e "$DIST/bin/libxul.so" ] || { echo "ERROR: build the engine first"; exit 1; }
|
||||
|
||||
CXX=${CXX:-g++}
|
||||
CXXFLAGS="-std=gnu++17 -fPIC -fno-rtti -fno-exceptions -O2 -g0"
|
||||
GLIB=$(pkg-config --cflags glib-2.0 gthread-2.0); GLIBL=$(pkg-config --libs glib-2.0 gthread-2.0)
|
||||
GTKC=$(pkg-config --cflags gtk+-2.0); GTKL=$(pkg-config --libs gtk+-2.0)
|
||||
ENGINC="-include $DIST/include/mozilla-config.h -I$DIST/include -I$DIST/include/nspr"
|
||||
YAPINC="-I$BS/Yap -I$BS/Src"
|
||||
XULLINK="$DIST/sdk/lib/libxpcomglue_s.a -L$DIST/bin -lxul $DIST/sdk/lib/libmozglue.a -lnspr4 -lplc4 -lplds4"
|
||||
|
||||
echo "== compiling libYap (incl YapClient) + BrowserServerBase =="
|
||||
for f in YapPacket YapProxy YapServer YapClient; do
|
||||
$CXX $CXXFLAGS $YAPINC $GLIB -c "$BS/Yap/$f.cpp" -o "/out/$f.o" || exit 10
|
||||
done
|
||||
$CXX $CXXFLAGS $YAPINC $GLIB -c "$BS/Src/BrowserServerBase.cpp" -o /out/BrowserServerBase.o || exit 11
|
||||
|
||||
echo "== compiling Goanna backend + daemon =="
|
||||
$CXX $CXXFLAGS $ENGINC $GTKC -c "$R/goanna/EngineHost.cpp" -o /out/EngineHost.o || exit 12
|
||||
$CXX $CXXFLAGS $ENGINC $GTKC -c "$R/goanna/DialogService.cpp" -o /out/DialogService.o || exit 12
|
||||
$CXX $CXXFLAGS $ENGINC $GTKC -c "$R/goanna/DownloadService.cpp" -o /out/DownloadService.o || exit 12
|
||||
$CXX $CXXFLAGS $ENGINC $GTKC -c "$R/goanna/GoannaRenderPage.cpp" -o /out/GoannaRenderPage.o || exit 13
|
||||
$CXX $CXXFLAGS $ENGINC $GTKC -c "$R/goanna/BrowserPageGoanna.cpp" -o /out/BrowserPageGoanna.o || exit 14
|
||||
$CXX $CXXFLAGS $YAPINC $GLIB -c "$BS/JihadBrowserServer.cpp" -o /out/JihadBrowserServer.o || exit 15
|
||||
$CXX $CXXFLAGS $YAPINC $ENGINC $GTKC $GLIB -c "$BS/Main.cpp" -o /out/bs_main.o || exit 16
|
||||
$CXX /out/bs_main.o /out/JihadBrowserServer.o /out/BrowserServerBase.o \
|
||||
/out/YapPacket.o /out/YapProxy.o /out/YapServer.o \
|
||||
/out/BrowserPageGoanna.o /out/GoannaRenderPage.o /out/EngineHost.o /out/DialogService.o /out/DownloadService.o \
|
||||
$XULLINK $GTKL $GLIBL -Wl,-rpath,"$DIST/bin" -ldl -lpthread -o /out/jihad-browserserver || exit 17
|
||||
|
||||
echo "== compiling download adapter stand-in =="
|
||||
$CXX $CXXFLAGS $YAPINC $GLIB -c "$BS/test/download_client.cpp" -o /out/download_client.o || exit 18
|
||||
$CXX /out/download_client.o /out/YapClient.o /out/YapPacket.o \
|
||||
$GLIBL -ldl -lpthread -o /out/jihad-download-client || exit 19
|
||||
|
||||
# --- local HTTP server: one fast attachment + one deliberately slow one -------
|
||||
PORT=${JIHAD_DL_PORT:-8137}
|
||||
SIZE=${JIHAD_DL_SIZE:-524288}
|
||||
cat > /out/dlserver.py <<'PYEOF'
|
||||
import sys, time
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
PORT = int(sys.argv[1]); SIZE = int(sys.argv[2])
|
||||
CHUNK = 32768
|
||||
SLOW_SIZE = 32 * 1024 * 1024 # never actually completes within the test
|
||||
|
||||
class H(BaseHTTPRequestHandler):
|
||||
protocol_version = "HTTP/1.1"
|
||||
def log_message(self, *a): pass
|
||||
|
||||
def _attach(self, total, name):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/octet-stream")
|
||||
self.send_header("Content-Disposition", 'attachment; filename="%s"' % name)
|
||||
self.send_header("Content-Length", str(total))
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
try:
|
||||
if self.path.startswith("/big"):
|
||||
self._attach(SIZE, "jihad-payload.bin")
|
||||
sent = 0
|
||||
while sent < SIZE:
|
||||
n = min(CHUNK, SIZE - sent)
|
||||
self.wfile.write(b"J" * n); self.wfile.flush()
|
||||
sent += n
|
||||
time.sleep(0.03) # several progress ticks
|
||||
elif self.path.startswith("/slow"):
|
||||
self._attach(SLOW_SIZE, "jihad-slow.bin")
|
||||
sent = 0
|
||||
while sent < SLOW_SIZE:
|
||||
self.wfile.write(b"S" * CHUNK); self.wfile.flush()
|
||||
sent += CHUNK
|
||||
time.sleep(0.25) # ~4 min: cancelled long before this ends
|
||||
else:
|
||||
self.send_response(404); self.send_header("Content-Length", "0"); self.end_headers()
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
pass # the cancel closed the socket: expected
|
||||
|
||||
ThreadingHTTPServer.daemon_threads = True
|
||||
ThreadingHTTPServer(("127.0.0.1", PORT), H).serve_forever()
|
||||
PYEOF
|
||||
|
||||
rm -rf /out/dltmp && mkdir -p /out/dltmp
|
||||
python3 /out/dlserver.py "$PORT" "$SIZE" &
|
||||
httpd=$!
|
||||
sleep 1
|
||||
|
||||
echo "== download lifecycle round-trip under Xvfb =="
|
||||
export DIST PORT SIZE
|
||||
xvfb-run -a -s "-screen 0 1024x768x24" bash -c '
|
||||
export JIHAD_DISABLE_OMTC=1
|
||||
export JIHAD_OFFSCREEN=1
|
||||
export JIHAD_DOWNLOAD_DIR=/out/dltmp
|
||||
export JIHAD_PROFILE_DIR=/out/dlprofile
|
||||
export LD_LIBRARY_PATH="$DIST/bin"
|
||||
/out/jihad-browserserver "$DIST/bin" &
|
||||
dpid=$!
|
||||
sleep 8 # let the engine come up + open the socket
|
||||
JIHAD_DL_URL="http://127.0.0.1:$PORT/big" \
|
||||
JIHAD_DL_SLOW="http://127.0.0.1:$PORT/slow" \
|
||||
JIHAD_DL_SIZE="$SIZE" \
|
||||
timeout 70 /out/jihad-download-client
|
||||
rc=$?
|
||||
kill "$dpid" 2>/dev/null; wait 2>/dev/null
|
||||
exit $rc
|
||||
'
|
||||
rc=$?
|
||||
kill "$httpd" 2>/dev/null
|
||||
echo "== files left in /out/dltmp: $(ls -l /out/dltmp | tail -n +2 | wc -l) =="
|
||||
ls -l /out/dltmp || true
|
||||
echo "== download2 exit: $rc =="
|
||||
exit $rc
|
||||
@@ -88,6 +88,8 @@ asserts real behavior under Xvfb (all PASS):
|
||||
| `build-download-test.sh` | download / MIME handoff (DOWNLOAD PASS) |
|
||||
| `build-resize-test.sh` / `build-scroll-test.sh` / `build-zoom-test.sh` | surface geometry |
|
||||
| `build-geo-test.sh` | contents-size / meta-viewport / scrolled-to events (GEO PASS) |
|
||||
| `build-download2-test.sh` | daemon-side download lifecycle over YAP: `msgDownloadStart`/`Progress`/`Finished` (temp path + MIME) and `cancelDownload` aborting an in-progress download (DOWNLOAD-LIFECYCLE PASS) |
|
||||
| `build-cookie-test.sh` | persistent cookie survives an engine restart + `cookies.sqlite` exists in the profile (COOKIE-PERSISTENCE PASS) |
|
||||
|
||||
## R4 — isis Enyo UI against the desktop daemon `[human-review]`
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ void ProxySink::msgUrlRedirected(const char* u, const char* ud) { mSrv->msgUrlRe
|
||||
void ProxySink::msgSSLConfirm(const char* host, int32_t code, const char* certFile) { mSrv->msgDialogSSLConfirm(mProxy, "", host, code, certFile); }
|
||||
void ProxySink::msgLinkClicked(const char* url) { mSrv->msgLinkClicked(mProxy, url); }
|
||||
void ProxySink::msgMimeHandoffUrl(const char* mimeType, const char* url) { mSrv->msgMimeHandoffUrl(mProxy, mimeType, url); }
|
||||
void ProxySink::msgDownloadStart(const char* url) { mSrv->msgDownloadStart(mProxy, url); }
|
||||
void ProxySink::msgDownloadProgress(const char* url, int32_t soFar, int32_t total) { mSrv->msgDownloadProgress(mProxy, url, soFar, total); }
|
||||
void ProxySink::msgDownloadFinished(const char* url, const char* mimeType, const char* tmpFilePath) { mSrv->msgDownloadFinished(mProxy, url, mimeType, tmpFilePath); }
|
||||
void ProxySink::msgDownloadError(const char* url, const char* errorMsg) { mSrv->msgDownloadError(mProxy, url, errorMsg); }
|
||||
|
||||
// ---- server ----------------------------------------------------------------
|
||||
JihadBrowserServer::JihadBrowserServer(const char* name, jihad::EngineHost& host)
|
||||
@@ -55,6 +59,35 @@ void JihadBrowserServer::OnDownload(const char* url, const char* mimeType,
|
||||
printf("[jihad-bs] download: no active page to hand off to\n");
|
||||
}
|
||||
|
||||
// The frozen msgDownloadProgress carries int32 byte counts; the engine counts in
|
||||
// int64. Saturate rather than wrap so a >2 GB transfer still reports monotonic
|
||||
// progress (and keep -1 = "unknown total" intact).
|
||||
static int32_t clampToI32(int64_t v) {
|
||||
if (v < 0) return -1;
|
||||
return v > 0x7fffffffLL ? 0x7fffffff : (int32_t)v;
|
||||
}
|
||||
|
||||
// Lifecycle of the download the ENGINE performs (DownloadService drives the
|
||||
// helper-app save). Like OnDownload these have no page handle, so they follow
|
||||
// the same rule: route to the ACTIVE card.
|
||||
void JihadBrowserServer::OnDownloadStart(const char* url) {
|
||||
if (auto* p = pageFor(mLastProxy)) p->emitDownloadStart(url ? url : "");
|
||||
}
|
||||
void JihadBrowserServer::OnDownloadProgress(const char* url, int64_t bytesSoFar, int64_t totalBytes) {
|
||||
if (auto* p = pageFor(mLastProxy))
|
||||
p->emitDownloadProgress(url ? url : "", clampToI32(bytesSoFar), clampToI32(totalBytes));
|
||||
}
|
||||
void JihadBrowserServer::OnDownloadFinished(const char* url, const char* mimeType, const char* tmpFilePath) {
|
||||
printf("[jihad-bs] download finished mime=%s path=%s\n",
|
||||
mimeType ? mimeType : "", tmpFilePath ? tmpFilePath : "");
|
||||
if (auto* p = pageFor(mLastProxy))
|
||||
p->emitDownloadFinished(url ? url : "", mimeType ? mimeType : "", tmpFilePath ? tmpFilePath : "");
|
||||
}
|
||||
void JihadBrowserServer::OnDownloadError(const char* url, const char* errorMsg) {
|
||||
printf("[jihad-bs] download error %s: %s\n", url ? url : "", errorMsg ? errorMsg : "");
|
||||
if (auto* p = pageFor(mLastProxy)) p->emitDownloadError(url ? url : "", errorMsg ? errorMsg : "");
|
||||
}
|
||||
|
||||
jihad::BrowserPageGoanna* JihadBrowserServer::pageFor(YapProxy* proxy) {
|
||||
auto it = mPages.find(proxy);
|
||||
return it == mPages.end() ? nullptr : it->second.page;
|
||||
@@ -238,9 +271,16 @@ void JihadBrowserServer::asyncCmdExit(YapProxy* proxy)
|
||||
(void)proxy; // TODO(T-016): route to pageFor(proxy) / GoannaRenderPage per PORT-MAP.md
|
||||
}
|
||||
|
||||
// YAP cancelDownload(url): abort the matching in-flight engine download. The
|
||||
// download is process-wide (the engine's helper-app service owns it, not a
|
||||
// page), so this is not routed per-proxy. An empty url cancels all of them,
|
||||
// matching the adapter's "stop whatever is downloading" intent.
|
||||
void JihadBrowserServer::asyncCmdCancelDownload(YapProxy* proxy, const char* url)
|
||||
{
|
||||
(void)proxy; // TODO(T-016): route to pageFor(proxy) / GoannaRenderPage per PORT-MAP.md
|
||||
(void)proxy;
|
||||
bool aborted = jihad::CancelDownload(url);
|
||||
printf("[jihad-bs] cancelDownload %s -> %s\n", url ? url : "(all)",
|
||||
aborted ? "aborted" : "no match");
|
||||
}
|
||||
|
||||
void JihadBrowserServer::asyncCmdInterrogateClicks(YapProxy* proxy, bool enable)
|
||||
|
||||
@@ -41,6 +41,10 @@ public:
|
||||
void msgSSLConfirm(const char* host, int32_t code, const char* certFile) override;
|
||||
void msgLinkClicked(const char* url) override;
|
||||
void msgMimeHandoffUrl(const char* mimeType, const char* url) override;
|
||||
void msgDownloadStart(const char* url) override;
|
||||
void msgDownloadProgress(const char* url, int32_t soFar, int32_t total) override;
|
||||
void msgDownloadFinished(const char* url, const char* mimeType, const char* tmpFilePath) override;
|
||||
void msgDownloadError(const char* url, const char* errorMsg) override;
|
||||
private:
|
||||
JihadBrowserServer* mSrv;
|
||||
YapProxy* mProxy;
|
||||
@@ -56,6 +60,12 @@ public:
|
||||
// that page's client so the app hands it to com.palm.downloadmanager.
|
||||
void OnDownload(const char* url, const char* mimeType,
|
||||
const char* suggestedName, int64_t contentLength) override;
|
||||
// ... and the lifecycle of the download the engine itself performs, which maps
|
||||
// 1:1 onto the frozen msgDownloadStart/Progress/Finished/Error contract.
|
||||
void OnDownloadStart(const char* url) override;
|
||||
void OnDownloadProgress(const char* url, int64_t bytesSoFar, int64_t totalBytes) override;
|
||||
void OnDownloadFinished(const char* url, const char* mimeType, const char* tmpFilePath) override;
|
||||
void OnDownloadError(const char* url, const char* errorMsg) override;
|
||||
|
||||
virtual void clientConnected(YapProxy* proxy);
|
||||
virtual void clientDisconnected(YapProxy* proxy);
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Jihad Browser — download-lifecycle adapter stand-in (domain G / R4).
|
||||
*
|
||||
* A YapClient that drives the real jihad-browserserver over the frozen YAP
|
||||
* contract and asserts the daemon-side download messages:
|
||||
*
|
||||
* openUrl($JIHAD_DL_URL) -> msgDownloadStart (0x2010)
|
||||
* -> msgDownloadProgress (0x2011) [>= 1]
|
||||
* -> msgDownloadFinished (0x2013) with mime + temp path
|
||||
* openUrl($JIHAD_DL_SLOW) -> msgDownloadStart, then
|
||||
* cancelDownload (0x1015) -> msgDownloadError (0x2012), and NO finished.
|
||||
*
|
||||
* The finished temp file is stat()ed to prove the daemon really wrote the bytes
|
||||
* (both processes run in the same container / filesystem).
|
||||
*/
|
||||
#include <YapClient.h>
|
||||
#include <YapPacket.h>
|
||||
#include <glib.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static const char* envOr(const char* k, const char* dflt) {
|
||||
const char* v = getenv(k);
|
||||
return (v && *v) ? v : dflt;
|
||||
}
|
||||
|
||||
class DownloadClient : public YapClient {
|
||||
public:
|
||||
explicit DownloadClient(const char* name)
|
||||
: YapClient(name), W(640), H(480), sz(640*480*4),
|
||||
key1(0x4a494451), key2(0x4a494452) {}
|
||||
|
||||
int W, H, sz, key1, key2;
|
||||
std::string bigUrl, slowUrl;
|
||||
GMainContext* ctx = nullptr; // the client's own loop context (timers attach here)
|
||||
|
||||
// Phase 1 (bigUrl) tallies.
|
||||
int startBig = 0, progressBig = 0, finishedBig = 0, errorBig = 0;
|
||||
long finishedSize = -1;
|
||||
std::string finishedMime, finishedPath;
|
||||
// Phase 2 (slowUrl) tallies.
|
||||
int startSlow = 0, progressSlow = 0, finishedSlow = 0, errorSlow = 0;
|
||||
bool cancelSent = false;
|
||||
bool phase2 = false;
|
||||
|
||||
void serverConnected() override {}
|
||||
|
||||
void openUrl(const std::string& url) {
|
||||
YapPacket* o = packetCommand();
|
||||
(*o) << (int16_t)0x1004; (*o) << url.c_str();
|
||||
sendAsyncCommand();
|
||||
printf("[dlc] -> openUrl %s\n", url.c_str());
|
||||
}
|
||||
|
||||
void cancelDownload(const std::string& url) {
|
||||
YapPacket* c = packetCommand();
|
||||
(*c) << (int16_t)0x1015; (*c) << url.c_str();
|
||||
sendAsyncCommand();
|
||||
cancelSent = true;
|
||||
printf("[dlc] -> cancelDownload %s\n", url.c_str());
|
||||
}
|
||||
|
||||
void start() {
|
||||
shmget(key1, sz, IPC_CREAT | 0600);
|
||||
shmget(key2, sz, IPC_CREAT | 0600);
|
||||
YapPacket* c = packetCommand(); // Connect (0x1000)
|
||||
(*c) << (int16_t)0x1000;
|
||||
(*c) << (int32_t)W; (*c) << (int32_t)H;
|
||||
(*c) << (int32_t)key1; (*c) << (int32_t)key2;
|
||||
(*c) << (int32_t)sz; (*c) << (int32_t)1;
|
||||
sendAsyncCommand();
|
||||
openUrl(bigUrl);
|
||||
}
|
||||
|
||||
void beginPhase2() {
|
||||
if (phase2) return;
|
||||
phase2 = true;
|
||||
openUrl(slowUrl);
|
||||
}
|
||||
|
||||
// Cancel the slow download only after it has really been running for a while,
|
||||
// so the assertion is "aborts an IN-PROGRESS download", not "refuses to start".
|
||||
void scheduleCancel(int ms) {
|
||||
if (cancelArmed || !ctx) return;
|
||||
cancelArmed = true;
|
||||
GSource* s = g_timeout_source_new(ms);
|
||||
g_source_set_callback(s, &DownloadClient::cancelCb, this, nullptr);
|
||||
g_source_attach(s, ctx);
|
||||
g_source_unref(s);
|
||||
}
|
||||
static gboolean cancelCb(gpointer d) {
|
||||
DownloadClient* c = (DownloadClient*)d;
|
||||
if (!c->cancelSent) c->cancelDownload(c->slowUrl);
|
||||
return FALSE;
|
||||
}
|
||||
bool cancelArmed = false;
|
||||
|
||||
// End the run a beat after the cancel is reported — long enough that a stray
|
||||
// msgDownloadFinished for the cancelled download would still be caught.
|
||||
void scheduleQuit(int ms) {
|
||||
if (quitArmed || !ctx) return;
|
||||
quitArmed = true;
|
||||
GSource* s = g_timeout_source_new(ms);
|
||||
g_source_set_callback(s, &DownloadClient::quitCb2, this, nullptr);
|
||||
g_source_attach(s, ctx);
|
||||
g_source_unref(s);
|
||||
}
|
||||
static gboolean quitCb2(gpointer d) {
|
||||
g_main_loop_quit(((DownloadClient*)d)->mainLoop());
|
||||
return FALSE;
|
||||
}
|
||||
bool quitArmed = false;
|
||||
|
||||
void serverDisconnected() override { printf("[dlc] serverDisconnected\n"); }
|
||||
|
||||
void handleAsyncMessage(YapPacket* msg) override {
|
||||
int16_t id = 0;
|
||||
(*msg) >> id;
|
||||
switch ((uint16_t)id) {
|
||||
case 0x2000: { // msgPainted — hand the buffer straight back
|
||||
int32_t k = 0; (*msg) >> k;
|
||||
YapPacket* r = packetCommand();
|
||||
(*r) << (int16_t)0x150d; (*r) << (int32_t)k;
|
||||
sendAsyncCommand();
|
||||
break; }
|
||||
case 0x2010: { // msgDownloadStart(url)
|
||||
char* u = 0; (*msg) >> u;
|
||||
std::string url = u ? u : ""; if (u) free(u);
|
||||
printf("[dlc] <- msgDownloadStart(%s)\n", url.c_str());
|
||||
if (url == slowUrl) { ++startSlow; scheduleCancel(2000); }
|
||||
else { ++startBig; }
|
||||
break; }
|
||||
case 0x2011: { // msgDownloadProgress(url, soFar, total)
|
||||
char* u = 0; int32_t so = 0, tot = 0;
|
||||
(*msg) >> u; (*msg) >> so; (*msg) >> tot;
|
||||
std::string url = u ? u : ""; if (u) free(u);
|
||||
if (url == slowUrl) {
|
||||
if (++progressSlow <= 3)
|
||||
printf("[dlc] <- msgDownloadProgress(slow, %d/%d)\n", so, tot);
|
||||
} else {
|
||||
if (++progressBig <= 3)
|
||||
printf("[dlc] <- msgDownloadProgress(big, %d/%d)\n", so, tot);
|
||||
}
|
||||
break; }
|
||||
case 0x2012: { // msgDownloadError(url, msg)
|
||||
char* u = 0; char* e = 0; (*msg) >> u; (*msg) >> e;
|
||||
std::string url = u ? u : "", err = e ? e : "";
|
||||
if (u) free(u); if (e) free(e);
|
||||
printf("[dlc] <- msgDownloadError(%s, %s)\n", url.c_str(), err.c_str());
|
||||
if (url == slowUrl) { ++errorSlow; scheduleQuit(3000); } else ++errorBig;
|
||||
break; }
|
||||
case 0x2013: { // msgDownloadFinished(url, mime, tmpPath)
|
||||
char* u = 0; char* m = 0; char* p = 0;
|
||||
(*msg) >> u; (*msg) >> m; (*msg) >> p;
|
||||
std::string url = u ? u : "", mime = m ? m : "", path = p ? p : "";
|
||||
if (u) free(u); if (m) free(m); if (p) free(p);
|
||||
printf("[dlc] <- msgDownloadFinished(%s, mime=%s, path=%s)\n",
|
||||
url.c_str(), mime.c_str(), path.c_str());
|
||||
if (url == slowUrl) { ++finishedSlow; break; }
|
||||
++finishedBig;
|
||||
finishedMime = mime; finishedPath = path;
|
||||
struct stat st;
|
||||
finishedSize = (!path.empty() && stat(path.c_str(), &st) == 0) ? (long)st.st_size : -1;
|
||||
beginPhase2();
|
||||
break; }
|
||||
case 0x2015: { // msgMimeHandoffUrl(mime, url) — the app's existing path
|
||||
char* m = 0; char* u = 0; (*msg) >> m; (*msg) >> u;
|
||||
printf("[dlc] <- msgMimeHandoffUrl(%s, %s)\n", m ? m : "", u ? u : "");
|
||||
if (m) free(m); if (u) free(u);
|
||||
break; }
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static gboolean quitCb(gpointer data) {
|
||||
g_main_loop_quit((GMainLoop*)data);
|
||||
return FALSE;
|
||||
}
|
||||
// Phase 2 never completes on its own (the download is cancelled), so a second
|
||||
// timer closes it out once the cancel has had time to be reported.
|
||||
static gboolean phase2GuardCb(gpointer data) {
|
||||
DownloadClient* c = (DownloadClient*)data;
|
||||
if (!c->phase2) c->beginPhase2(); // phase 1 stalled; still exercise cancel
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc; (void)argv;
|
||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
const char* name = envOr("JIHAD_BS_NAME", "jihad-browserserver");
|
||||
DownloadClient client(name);
|
||||
client.bigUrl = envOr("JIHAD_DL_URL", "http://127.0.0.1:8137/big");
|
||||
client.slowUrl = envOr("JIHAD_DL_SLOW", "http://127.0.0.1:8137/slow");
|
||||
long expectSize = atol(envOr("JIHAD_DL_SIZE", "524288"));
|
||||
|
||||
if (!client.connect()) { fprintf(stderr, "[dlc] connect('%s') FAILED\n", name); return 1; }
|
||||
printf("[dlc] connected to '%s'\n", name);
|
||||
GMainContext* ctx = g_main_loop_get_context(client.mainLoop());
|
||||
client.ctx = ctx;
|
||||
client.start();
|
||||
|
||||
GSource* guard = g_timeout_source_new(25000);
|
||||
g_source_set_callback(guard, phase2GuardCb, &client, nullptr);
|
||||
g_source_attach(guard, ctx);
|
||||
GSource* quit = g_timeout_source_new(45000);
|
||||
g_source_set_callback(quit, quitCb, client.mainLoop(), nullptr);
|
||||
g_source_attach(quit, ctx);
|
||||
|
||||
client.run();
|
||||
|
||||
{ int i1 = shmget(client.key1, client.sz, 0); if (i1 >= 0) shmctl(i1, IPC_RMID, nullptr);
|
||||
int i2 = shmget(client.key2, client.sz, 0); if (i2 >= 0) shmctl(i2, IPC_RMID, nullptr); }
|
||||
|
||||
bool startOK = client.startBig >= 1;
|
||||
bool progressOK = client.progressBig >= 1;
|
||||
bool finishOK = client.finishedBig == 1 && !client.finishedPath.empty() &&
|
||||
client.finishedMime.find("octet-stream") != std::string::npos &&
|
||||
client.finishedSize == expectSize;
|
||||
bool cancelOK = client.startSlow >= 1 && client.progressSlow >= 1 &&
|
||||
client.cancelSent && client.finishedSlow == 0 &&
|
||||
client.errorSlow >= 1;
|
||||
|
||||
printf("[dlc] big: start=%d progress=%d finished=%d mime='%s' size=%ld (expect %ld)\n",
|
||||
client.startBig, client.progressBig, client.finishedBig,
|
||||
client.finishedMime.c_str(), client.finishedSize, expectSize);
|
||||
printf("[dlc] slow: start=%d progress=%d cancelSent=%d finished=%d error=%d\n",
|
||||
client.startSlow, client.progressSlow, (int)client.cancelSent,
|
||||
client.finishedSlow, client.errorSlow);
|
||||
printf("[dlc] startOK=%d progressOK=%d finishOK=%d cancelOK=%d\n",
|
||||
startOK, progressOK, finishOK, cancelOK);
|
||||
|
||||
bool ok = startOK && progressOK && finishOK && cancelOK;
|
||||
printf("[dlc] %s\n", ok ? "DOWNLOAD-LIFECYCLE PASS" : "DOWNLOAD-LIFECYCLE FAIL");
|
||||
return ok ? 0 : 2;
|
||||
}
|
||||
@@ -67,12 +67,25 @@ public:
|
||||
(void)focused; (void)fieldType; (void)fieldActions;
|
||||
}
|
||||
// A navigation resolved to a non-displayable resource (download / MIME handoff).
|
||||
// Drives BrowserServerBase::msgMimeHandoffUrl (YAP 0x2014) -> adapter
|
||||
// Drives BrowserServerBase::msgMimeHandoffUrl (YAP 0x2015) -> adapter
|
||||
// mimeHandoffUrl -> BasicWebView.doFileLoad -> app handleResource, which routes
|
||||
// it to com.palm.downloadmanager. (mimeType may be empty if unknown.)
|
||||
virtual void msgMimeHandoffUrl(const char* mimeType, const char* url) {
|
||||
(void)mimeType; (void)url;
|
||||
}
|
||||
// Lifecycle of a download the ENGINE performs to a temp file (YAP 0x2010..0x2013).
|
||||
// Exactly one start, then >=0 progress, terminated by one finished OR one error.
|
||||
virtual void msgDownloadStart(const char* url) { (void)url; }
|
||||
virtual void msgDownloadProgress(const char* url, int32_t soFar, int32_t total) {
|
||||
(void)url; (void)soFar; (void)total;
|
||||
}
|
||||
virtual void msgDownloadFinished(const char* url, const char* mimeType,
|
||||
const char* tmpFilePath) {
|
||||
(void)url; (void)mimeType; (void)tmpFilePath;
|
||||
}
|
||||
virtual void msgDownloadError(const char* url, const char* errorMsg) {
|
||||
(void)url; (void)errorMsg;
|
||||
}
|
||||
};
|
||||
|
||||
class BrowserPageGoanna
|
||||
@@ -89,6 +102,15 @@ public:
|
||||
// Emit a download/MIME handoff for this page (from the process-wide download
|
||||
// interceptor, routed to the active page). See IPageMessageSink::msgMimeHandoffUrl.
|
||||
void emitMimeHandoff(const char* mimeType, const char* url) { mSink.msgMimeHandoffUrl(mimeType, url); }
|
||||
// Same routing for the engine-performed download lifecycle (YAP msgDownload*).
|
||||
void emitDownloadStart(const char* url) { mSink.msgDownloadStart(url); }
|
||||
void emitDownloadProgress(const char* url, int32_t soFar, int32_t total) {
|
||||
mSink.msgDownloadProgress(url, soFar, total);
|
||||
}
|
||||
void emitDownloadFinished(const char* url, const char* mime, const char* path) {
|
||||
mSink.msgDownloadFinished(url, mime, path);
|
||||
}
|
||||
void emitDownloadError(const char* url, const char* msg) { mSink.msgDownloadError(url, msg); }
|
||||
void returnBuffer(int sharedBufferKey); // YAP: returnBuffer (adapter freed it)
|
||||
void freeze(); // YAP: freeze (card backgrounded — pause paint)
|
||||
void thaw(int key1, int key2, int size); // YAP: thaw (reattach buffers, resume)
|
||||
|
||||
@@ -13,9 +13,21 @@
|
||||
#include "nsIExternalHelperAppService.h" // nsIHelperAppLauncher
|
||||
#include "nsIMIMEInfo.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsIFileURL.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsICancelable.h"
|
||||
#include "nsITransfer.h" // the embedding download-report hook
|
||||
#include "nsIWebProgressListener.h" // STATE_STOP
|
||||
#include "nsIComponentRegistrar.h"
|
||||
#include "nsISupportsImpl.h" // NS_IMPL_ISUPPORTS
|
||||
#include "nsDirectoryServiceDefs.h" // NS_OS_TEMP_DIR
|
||||
#include "nsDirectoryServiceUtils.h" // NS_GetSpecialDirectory
|
||||
#include "nsXPCOM.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/RefCountType.h"
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
namespace jihad {
|
||||
|
||||
@@ -28,6 +40,195 @@ static DownloadSink* gSink = nullptr;
|
||||
static const nsCID kJihadHelperDialogCID = JIHAD_HELPERDIALOG_CID;
|
||||
static const char* kHelperDialogContract = "@mozilla.org/helperapplauncherdialog;1";
|
||||
|
||||
// {b7c8d9ea-1f20-4a31-8b42-5c6d7e8f9a0b}
|
||||
#define JIHAD_TRANSFER_CID \
|
||||
{ 0xb7c8d9ea, 0x1f20, 0x4a31, \
|
||||
{ 0x8b, 0x42, 0x5c, 0x6d, 0x7e, 0x8f, 0x9a, 0x0b } }
|
||||
static const nsCID kJihadTransferCID = JIHAD_TRANSFER_CID;
|
||||
// From nsITransfer.idl (NS_TRANSFER_CONTRACTID); spelled out so this TU does not
|
||||
// depend on the %{C++ block's macro being exported.
|
||||
static const char* kTransferContract = "@mozilla.org/transfer;1";
|
||||
|
||||
// ── the download the engine is performing ───────────────────────────────────
|
||||
// nsExternalAppHandler creates ONE of these per download (via the
|
||||
// "@mozilla.org/transfer;1" contract) and drives it with the standard
|
||||
// nsIWebProgressListener2 notifications. That is the whole lifecycle:
|
||||
// Init -> msgDownloadStart
|
||||
// OnProgressChange64 -> msgDownloadProgress
|
||||
// OnStateChange(STATE_STOP) -> msgDownloadFinished | msgDownloadError
|
||||
// Init also hands us the nsICancelable, which is what makes the YAP
|
||||
// cancelDownload command implementable (CancelDownload below).
|
||||
class JihadTransfer final : public nsITransfer {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIWEBPROGRESSLISTENER
|
||||
NS_DECL_NSIWEBPROGRESSLISTENER2
|
||||
NS_DECL_NSITRANSFER
|
||||
|
||||
JihadTransfer() : mDone(false) {}
|
||||
|
||||
const nsCString& Url() const { return mUrl; }
|
||||
bool Done() const { return mDone; }
|
||||
// Abort this download. Cancelling the helper-app handler finishes its file
|
||||
// saver with the abort status, which comes back as OnStateChange(STATE_STOP,
|
||||
// <failure>) -> exactly one msgDownloadError and never a Finished. Safe to
|
||||
// call re-entrantly (Finish is idempotent).
|
||||
void Abort() {
|
||||
if (mDone) return;
|
||||
nsCOMPtr<nsICancelable> c = mCancelable;
|
||||
if (c) c->Cancel(NS_BINDING_ABORTED);
|
||||
// Nothing to cancel through: terminate the lifecycle ourselves so a client
|
||||
// that asked for a cancel always gets a terminal message.
|
||||
if (!c) Finish(NS_BINDING_ABORTED);
|
||||
}
|
||||
|
||||
private:
|
||||
~JihadTransfer() {}
|
||||
void Finish(nsresult aStatus);
|
||||
// Rename the "<target>.part" work file the helper-app service writes into over
|
||||
// the final target. Firefox does this move in its own nsITransfer (Download.jsm);
|
||||
// there is no other owner for it, so an embedder that supplies the contract must.
|
||||
void FinalizePartFile();
|
||||
|
||||
nsCString mUrl;
|
||||
nsCString mMime;
|
||||
nsCOMPtr<nsIFile> mTarget; // where the finished file must end up
|
||||
nsCOMPtr<nsIFile> mTempFile; // "<target>.part" while the download runs
|
||||
nsCOMPtr<nsICancelable> mCancelable;
|
||||
bool mDone;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(JihadTransfer, nsITransfer, nsIWebProgressListener,
|
||||
nsIWebProgressListener2)
|
||||
|
||||
// Active (not yet finished) downloads, in start order. Strong refs: the entry is
|
||||
// dropped in Finish(), so an in-flight download always has a live cancelable.
|
||||
static std::vector<RefPtr<JihadTransfer>>* gActive = nullptr;
|
||||
|
||||
static void ActiveAdd(JihadTransfer* t) {
|
||||
if (!gActive) gActive = new std::vector<RefPtr<JihadTransfer>>();
|
||||
gActive->push_back(t);
|
||||
}
|
||||
static void ActiveRemove(JihadTransfer* t) {
|
||||
if (!gActive) return;
|
||||
for (size_t i = 0; i < gActive->size(); ++i) {
|
||||
if ((*gActive)[i].get() == t) { gActive->erase(gActive->begin() + i); return; }
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
JihadTransfer::Init(nsIURI* aSource, nsIURI* aTarget, const nsAString& /*aDisplayName*/,
|
||||
nsIMIMEInfo* aMIMEInfo, PRTime /*aStartTime*/, nsIFile* aTempFile,
|
||||
nsICancelable* aCancelable, bool /*aIsPrivate*/) {
|
||||
if (aSource) aSource->GetSpec(mUrl);
|
||||
if (aMIMEInfo) aMIMEInfo->GetMIMEType(mMime);
|
||||
mTempFile = aTempFile;
|
||||
mCancelable = aCancelable;
|
||||
// aTarget is a file:// URI for the destination we picked in the dialog.
|
||||
nsCOMPtr<nsIFileURL> furl = do_QueryInterface(aTarget);
|
||||
if (furl) furl->GetFile(getter_AddRefs(mTarget));
|
||||
|
||||
ActiveAdd(this);
|
||||
printf("[jihad-dl] start %s (mime=%s)\n", mUrl.get(), mMime.get());
|
||||
if (gSink) gSink->OnDownloadStart(mUrl.get());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP JihadTransfer::SetSha256Hash(const nsACString&) { return NS_OK; }
|
||||
NS_IMETHODIMP JihadTransfer::SetSignatureInfo(nsIArray*) { return NS_OK; }
|
||||
NS_IMETHODIMP JihadTransfer::SetRedirects(nsIArray*) { return NS_OK; }
|
||||
|
||||
NS_IMETHODIMP
|
||||
JihadTransfer::OnProgressChange64(nsIWebProgress*, nsIRequest*,
|
||||
int64_t, int64_t,
|
||||
int64_t aCurTotalProgress,
|
||||
int64_t aMaxTotalProgress) {
|
||||
if (mDone) return NS_OK;
|
||||
if (gSink) gSink->OnDownloadProgress(mUrl.get(), aCurTotalProgress, aMaxTotalProgress);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
JihadTransfer::OnStateChange(nsIWebProgress*, nsIRequest*, uint32_t aStateFlags,
|
||||
nsresult aStatus) {
|
||||
if (aStateFlags & nsIWebProgressListener::STATE_STOP) Finish(aStatus);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Unused halves of the listener surface.
|
||||
NS_IMETHODIMP JihadTransfer::OnProgressChange(nsIWebProgress*, nsIRequest*, int32_t,
|
||||
int32_t, int32_t, int32_t) { return NS_OK; }
|
||||
NS_IMETHODIMP JihadTransfer::OnLocationChange(nsIWebProgress*, nsIRequest*, nsIURI*,
|
||||
uint32_t) { return NS_OK; }
|
||||
NS_IMETHODIMP JihadTransfer::OnStatusChange(nsIWebProgress*, nsIRequest*, nsresult,
|
||||
const char16_t*) { return NS_OK; }
|
||||
NS_IMETHODIMP JihadTransfer::OnSecurityChange(nsIWebProgress*, nsIRequest*,
|
||||
uint32_t) { return NS_OK; }
|
||||
NS_IMETHODIMP JihadTransfer::OnRefreshAttempted(nsIWebProgress*, nsIURI*, int32_t,
|
||||
bool, bool* _retval) {
|
||||
if (_retval) *_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void JihadTransfer::FinalizePartFile() {
|
||||
if (!mTempFile || !mTarget) return;
|
||||
bool same = false;
|
||||
if (NS_SUCCEEDED(mTempFile->Equals(mTarget, &same)) && same) return;
|
||||
bool exists = false;
|
||||
if (NS_FAILED(mTempFile->Exists(&exists)) || !exists) return;
|
||||
nsAutoString leaf;
|
||||
if (NS_FAILED(mTarget->GetLeafName(leaf))) return;
|
||||
// Same directory (the service derived the .part name from our target), so a
|
||||
// leaf-name move is an atomic rename over the placeholder we created.
|
||||
mTempFile->MoveTo(nullptr, leaf);
|
||||
}
|
||||
|
||||
void JihadTransfer::Finish(nsresult aStatus) {
|
||||
if (mDone) return;
|
||||
mDone = true;
|
||||
// Keep ourselves alive across the sink callback + registry drop.
|
||||
RefPtr<JihadTransfer> kungFuDeathGrip(this);
|
||||
ActiveRemove(this);
|
||||
mCancelable = nullptr;
|
||||
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
FinalizePartFile();
|
||||
nsAutoCString path;
|
||||
if (mTarget) mTarget->GetNativePath(path);
|
||||
printf("[jihad-dl] finished %s -> %s\n", mUrl.get(), path.get());
|
||||
if (gSink) gSink->OnDownloadFinished(mUrl.get(), mMime.get(), path.get());
|
||||
} else {
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof msg, "0x%08x", (unsigned)static_cast<uint32_t>(aStatus));
|
||||
printf("[jihad-dl] error %s (%s)\n", mUrl.get(), msg);
|
||||
if (gSink) gSink->OnDownloadError(mUrl.get(), msg);
|
||||
// A cancelled/failed download leaves two files behind: the "<target>.part"
|
||||
// the saver was writing, and the empty target we reserved with CreateUnique.
|
||||
// Both are ours; drop them so a cancel doesn't litter the download dir.
|
||||
if (mTempFile) mTempFile->Remove(false);
|
||||
if (mTarget) {
|
||||
int64_t sz = -1;
|
||||
if (NS_SUCCEEDED(mTarget->GetFileSize(&sz)) && sz == 0) mTarget->Remove(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CancelDownload(const char* url) {
|
||||
if (!gActive || gActive->empty()) return false;
|
||||
// Snapshot: Abort() runs the whole termination path synchronously and mutates
|
||||
// gActive (Codex-style re-entrancy guard).
|
||||
std::vector<RefPtr<JihadTransfer>> snap(*gActive);
|
||||
bool any = false;
|
||||
for (size_t i = 0; i < snap.size(); ++i) {
|
||||
if (snap[i]->Done()) continue;
|
||||
if (url && *url && !snap[i]->Url().Equals(nsDependentCString(url))) continue;
|
||||
snap[i]->Abort();
|
||||
any = true;
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
// ── the helper-app dialog override ──────────────────────────────────────────
|
||||
class JihadHelperDialog final : public nsIFactory,
|
||||
public nsIHelperAppLauncherDialog {
|
||||
public:
|
||||
@@ -40,6 +241,10 @@ class JihadHelperDialog final : public nsIFactory,
|
||||
|
||||
private:
|
||||
static void Report(nsIHelperAppLauncher* aLauncher);
|
||||
// Pick a temp destination and tell the launcher to save there. viaPrompt
|
||||
// selects the callback the service is waiting on (saveDestinationAvailable
|
||||
// for promptForSaveToFileAsync, saveToDisk for show).
|
||||
static void BeginSave(nsIHelperAppLauncher* aLauncher, bool viaPrompt);
|
||||
};
|
||||
|
||||
void JihadHelperDialog::Report(nsIHelperAppLauncher* aLauncher) {
|
||||
@@ -58,6 +263,52 @@ void JihadHelperDialog::Report(nsIHelperAppLauncher* aLauncher) {
|
||||
NS_ConvertUTF16toUTF8(name).get(), len);
|
||||
}
|
||||
|
||||
// Destination directory for engine downloads: $JIHAD_DOWNLOAD_DIR, else the
|
||||
// engine temp dir. On the device the launcher points this at the media
|
||||
// partition so a finished file survives for the app to hand to the user.
|
||||
static already_AddRefed<nsIFile> jihadDownloadDir() {
|
||||
nsCOMPtr<nsIFile> dir;
|
||||
const char* env = getenv("JIHAD_DOWNLOAD_DIR");
|
||||
if (env && *env) {
|
||||
if (NS_SUCCEEDED(NS_NewNativeLocalFile(nsDependentCString(env), true,
|
||||
getter_AddRefs(dir))) && dir) {
|
||||
nsresult crv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
||||
if (NS_FAILED(crv) && crv != NS_ERROR_FILE_ALREADY_EXISTS) dir = nullptr;
|
||||
}
|
||||
}
|
||||
if (!dir) NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
|
||||
return dir.forget();
|
||||
}
|
||||
|
||||
void JihadHelperDialog::BeginSave(nsIHelperAppLauncher* aLauncher, bool viaPrompt) {
|
||||
if (!aLauncher) return;
|
||||
nsCOMPtr<nsIFile> dir = jihadDownloadDir();
|
||||
nsCOMPtr<nsIFile> file;
|
||||
if (dir) dir->Clone(getter_AddRefs(file));
|
||||
if (file) {
|
||||
nsString name;
|
||||
aLauncher->GetSuggestedFileName(name);
|
||||
// The service already stripped path separators + illegal characters from
|
||||
// the suggested name; guard against an empty one only.
|
||||
if (name.IsEmpty()) name.AssignLiteral("jihad-download");
|
||||
if (NS_FAILED(file->Append(name)) ||
|
||||
NS_FAILED(file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600))) {
|
||||
file = nullptr;
|
||||
}
|
||||
}
|
||||
if (!file) {
|
||||
// No writable destination: terminate the launcher rather than leave it (and
|
||||
// its temp file) dangling. msgMimeHandoffUrl already went out, so the client
|
||||
// can still fetch the URL itself.
|
||||
fprintf(stderr, "[jihad-dl] no writable download dir — aborting engine save\n");
|
||||
if (viaPrompt) aLauncher->SaveDestinationAvailable(nullptr);
|
||||
else aLauncher->Cancel(NS_BINDING_ABORTED);
|
||||
return;
|
||||
}
|
||||
if (viaPrompt) aLauncher->SaveDestinationAvailable(file);
|
||||
else aLauncher->SaveToDisk(file, false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
JihadHelperDialog::QueryInterface(const nsIID& aIID, void** aResult) {
|
||||
if (!aResult) return NS_ERROR_NULL_POINTER;
|
||||
@@ -82,16 +333,12 @@ JihadHelperDialog::CreateInstance(nsISupports* aOuter, const nsIID& aIID, void**
|
||||
}
|
||||
NS_IMETHODIMP JihadHelperDialog::LockFactory(bool) { return NS_OK; }
|
||||
|
||||
// nsIHelperAppLauncherDialog — capture the handoff instead of opening a window.
|
||||
// NOTE (Codex P1): this UXP's nsIHelperAppLauncher has no Cancel(); returning
|
||||
// NS_OK without SaveToDisk leaves the launcher unresolved (the engine may leave a
|
||||
// temp file). Actually saving/cancelling requires the adapter to choose a
|
||||
// destination over the YAP contract (msgDownload* + a reply) -- device/adapter
|
||||
// work. Here we only surface the handoff so the daemon never blocks on a chrome
|
||||
// save dialog.
|
||||
// nsIHelperAppLauncherDialog — capture the handoff instead of opening a window,
|
||||
// then drive the save so the download actually completes and is reported.
|
||||
NS_IMETHODIMP
|
||||
JihadHelperDialog::Show(nsIHelperAppLauncher* aLauncher, nsISupports*, uint32_t) {
|
||||
Report(aLauncher);
|
||||
BeginSave(aLauncher, /* viaPrompt */ false);
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHODIMP
|
||||
@@ -99,11 +346,38 @@ JihadHelperDialog::PromptForSaveToFileAsync(nsIHelperAppLauncher* aLauncher,
|
||||
nsISupports*, const char16_t*,
|
||||
const char16_t*, bool) {
|
||||
Report(aLauncher);
|
||||
BeginSave(aLauncher, /* viaPrompt */ true);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static JihadHelperDialog& Singleton() { static JihadHelperDialog s; return s; }
|
||||
|
||||
// Factory for the per-download transfer objects (unlike the dialog, each
|
||||
// CreateInstance MUST hand back a fresh, really-refcounted object).
|
||||
class JihadTransferFactory final : public nsIFactory {
|
||||
public:
|
||||
NS_IMETHOD QueryInterface(const nsIID& aIID, void** aResult) override {
|
||||
if (!aResult) return NS_ERROR_NULL_POINTER;
|
||||
if (aIID.Equals(NS_GET_IID(nsISupports)) || aIID.Equals(NS_GET_IID(nsIFactory))) {
|
||||
*aResult = static_cast<nsIFactory*>(this);
|
||||
AddRef();
|
||||
return NS_OK;
|
||||
}
|
||||
*aResult = nullptr;
|
||||
return NS_NOINTERFACE;
|
||||
}
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return 2; }
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return 1; }
|
||||
|
||||
NS_IMETHOD CreateInstance(nsISupports* aOuter, const nsIID& aIID, void** aResult) override {
|
||||
if (aOuter) return NS_ERROR_NO_AGGREGATION;
|
||||
RefPtr<JihadTransfer> t = new JihadTransfer();
|
||||
return t->QueryInterface(aIID, aResult);
|
||||
}
|
||||
NS_IMETHOD LockFactory(bool) override { return NS_OK; }
|
||||
};
|
||||
static JihadTransferFactory& TransferFactory() { static JihadTransferFactory f; return f; }
|
||||
|
||||
bool InstallDownloadService() {
|
||||
static bool installed = false;
|
||||
if (installed) return true;
|
||||
@@ -111,7 +385,11 @@ bool InstallDownloadService() {
|
||||
if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(reg))) || !reg) return false;
|
||||
nsresult rv = reg->RegisterFactory(kJihadHelperDialogCID, "Jihad Helper Dialog",
|
||||
kHelperDialogContract, &Singleton());
|
||||
installed = NS_SUCCEEDED(rv);
|
||||
// Without this second registration the helper-app service cannot create an
|
||||
// nsITransfer and CANCELS every download (CreateTransfer failure => Cancel).
|
||||
nsresult rv2 = reg->RegisterFactory(kJihadTransferCID, "Jihad Transfer",
|
||||
kTransferContract, &TransferFactory());
|
||||
installed = NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2);
|
||||
return installed;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,19 @@
|
||||
* dialog opens a chrome save/open window -- meaningless in the headless daemon.
|
||||
* We override that contract so every download/handoff is reported to a sink,
|
||||
* which maps to the YAP msgDownload* / msgMimeNotSupported surface.
|
||||
*
|
||||
* Two things happen for one captured download:
|
||||
* 1. OnDownload -> msgMimeHandoffUrl (YAP 0x2015): the isis app's existing
|
||||
* path, which hands the URL to com.palm.downloadmanager.
|
||||
* 2. the engine SAVES the response to a temp file and reports the lifecycle
|
||||
* (OnDownloadStart / OnDownloadProgress / OnDownloadFinished |
|
||||
* OnDownloadError) -> msgDownloadStart/Progress/Finished/Error
|
||||
* (YAP 0x2010..0x2013), the same messages the stock isis BrowserServer
|
||||
* emitted from its WebKit download callbacks.
|
||||
* (2) costs no extra network traffic: nsExternalAppHandler has already opened
|
||||
* the channel and is streaming the body into its own temp file by the time the
|
||||
* dialog is consulted. Driving saveToDisk is what makes that download RESOLVE
|
||||
* (and stops the temp file being orphaned) instead of hanging unreferenced.
|
||||
*/
|
||||
#ifndef JIHAD_DOWNLOAD_SERVICE_H
|
||||
#define JIHAD_DOWNLOAD_SERVICE_H
|
||||
@@ -20,17 +33,40 @@
|
||||
namespace jihad {
|
||||
|
||||
// Process-wide receiver of download/handoff requests. Not owned by the service.
|
||||
// Every call runs on the embedding (main) thread.
|
||||
class DownloadSink {
|
||||
public:
|
||||
virtual ~DownloadSink() {}
|
||||
// Called when content is handed off as a download. All strings are UTF-8;
|
||||
// contentLength is -1 when unknown.
|
||||
// contentLength is -1 when unknown. Drives msgMimeHandoffUrl.
|
||||
virtual void OnDownload(const char* url, const char* mimeType,
|
||||
const char* suggestedName, int64_t contentLength) = 0;
|
||||
|
||||
// --- engine-performed download lifecycle (YAP msgDownload*) ---------------
|
||||
// Defaulted to no-ops so existing sinks (tests) need not override them.
|
||||
// Exactly one Start is emitted per download, followed by zero or more
|
||||
// Progress, terminated by exactly one Finished OR one Error.
|
||||
virtual void OnDownloadStart(const char* url) { (void)url; }
|
||||
// bytesSoFar/totalBytes are the raw 64-bit counts; totalBytes is -1 when the
|
||||
// response had no Content-Length.
|
||||
virtual void OnDownloadProgress(const char* url, int64_t bytesSoFar,
|
||||
int64_t totalBytes) {
|
||||
(void)url; (void)bytesSoFar; (void)totalBytes;
|
||||
}
|
||||
// tmpFilePath is the on-disk path of the completed file (see JIHAD_DOWNLOAD_DIR).
|
||||
virtual void OnDownloadFinished(const char* url, const char* mimeType,
|
||||
const char* tmpFilePath) {
|
||||
(void)url; (void)mimeType; (void)tmpFilePath;
|
||||
}
|
||||
virtual void OnDownloadError(const char* url, const char* errorMsg) {
|
||||
(void)url; (void)errorMsg;
|
||||
}
|
||||
};
|
||||
|
||||
// Install the override of "@mozilla.org/helperapplauncherdialog;1". Call once
|
||||
// after the engine is initialised. Idempotent; returns false on failure.
|
||||
// Install the override of "@mozilla.org/helperapplauncherdialog;1" and of
|
||||
// "@mozilla.org/transfer;1" (the embedding hook the helper-app service uses to
|
||||
// report download progress). Call once after the engine is initialised.
|
||||
// Idempotent; returns false on failure.
|
||||
bool InstallDownloadService();
|
||||
|
||||
// Set (or clear, with nullptr) the process-wide sink. Not owned. Threading/
|
||||
@@ -39,6 +75,12 @@ bool InstallDownloadService();
|
||||
// it as a process-lifetime backstop.
|
||||
void SetDownloadSink(DownloadSink* sink);
|
||||
|
||||
// YAP: cancelDownload(url) — abort an in-flight engine download. Matches the
|
||||
// source URL emitted by OnDownloadStart; an empty/null url cancels every active
|
||||
// download. Returns true if at least one download was aborted. The aborted
|
||||
// download reports OnDownloadError (never OnDownloadFinished).
|
||||
bool CancelDownload(const char* url);
|
||||
|
||||
} // namespace jihad
|
||||
|
||||
#endif // JIHAD_DOWNLOAD_SERVICE_H
|
||||
@@ -173,9 +173,16 @@ public:
|
||||
NS_IMETHOD GetFile(const char* aProp, bool* aPersistent, nsIFile** aResult) override
|
||||
{
|
||||
*aPersistent = true;
|
||||
// The four profile keys nsXULAppAPI.h documents for embedders that pick the
|
||||
// profile BEFORE XRE_InitEmbedding. "ProfDS"/"ProfLDS" are the *startup*
|
||||
// aliases nsXREDirProvider falls back to for consumers that run before a
|
||||
// profile is "selected" — the startup cache is one (StartupCache::Init asks
|
||||
// for ProfLDS), so omitting them left it homeless.
|
||||
if (mProfile &&
|
||||
(!strcmp(aProp, "ProfD") || // NS_APP_USER_PROFILE_50_DIR
|
||||
!strcmp(aProp, "ProfLD"))) { // NS_APP_USER_PROFILE_LOCAL_50_DIR
|
||||
(!strcmp(aProp, "ProfD") || // NS_APP_USER_PROFILE_50_DIR
|
||||
!strcmp(aProp, "ProfLD") || // NS_APP_USER_PROFILE_LOCAL_50_DIR
|
||||
!strcmp(aProp, "ProfDS") || // NS_APP_PROFILE_DIR_STARTUP
|
||||
!strcmp(aProp, "ProfLDS"))) { // NS_APP_PROFILE_LOCAL_DIR_STARTUP
|
||||
nsCOMPtr<nsIFile> f;
|
||||
if (NS_SUCCEEDED(mProfile->Clone(getter_AddRefs(f))) && f) {
|
||||
f.forget(aResult);
|
||||
@@ -258,6 +265,24 @@ EngineHost::Init(const char* greDir)
|
||||
if (mInited) {
|
||||
InstallDialogService();
|
||||
InstallDownloadService();
|
||||
// ── announce the profile ────────────────────────────────────────────────
|
||||
// XRE_InitEmbedding2 takes the directory provider but does NOT arm it:
|
||||
// nsXREDirProvider::GetFile short-circuits EVERY profile key with
|
||||
// if (!mProfileNotified) return NS_ERROR_FAILURE;
|
||||
// BEFORE it ever consults the embedder's provider, and mProfileNotified is
|
||||
// only set by XRE_NotifyProfile()/DoStartup(). So without this call
|
||||
// NS_GetSpecialDirectory("ProfD") fails process-wide, and every consumer
|
||||
// that keys off it silently degrades to memory-only — nsCookieService takes
|
||||
// its "couldn't get cookie file" branch in InitDBStates(), which is exactly
|
||||
// why cookies.sqlite was never created and cookies died with the daemon
|
||||
// (browser-services R2). nsXULAppAPI.h documents this call as mandatory for
|
||||
// scenario 1 (profile chosen before XRE_InitEmbedding): "XRE_NotifyProfile
|
||||
// should be called immediately after XRE_InitEmbedding".
|
||||
// It also fires profile-do-change / profile-after-change, the notifications
|
||||
// profile-scoped services wait on. Called BEFORE the pref block below
|
||||
// because DoStartup() runs Preferences::ResetAndReadUserPrefs(), which would
|
||||
// otherwise discard the runtime prefs we set here.
|
||||
if (sJihadDirProvider) XRE_NotifyProfile();
|
||||
// Mobile-browser defaults. <meta name=viewport> is off by default on desktop
|
||||
// Goanna; a webOS phone browser must honor it (drives msgMetaViewportSet).
|
||||
nsCOMPtr<nsIPrefBranch> pb =
|
||||
|
||||
@@ -20,7 +20,7 @@ internals; the YAP interface and shared-buffer plumbing are unchanged.
|
||||
| Selection/clipboard | `nsISelectionController`, `nsIClipboard`, `nsICommandManager` (`cmd_copy/cut/paste/selectAll`) | copy/cut/paste/selectAll, caret bounds via selection range rects. |
|
||||
| Cookies / cache | `nsICookieManager2`, `nsICacheStorageService` | `clearCookies`, `clearCache`, `setAcceptCookies`. |
|
||||
| User agent | `nsIHttpProtocolHandler` userAgent override / pref `general.useragent.override` | `setUserAgent`. |
|
||||
| Downloads | `nsIWebBrowserPersist` / `nsIExternalHelperAppService` / `nsIHelperAppLauncherDialog` | download start/progress/error/finished, MIME not-supported handoff. |
|
||||
| Downloads | `nsIHelperAppLauncherDialog` override (capture the handoff + drive `saveToDisk`) **and** an `@mozilla.org/transfer;1` (`nsITransfer`) implementation | `render/goanna/DownloadService.cpp`. The dialog override → `msgMimeHandoffUrl`; the transfer's `init`/`onProgressChange64`/`onStateChange(STATE_STOP)` → `msgDownloadStart/Progress/Finished/Error`; the `nsICancelable` handed to `init` is what `cancelDownload` aborts. Registering the transfer contract is mandatory: without it `nsExternalAppHandler::CreateTransfer` fails and cancels every download. |
|
||||
| TLS/cert prompts | `nsICertOverrideService`, `nsIBadCertListener2` | `msgDialogSSLConfirm` (bridge to webOS CertificateMgr as the QtWebKit path did). |
|
||||
| JS dialogs | `nsIPromptService` / `nsIPrompt` override | `msgDialogAlert/Confirm/Prompt/UserPassword`. |
|
||||
| Network interface / DNS | proxy + `nsIDNSService` / interface binding via socket transport | `setNetworkInterface`, `setDNSServers` (may need engine patch for bind-to-iface). |
|
||||
@@ -44,6 +44,7 @@ internals; the YAP interface and shared-buffer plumbing are unchanged.
|
||||
| `clearCache/clearCookies` | cache storage clear / cookie manager `removeAll`. |
|
||||
| `setEnableJavaScript/setBlockPopups/setAcceptCookies/setMinFontSize/setUserAgent` | set corresponding `nsIPrefBranch` prefs / per-docshell `nsIDocShell.allowJavascript`. |
|
||||
| hit-test family | `nsIDOMWindowUtils.elementFromPoint` + element geometry → matching `msg…Response`. |
|
||||
| `cancelDownload(url)` | `jihad::CancelDownload` → `nsICancelable.cancel(NS_BINDING_ABORTED)` on the matching in-flight transfer (process-wide, not per page). |
|
||||
|
||||
## Paint loop (the hard part)
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Jihad Browser — cookie PERSISTENCE test (domain G / R2).
|
||||
*
|
||||
* Run twice against the same $JIHAD_PROFILE_DIR (that second run IS the
|
||||
* "daemon restart"):
|
||||
* set — load /set (Set-Cookie with a future Max-Age, i.e. a PERSISTENT
|
||||
* cookie; session cookies are not supposed to survive), then /echo to
|
||||
* prove the cookie is live in-session.
|
||||
* check — load /echo only. The cookie can only come back if the engine read
|
||||
* it from cookies.sqlite in the profile.
|
||||
* Both modes report whether cookies.sqlite exists after a clean shutdown.
|
||||
*/
|
||||
#include <gtk/gtk.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include "../GoannaRenderPage.h"
|
||||
#include "../EngineHost.h"
|
||||
|
||||
static const char* kCookie = "jihadpersist=1";
|
||||
|
||||
static bool fileExists(const std::string& p) {
|
||||
struct stat st;
|
||||
return stat(p.c_str(), &st) == 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
const char* greDir = (argc > 1) ? argv[1] : ".";
|
||||
const char* mode = (argc > 2) ? argv[2] : "set";
|
||||
const char* base = (argc > 3) ? argv[3] : "http://127.0.0.1:8138";
|
||||
const bool setMode = strcmp(mode, "set") == 0;
|
||||
|
||||
gtk_init(&argc, &argv);
|
||||
jihad::EngineHost host;
|
||||
if (!host.Init(greDir)) { fprintf(stderr, "[cookie] engine init FAIL\n"); return 1; }
|
||||
printf("[cookie] engine up (mode=%s)\n", mode);
|
||||
|
||||
bool ok = false;
|
||||
{
|
||||
jihad::GoannaRenderPage page(host);
|
||||
if (!page.Create(640, 480)) { host.Shutdown(); return 2; }
|
||||
if (setMode) {
|
||||
page.LoadUrlAndWait((std::string(base) + "/set").c_str(), 25);
|
||||
page.PumpFor(1500);
|
||||
}
|
||||
page.LoadUrlAndWait((std::string(base) + "/echo").c_str(), 25);
|
||||
page.PumpFor(1500);
|
||||
std::string title = page.GetTitle();
|
||||
printf("[cookie] %s-run echo title='%s'\n", mode, title.c_str());
|
||||
ok = title.find(kCookie) != std::string::npos;
|
||||
}
|
||||
host.Shutdown(); // clean teardown: this is what flushes/closes cookies.sqlite
|
||||
|
||||
const char* prof = getenv("JIHAD_PROFILE_DIR");
|
||||
std::string db = std::string(prof ? prof : "") + "/cookies.sqlite";
|
||||
bool dbOK = prof && *prof && fileExists(db);
|
||||
printf("[cookie] cookies.sqlite (%s): %s\n", db.c_str(), dbOK ? "PRESENT" : "MISSING");
|
||||
|
||||
bool pass = ok && dbOK;
|
||||
printf("[cookie] %s %s\n", setMode ? "COOKIE-SET" : "COOKIE-PERSIST",
|
||||
pass ? "PASS" : "FAIL");
|
||||
return pass ? 0 : 4;
|
||||
}
|
||||
Reference in new issue
Block a user