aboutsummaryrefslogtreecommitdiff
dnl -*- Autoconf -*- fragment for the C++ daemon.

AC_MSG_CHECKING([whether to build daemon])
AC_MSG_RESULT([$guix_build_daemon])

dnl C++ environment.  This macro must be used unconditionnaly.
AC_PROG_CXX
AM_PROG_AR
AC_LANG([C++])

if test "x$guix_build_daemon" = "xyes"; then

  GUIX_ASSERT_CXX11

  AC_PROG_RANLIB
  AC_CONFIG_HEADERS([nix/config.h])

  dnl Use 64-bit file system calls so that we can support files > 2 GiB.
  AC_SYS_LARGEFILE

  dnl Look for zlib, a required dependency.
  AC_CHECK_LIB([z], [gzdopen], [true],
    [AC_MSG_ERROR([Guix requires zlib.  See http://www.zlib.net/.])])
  AC_CHECK_HEADERS([zlib.h], [true],
    [AC_MSG_ERROR([Guix requires zlib.  See http://www.zlib.net/.])])

  dnl Look for libbz2, an optional dependency.
  AC_CHECK_LIB([bz2], [BZ2_bzWriteOpen], [HAVE_LIBBZ2=yes], [HAVE_LIBBZ2=no])
  if test "x$HAVE_LIBBZ2" = xyes; then
    AC_CHECK_HEADERS([bzlib.h])
    HAVE_LIBBZ2="$ac_cv_header_bzlib_h"
  fi

  dnl Look for SQLite, a required dependency.
  PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= 3.6.19])

  AC_DEFINE_UNQUOTED([SYSTEM], ["$guix_system"],
    [Guix host system type--i.e., platform and OS kernel tuple.])

  case "$LIBGCRYPT_PREFIX" in
    no)
      LIBGCRYPT_CFLAGS=""
      ;;
    *)
      LIBGCRYPT_CFLAGS="-I$LIBGCRYPT_PREFIX/include"
      ;;
  esac

  case "$LIBGCRYPT_LIBDIR" in
    no | "")
      LIBGCRYPT_LIBS="-lgcrypt"
      ;;
    *)
      LIBGCRYPT_LIBS="-L$LIBGCRYPT_LIBDIR -lgcrypt"
      ;;
  esac

  AC_SUBST([LIBGCRYPT_CFLAGS])
  AC_SUBST([LIBGCRYPT_LIBS])

  save_CFLAGS="$CFLAGS"
  save_LDFLAGS="$LDFLAGS"
  CFLAGS="$CFLAGS $LIBGCRYPT_CFLAGS"
  LDFLAGS="$LDFLAGS $LIBGCRYPT_LDFLAGS"

  have_gcrypt=yes
  AC_CHECK_LIB([gcrypt], [gcry_md_open], [:], [have_gcrypt=no])
  AC_CHECK_HEADER([gcrypt.h], [:], [have_gcrypt=no])
  if test "x$have_gcrypt" != "xyes"; then
    AC_MSG_ERROR([GNU libgcrypt not found; please install it.])
  fi

  CFLAGS="$save_CFLAGS"
  LDFLAGS="$save_LDFLAGS"

  dnl Chroot support.
  AC_CHECK_FUNCS([chroot unshare])
  AC_CHECK_HEADERS([sched.h sys/param.h sys/mount.h sys/syscall.h])

  if test "x$ac_cv_func_chroot" != "xyes"; then
    AC_MSG_ERROR(['chroot' function missing, bailing out])
  fi

  dnl lutimes and lchown: used when canonicalizing store items.
  dnl posix_fallocate: used when extracting archives.
  dnl vfork: to speed up spawning of helper programs.
  dnl   `--> now disabled because of unpredictable behavior:
  dnl        see <http://lists.gnu.org/archive/html/guix-devel/2014-05/msg00036.html>
  dnl        and Nix commit f794465c (Nov. 2012).
  dnl sched_setaffinity: to improve RPC locality.
  dnl statvfs: to detect disk-full conditions.
  dnl strsignal: for error reporting.
  dnl statx: fine-grain 'stat' call, new in glibc 2.28.
  AC_CHECK_FUNCS([lutimes lchown posix_fallocate sched_setaffinity \
     statvfs nanosleep strsignal statx])

  dnl Check for <locale>.
  AC_LANG_PUSH(C++)
  AC_CHECK_HEADERS([locale])
  AC_LANG_POP(C++)


  dnl Check whether we have the `personality' syscall, which allows us
  dnl to do i686-linux builds on x86_64-linux machines.
  AC_CHECK_HEADERS([sys/personality.h])

  dnl Determine the appropriate default list of substitute URLs (GnuTLS
  dnl is required so we can default to 'https'.)
  GUIX_SUBSTITUTE_URLS="https://bordeaux.guix.gnu.org https://ci.guix.gnu.org"

  AC_MSG_CHECKING([for default substitute URLs])
  AC_MSG_RESULT([$GUIX_SUBSTITUTE_URLS])
  AC_SUBST([GUIX_SUBSTITUTE_URLS])

  AC_DEFINE_UNQUOTED([GUIX_SUBSTITUTE_URLS], ["$GUIX_SUBSTITUTE_URLS"],
    [Default list of substitute URLs used by 'guix-daemon'.])

  dnl Check for Guile-SSH, which is required by 'guix offload'.
  GUIX_CHECK_GUILE_SSH

  case "x$guix_cv_have_recent_guile_ssh" in
    xyes)
      guix_build_daemon_offload="yes"
      AC_DEFINE([HAVE_DAEMON_OFFLOAD_HOOK], [1],
	[Define if the daemon's 'offload' build hook is being built (requires Guile-SSH).])
      ;;
    *)
      guix_build_daemon_offload="no"
      ;;
  esac

  dnl Temporary directory used to store the daemon's data.
  GUIX_TEST_ROOT_DIRECTORY
  GUIX_TEST_ROOT="$ac_cv_guix_test_root"
  AC_SUBST([GUIX_TEST_ROOT])

  GUIX_CHECK_LOCALSTATEDIR
fi

AM_CONDITIONAL([HAVE_LIBBZ2], [test "x$HAVE_LIBBZ2" = "xyes"])
AM_CONDITIONAL([BUILD_DAEMON], [test "x$guix_build_daemon" = "xyes"])
AM_CONDITIONAL([BUILD_DAEMON_OFFLOAD],			\
  [test "x$guix_build_daemon" = "xyes"			\
   && test "x$guix_build_daemon_offload" = "xyes"])
'width: 2.7%;'/> -rw-r--r--html/file_preview.html48
-rw-r--r--html/file_preview.js75
-rw-r--r--html/item_list.js7
-rw-r--r--html/item_preview.js46
-rw-r--r--manifest.json2
-rw-r--r--test/data/pages/scripts_to_block_1.html2
-rw-r--r--test/misc_constants.py12
-rwxr-xr-xtest/profiles.py6
-rw-r--r--test/unit/test_indexeddb.py23
-rw-r--r--test/unit/test_item_list.py34
-rw-r--r--test/unit/test_item_preview.py51
-rw-r--r--test/unit/test_patterns_query_manager.py29
-rw-r--r--test/unit/test_popup.py32
19 files changed, 315 insertions, 140 deletions
diff --git a/Makefile.in b/Makefile.in
index 570260b..76aaf8e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -16,6 +16,7 @@ SHELL = /bin/sh
VPATH = <<VPATH>>
version = <<VERSION>>
+PYTEST = <<PYTEST>>
extension_files = background/ common/ content/ html/ licenses/ \
copyright default_settings.json manifest.json
@@ -70,7 +71,7 @@ test/certs/rootCA.pem: test/certs/rootCA.key
-subj "/CN=Haketilo Test"
test: test/certs/rootCA.pem test/certs/site.key $(default_target)-build.zip
- MOZ_HEADLESS=whatever pytest
+ MOZ_HEADLESS=whatever $(PYTEST)
test-environment: test/certs/rootCA.pem test/certs/site.key
python3 -m test
diff --git a/common/broadcast.js b/common/broadcast.js
index 4dcac2b..4fc5237 100644
--- a/common/broadcast.js
+++ b/common/broadcast.js
@@ -46,6 +46,7 @@
function sender_connection()
{
return {
+ type: "sender",
port: connect_to_background("broadcast_send")
};
}
@@ -92,6 +93,7 @@ function flush(sender_conn)
function listener_connection(cb)
{
const conn = {
+ type: "listener",
port: connect_to_background("broadcast_listen")
};
@@ -115,6 +117,8 @@ function unsubscribe(listener_conn, channel_name)
function close(conn)
{
+ if (conn.type === "sender")
+ flush(conn);
conn.port.disconnect();
}
#EXPORT close
diff --git a/common/entities.js b/common/entities.js
index 3ccbf04..96de5cb 100644
--- a/common/entities.js
+++ b/common/entities.js
@@ -74,7 +74,7 @@ function item_id_string(...args) {
#EXPORT item_id_string
/* vers should be an array of comparable values. Return the greatest one. */
-const max = vals => Array.reduce(vals, (v1, v2) => v1 > v2 ? v1 : v2);
+const max = vals => vals.reduce((v1, v2) => v1 > v2 ? v1 : v2);
/*
* versioned_item should be a dict with keys being version strings and values
@@ -167,6 +167,6 @@ const version_reductor = (acc, n) => [...(n || acc.length ? [n] : []), ...acc];
*
* Returns a *new* array. Doesn't modify its argument.
*/
-const normalize_version = ver => Array.reduceRight(ver, version_reductor, []);
+const normalize_version = ver => ver.reduceRight(version_reductor, []);
#ENDIF
diff --git a/common/indexeddb.js b/common/indexeddb.js
index 271dfce..f916162 100644
--- a/common/indexeddb.js
+++ b/common/indexeddb.js
@@ -56,7 +56,7 @@ let initial_data = (
const db_version = [1, 0, 0];
const nr_reductor = ([i, s], num) => [i - 1, s + num * 1024 ** i];
-const version_nr = ver => Array.reduce(ver.slice(0, 3), nr_reductor, [2, 0])[1];
+const version_nr = ver => ver.slice(0, 3).reduce(nr_reductor, [2, 0])[1];
const stores = [
["files", {keyPath: "hash_key"}],
diff --git a/common/patterns_query_tree.js b/common/patterns_query_tree.js
index ec1d989..ea3607e 100644
--- a/common/patterns_query_tree.js
+++ b/common/patterns_query_tree.js
@@ -68,7 +68,7 @@ function is_empty_node(tree_node) {
return false;
}
- if (Array.reduce(tree_node.wildcard_matches, (a, b) => b && a !== null, 1))
+ if (tree_node.wildcard_matches.reduce((a, b) => b && a !== null, 1))
return false;
return tree_node.literal_match === null;
diff --git a/configure b/configure
index 06a43eb..bd7800f 100755
--- a/configure
+++ b/configure
@@ -18,16 +18,29 @@ set -e
BROWSERPATH=''
SRCDIR=''
TARGET=''
+BROWSER_BINARY=''
+CLEAN_PROFILE=''
+DRIVER=''
+PYTEST=''
# Parse command line options
while [ "x$1" != x ]; do
case "$1" in
- --srcdir=*) SRCDIR="$(echo "$1" | cut -c 10-)";;
- --srcdir) SRCDIR="$2"; shift;;
- "DESTDIR"=*) DESTDIR="$(echo "$1" | cut -c 9-)";;
- "UPDATE_URL"=*) UPDATE_URL="$(echo "$1" | cut -c 12-)";;
- --host=*) TARGET="$(echo "$1" | cut -c 8-)";;
- --host) TARGET="$2"; shift;;
+ --srcdir=*) SRCDIR="$(printf %s "$1" | cut -c 10-)";;
+ --srcdir) SRCDIR="$2"; shift;;
+ --browser-binary=*) BROWSER_BINARY="$(printf %s "$1" | cut -c 18-)";;
+ --browser-binary) BROWSER_BINARY="$2"; shift;;
+ --clean-profile=*) CLEAN_PROFILE="$(printf %s "$1" | cut -c 17-)";;
+ --clean-profile) CLEAN_PROFILE="$2"; shift;;
+ --driver=*) DRIVER="$(printf %s "$1" | cut -c 10-)";;
+ --driver) DRIVER="$2"; shift;;
+ --pytest=*) PYTEST="$(printf %s "$1" | cut -c 10-)";;
+ --pytest) PYTEST="$2"; shift;;
+ --srcdir) SRCDIR="$2"; shift;;
+ "DESTDIR"=*) DESTDIR="$(printf %s "$1" | cut -c 9-)";;
+ "UPDATE_URL"=*) UPDATE_URL="$(printf %s "$1" | cut -c 12-)";;
+ --host=*) TARGET="$(printf %s "$1" | cut -c 8-)";;
+ --host) TARGET="$2"; shift;;
# browsers
chromium | chrome | google-chrome | mozilla |\
@@ -70,6 +83,20 @@ else
BROWSERPATH="$(realpath "$(which $TARGET)")"
fi
+# Autodetect browser binary (needed for Selenium)
+if [ "x$BROWSER_BINARY" = x ]; then
+ if [ "x$TARGET" = xabrowser ]; then
+ # Trisquel's path to Abrowser
+ BROWSER_BINARY=/usr/lib/abrowser/abrowser
+ if [ "x$TARGET" = xabrowser ]; then
+ # Debian's path to Librewolf
+ BROWSER_BINARY=/usr/share/librewolf/librewolf
+ elif [ "x$TARGET" = xicecat ]; then
+ # Parabola's path to IceCat
+ BROWSER_BINARY=/usr/lib/icecat/icecat
+ fi
+fi
+
# Check and standardize target
case "$TARGET" in
mozilla | firefox | abrowser | icecat | iceweasel-uxp |\
@@ -79,6 +106,27 @@ case "$TARGET" in
*) echo Invalid target "'$TARGET'" >&2; exit 2;;
esac
+# Autodetect Selenium driver
+if [ "x$DRIVER" = x ]; then
+ if [ "x$TARGET" = mozilla ]; then
+ DRIVER=geckodriver
+ fi
+fi
+
+# Autodetect clean profile directory for use in selenium tests
+if [ "x$CLEAN_PROFILE" = x ]; then
+ if [ "x$TARGET" = mozilla ]; then
+ CLEAN_PROFILE="$SRCDIR"/test/default_profile/icecat_empty
+ fi
+fi
+
+# Autodetect pytest
+for PYTEST_GUESS in pytest pytest-3 pytest3; do
+ if [ "x$PYTEST" = x ]; then
+ PYTEST="$(which $PYTEST_GUESS || true)"
+ fi
+done
+
# Autodetect DESTDIR (no check needed)
if [ "x$DESTDIR" = x ]; then
echo Guessing installation directory.
@@ -95,11 +143,14 @@ if [ "x$DESTDIR" = x ]; then
fi
# Write record.conf (LEAVE SRCDIR FIRST)
-echo srcdir = "$SRCDIR" > record.conf
-echo default_target = "$TARGET" >> record.conf
-echo DESTDIR = "$DESTDIR" >> record.conf
-echo UPDATE_URL = "$UPDATE_URL" >> record.conf
-
+printf '%s\n' "srcdir = $SRCDIR" > record.conf
+printf '%s\n' "default_target = $TARGET" >> record.conf
+printf '%s\n' "DESTDIR = $DESTDIR" >> record.conf
+printf '%s\n' "UPDATE_URL = $UPDATE_URL" >> record.conf
+printf '%s\n' "DRIVER = $DRIVER" >> record.conf
+printf '%s\n' "BROWSER_BINARY = $BROWSER_BINARY" >> record.conf
+printf '%s\n' "CLEAN_PROFILE = $CLEAN_PROFILE" >> record.conf
+printf '%s\n' "PYTEST = $PYTEST" >> record.conf
# Prepare and run write_makefile.sh (as config.status)
if [ ! -e config.status ]; then
diff --git a/html/file_preview.html b/html/file_preview.html
new file mode 100644
index 0000000..20e2392
--- /dev/null
+++ b/html/file_preview.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!--
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+ Haketilo's preview of a file kept in IndexedDB
+
+ This file is part of Haketilo.
+
+ Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. Although I request that you do not make use of this code in a
+ proprietary program, I am not going to enforce this in court.
+ -->
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>File preview</title>
+#LOADCSS html/reset.css
+#LOADCSS html/base.css
+ <style>
+ .error_msg {
+ color: #a33;
+ /* also italics maybe?*/
+ }
+ </style>
+ </head>
+ <body>
+ <div id="info_msg">loading...</div>
+ <div id="error_msg" class="hide"></div>
+#LOADJS html/file_preview.js
+ </body>
+</html>
diff --git a/html/file_preview.js b/html/file_preview.js
new file mode 100644
index 0000000..3e22225
--- /dev/null
+++ b/html/file_preview.js
@@ -0,0 +1,75 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Haketilo's preview of a file kept in IndexedDB.
+ *
+ * Copyright (C) 2022 Wojtek Kosior
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * As additional permission under GNU GPL version 3 section 7, you
+ * may distribute forms of that code without the copy of the GNU
+ * GPL normally required by section 4, provided you include this
+ * license notice and, in case of non-source distribution, a URL
+ * through which recipients can access the Corresponding Source.
+ * If you modify file(s) with this exception, you may extend this
+ * exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * As a special exception to the GPL, any HTML file which merely
+ * makes function calls to this code, and for that purpose
+ * includes it by reference shall be deemed a separate work for
+ * copyright law purposes. If you modify this code, you may extend
+ * this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ * license. Although I request that you do not make use of this code in a
+ * proprietary program, I am not going to enforce this in court.
+ */
+
+#IMPORT common/indexeddb.js AS haketilodb
+
+#FROM html/DOM_helpers.js IMPORT by_id
+
+const error_msg = by_id("error_msg"), info_msg = by_id("info_msg");
+
+function error(msg) {
+ info_msg.remove();
+ error_msg.classList.remove("hide");
+ error_msg.innerText = msg;
+}
+
+async function show_preview(hash_key) {
+ const db = await haketilodb.get();
+ const file = await haketilodb.idb_get(db.transaction("files"),
+ "files", hash_key);
+ if (file === undefined) {
+ error("Couldn't find file in Haketilo's internal database :(");
+ } else {
+ const blobby_opts = {type: "text/plain;charset=UTF-8"};
+ const blobby = new Blob([file.contents], blobby_opts);
+ location.replace(URL.createObjectURL(blobby));
+ }
+}
+
+const match = /^[^#]*#(.*)$/.exec(document.URL);
+if (!match) {
+ error("Bad URL :(");
+} else {
+ const hash_key = match[1];
+ show_preview(hash_key);
+}
diff --git a/html/item_list.js b/html/item_list.js
index a616713..bc1acb9 100644
--- a/html/item_list.js
+++ b/html/item_list.js
@@ -52,11 +52,8 @@ function preview_item(list_ctx, item, ignore_dialog=false)
if (list_ctx.dialog_ctx.shown && !ignore_dialog)
return;
- list_ctx.preview_ctx = list_ctx.preview_cb(
- item.definition,
- list_ctx.preview_ctx,
- list_ctx.dialog_ctx
- );
+ list_ctx.preview_ctx =
+ list_ctx.preview_cb(item.definition, list_ctx.preview_ctx);
list_ctx.preview_container.prepend(list_ctx.preview_ctx.main_div);
if (list_ctx.previewed_item !== null)
diff --git a/html/item_preview.js b/html/item_preview.js
index dccf2d4..c55183c 100644
--- a/html/item_preview.js
+++ b/html/item_preview.js
@@ -44,10 +44,10 @@
#IMPORT common/indexeddb.js AS haketilodb
#IMPORT html/dialog.js
+#FROM common/browser.js IMPORT browser
#FROM html/DOM_helpers.js IMPORT clone_template
-function populate_list(ul, items)
-{
+function populate_list(ul, items) {
for (const item of items) {
const li = document.createElement("li");
li.append(item);
@@ -55,41 +55,21 @@ function populate_list(ul, items)
}
}
-/* Link click handler used in make_file_link(). */
-async function file_link_clicked(preview_object, file_ref, event)
-{
- event.preventDefault();
-
- const db = await haketilodb.get();
- const file = await haketilodb.idb_get(db.transaction("files"),
- "files", file_ref.hash_key);
- if (file === undefined) {
- dialog.error(preview_object.dialog_context,
- "File missing from Haketilo's internal database :(");
- } else {
- const encoded_file = encodeURIComponent(file.contents);
- open(`data:text/plain;charset=utf8,${encoded_file}`, '_blank');
- }
-}
+const file_preview_link = browser.runtime.getURL("html/file_preview.html");
/*
* The default function to use to create file preview link. Links it creates can
* be used to view files from IndexedDB.
*/
-function make_file_link(preview_object, file_ref)
-{
+function make_file_link(preview_object, file_ref) {
const a = document.createElement("a");
- a.href = "javascript:void(0)";
+ a.href = `${file_preview_link}#${file_ref.hash_key}`;
a.innerText = file_ref.file;
- a.addEventListener("click",
- e => file_link_clicked(preview_object, file_ref, e));
-
+ a.target = "_blank";
return a;
}
-function resource_preview(resource, preview_object, dialog_context,
- make_link_cb=make_file_link)
-{
+function resource_preview(resource, preview_object, link_cb=make_file_link) {
if (preview_object === undefined)
preview_object = clone_template("resource_preview");
@@ -104,7 +84,7 @@ function resource_preview(resource, preview_object, dialog_context,
[...preview_object.dependencies.childNodes].forEach(n => n.remove());
populate_list(preview_object.dependencies, resource.dependencies);
- const link_maker = file_ref => make_link_cb(preview_object, file_ref);
+ const link_maker = file_ref => link_cb(preview_object, file_ref);
[...preview_object.scripts.childNodes].forEach(n => n.remove());
populate_list(preview_object.scripts, resource.scripts.map(link_maker));
@@ -113,15 +93,11 @@ function resource_preview(resource, preview_object, dialog_context,
populate_list(preview_object.copyright,
resource.source_copyright.map(link_maker));
- preview_object.dialog_context = dialog_context;
-
return preview_object;
}
#EXPORT resource_preview
-function mapping_preview(mapping, preview_object, dialog_context,
- make_link_cb=make_file_link)
-{
+function mapping_preview(mapping, preview_object, link_cb=make_file_link) {
if (preview_object === undefined)
preview_object = clone_template("mapping_preview");
@@ -145,14 +121,12 @@ function mapping_preview(mapping, preview_object, dialog_context,
}
}
- const link_maker = file_ref => make_link_cb(preview_object, file_ref);
+ const link_maker = file_ref => link_cb(preview_object, file_ref);
[...preview_object.copyright.childNodes].forEach(n => n.remove());
populate_list(preview_object.copyright,
mapping.source_copyright.map(link_maker));
- preview_object.dialog_context = dialog_context;
-
return preview_object;
}
#EXPORT mapping_preview
diff --git a/manifest.json b/manifest.json
index 25b5a1a..350c39a 100644
--- a/manifest.json
+++ b/manifest.json
@@ -68,6 +68,8 @@
"16": "icons/haketilo16.png"
},
"default_title": "Haketilo",
+ // Both popup.html and settings.html depend on file_preview.html.
+#LOADHTML html/file_preview.html
#LOADHTML html/popup.html
"default_popup": "html/popup.html"
},
diff --git a/test/data/pages/scripts_to_block_1.html b/test/data/pages/scripts_to_block_1.html
index 1aa49ee..164979d 100644
--- a/test/data/pages/scripts_to_block_1.html
+++ b/test/data/pages/scripts_to_block_1.html
@@ -37,7 +37,7 @@
href="javascript:window.__run = [...(window.__run || []), 'href'];void(0);">
Click Meee!
</a>
- <iframe src="javascript:window.parent.__run = [...(window.parent.__run || []), 'src'];">
+ <iframe src="javascript:void(window.parent.__run = [...(window.parent.__run || []), 'src']);">
</iframe>
<object data="javascript:window.__run = [...(window.__run || []), 'data'];">
</object>
diff --git a/test/misc_constants.py b/test/misc_constants.py
index db9f3f1..51602b3 100644
--- a/test/misc_constants.py
+++ b/test/misc_constants.py
@@ -27,6 +27,7 @@ Miscellaneous data that were found useful
# file's license. Although I request that you do not make use of this code
# in a proprietary program, I am not going to enforce this in court.
+import re
from pathlib import Path
here = Path(__file__).resolve().parent
@@ -36,10 +37,13 @@ awk_script_name = 'compute_scripts.awk'
unit_test_defines = ['-D', 'MOZILLA', '-D', 'MV2', '-D', 'TEST',
'-D', 'UNIT_TEST', '-D', 'DEBUG']
-default_firefox_binary = '/usr/lib/icecat/icecat'
-# The browser might be loading some globally-installed add-ons by default. They
-# could interfere with the tests, so we'll disable all of them.
-default_clean_profile_dir = here / 'default_profile' / 'icecat_empty'
+conf_line_regex = re.compile(r'^([^=]+)=(.*)$')
+conf_settings = {}
+with open(here.parent / 'record.conf', 'rt') as conf:
+ for line in conf.readlines():
+ match = conf_line_regex.match(line)
+ if match:
+ conf_settings[match.group(1).strip()] = match.group(2).strip()
default_proxy_host = '127.0.0.1'
default_proxy_port = 1337
diff --git a/test/profiles.py b/test/profiles.py
index 4892894..ae997fc 100755
--- a/test/profiles.py
+++ b/test/profiles.py
@@ -80,7 +80,7 @@ def set_webextension_uuid(profile, extension_id, uuid=default_extension_uuid):
profile.set_preference('extensions.webextensions.uuids',
json.dumps({extension_id: uuid}))
-def firefox_safe_mode(firefox_binary=default_firefox_binary,
+def firefox_safe_mode(firefox_binary=conf_settings['BROWSER_BINARY'],
proxy_host=default_proxy_host,
proxy_port=default_proxy_port):
"""
@@ -97,8 +97,8 @@ def firefox_safe_mode(firefox_binary=default_firefox_binary,
return HaketiloFirefox(options=options, firefox_profile=profile,
firefox_binary=firefox_binary)
-def firefox_with_profile(firefox_binary=default_firefox_binary,
- profile_dir=default_clean_profile_dir,
+def firefox_with_profile(firefox_binary=conf_settings['BROWSER_BINARY'],
+ profile_dir=conf_settings['CLEAN_PROFILE'],
proxy_host=default_proxy_host,
proxy_port=default_proxy_port):
"""
diff --git a/test/unit/test_indexeddb.py b/test/unit/test_indexeddb.py
index 550b923..7ce4781 100644
--- a/test/unit/test_indexeddb.py
+++ b/test/unit/test_indexeddb.py
@@ -323,17 +323,18 @@ def test_haketilodb_track(driver, execute_in_page, wait_elem_text):
# Create elements that will have tracked data inserted under them.
driver.switch_to.window(windows[0])
- execute_in_page('''
- for (const store_name of trackable) {
- const h2 = document.createElement("h2");
- h2.innerText = store_name;
- document.body.append(h2);
-
- const ul = document.createElement("ul");
- ul.id = store_name;
- document.body.append(ul);
- }
- ''')
+ execute_in_page(
+ '''
+ for (const store_name of trackable) {
+ const h2 = document.createElement("h2");
+ h2.innerText = store_name;
+ document.body.append(h2);
+
+ const ul = document.createElement("ul");
+ ul.id = store_name;
+ document.body.append(ul);
+ }
+ ''')
# Mock initial_data.
sample_resource = make_sample_resource()
diff --git a/test/unit/test_item_list.py b/test/unit/test_item_list.py
index ff532f8..0702129 100644
--- a/test/unit/test_item_list.py
+++ b/test/unit/test_item_list.py
@@ -217,17 +217,6 @@ def test_item_list_displaying(driver, execute_in_page, item_type):
assert f'item{i}' in text
assert f'Item {i}' in text
- # Check that file preview link works.
- window0 = driver.window_handles[0]
- driver.find_element_by_link_text('report.spdx').click()
- WebDriverWait(driver, 10).until(lambda _: len(driver.window_handles) == 2)
- window1 = next(filter(lambda w: w != window0, driver.window_handles))
- driver.switch_to.window(window1)
- assert 'dummy report' in driver.page_source
-
- driver.close()
- driver.switch_to.window(window0)
-
# Check that item removal confirmation dialog is displayed correctly.
execute_in_page('list_ctx.remove_but.click();')
WebDriverWait(driver, 10).until(lambda _: dialog_container.is_displayed())
@@ -271,29 +260,6 @@ def test_item_list_displaying(driver, execute_in_page, item_type):
execute_in_page('list_ctx.ul.children[1].click();')
- # Check that missing file causes the right error dialog to appear.
- execute_in_page(
- '''{
- async function steal_file(hash_key)
- {
- const db = await haketilodb.get();
- const transaction = db.transaction("files", "readwrite");
- transaction.objectStore("files").delete(hash_key);
- }
- returnval(steal_file(arguments[0]));
- }''',
- sample_files['LICENSES/CC0-1.0.txt']['hash_key'])
- driver.find_element_by_link_text('LICENSES/CC0-1.0.txt').click()
- WebDriverWait(driver, 10).until(lambda _: dialog_container.is_displayed())
- assert 'list_disabled' in ul.get_attribute('class')
- assert not preview_container.is_displayed()
-
- msg = execute_in_page('returnval(list_ctx.dialog_ctx.msg.textContent);')
- assert msg == "File missing from Haketilo's internal database :("
-
- execute_in_page('returnval(list_ctx.dialog_ctx.ok_but.click());')
- WebDriverWait(driver, 10).until(lambda _: preview_container.is_displayed())
-
# Check that item removal failure causes the right error dialog to appear.
execute_in_page('haketilodb.finalize_transaction = () => {throw "sth";};')
remove_current_item()
diff --git a/test/unit/test_item_preview.py b/test/unit/test_item_preview.py
index 6148bc2..8b2b161 100644
--- a/test/unit/test_item_preview.py
+++ b/test/unit/test_item_preview.py
@@ -19,6 +19,7 @@ Haketilo unit tests - displaying resources and mappings details
import pytest
from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import NoSuchWindowException
from ..extension_crafting import ExtraHTML
from ..script_loader import load_script
@@ -138,7 +139,10 @@ def test_mapping_preview(driver, execute_in_page):
@pytest.mark.ext_data({
'background_script': broker_js,
- 'extra_html': ExtraHTML('html/item_preview.html', {}),
+ 'extra_html': [
+ ExtraHTML('html/item_preview.html', {}),
+ ExtraHTML('html/file_preview.html', {}, wrap_into_htmldoc=False)
+ ],
'navigate_to': 'html/item_preview.html'
})
@pytest.mark.usefixtures('webextension')
@@ -148,8 +152,6 @@ def test_file_preview_link(driver, execute_in_page):
referenced file to be previewed.
"""
execute_in_page(load_script('html/item_preview.js'))
- # Mock dialog
- execute_in_page('dialog.error = (...args) => window.error_args = args;')
sample_data = make_complete_sample_data()
sample_data['mappings'] = {}
@@ -162,22 +164,45 @@ def test_file_preview_link(driver, execute_in_page):
execute_in_page(
'''
- let resource_preview_object =
- resource_preview(arguments[0], undefined, "dummy dialog ctx");
+ let resource_preview_object = resource_preview(arguments[0], undefined);
document.body.append(resource_preview_object.main_div);
''',
sample_resource)
window0 = driver.window_handles[0]
driver.find_element_by_link_text('hello.js').click()
- WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > 1)
- window1 = [wh for wh in driver.window_handles if wh != window0][0]
- driver.switch_to.window(window1)
- assert sample_files['hello.js']['contents'] in driver.page_source
+ def blob_url_navigated(driver):
+ if len(driver.window_handles) < 2:
+ return
+ window1 = [wh for wh in driver.window_handles if wh != window0][0]
+ driver.switch_to.window(window1)
+ try:
+ return driver.current_url.startswith('blob')
+ except NoSuchWindowException:
+ pass
+
+ WebDriverWait(driver, 10).until(blob_url_navigated)
+
+ assert sample_files['hello.js']['contents'].strip() \
+ in driver.find_element_by_tag_name("pre").text
+
+ driver.close()
driver.switch_to.window(window0)
+
driver.find_element_by_link_text('bye.js').click()
- assert driver.execute_script('return window.error_args;') == [
- 'dummy dialog ctx',
- "File missing from Haketilo's internal database :("
- ]
+
+ def get_error_span(driver):
+ if len(driver.window_handles) < 2:
+ return
+ window1 = [wh for wh in driver.window_handles if wh != window0][0]
+ driver.switch_to.window(window1)
+ try:
+ return driver.find_element_by_id('error_msg')
+ except NoSuchWindowException:
+ pass
+
+ error_span = WebDriverWait(driver, 10).until(get_error_span)
+ assert error_span.is_displayed()
+ assert "Couldn't find file in Haketilo's internal database :(" \
+ in error_span.text
diff --git a/test/unit/test_patterns_query_manager.py b/test/unit/test_patterns_query_manager.py
index 5daf3a0..4662e8a 100644
--- a/test/unit/test_patterns_query_manager.py
+++ b/test/unit/test_patterns_query_manager.py
@@ -20,6 +20,7 @@ Haketilo unit tests - building pattern tree and putting it in a content script
import pytest
import json
from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import TimeoutException
from ..script_loader import load_script
@@ -240,25 +241,31 @@ def background_js():
@pytest.mark.usefixtures('webextension')
def test_pqm_script_injection(driver, execute_in_page):
# Let's open a normal page in a second window. Window 0 will be used to make
- # changed to IndexedDB and window 1 to test the working of content scripts.
+ # changes to IndexedDB and window 1 to test the working of content scripts.
driver.execute_script('window.open("about:blank", "_blank");')
WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) == 2)
windows = [*driver.window_handles]
- def run_content_script():
- driver.switch_to.window(windows[1])
- driver.get('https://gotmyowndoma.in/index.html')
- windows[1] = driver.window_handles[1]
+ def get_tree_json(driver):
return driver.execute_script(
'''
return (document.getElementById("tree-json") || {}).innerText;
''')
- for attempt in range(10):
+ def run_content_script():
+ driver.switch_to.window(windows[1])
+ driver.get('https://gotmyowndoma.in/index.html')
+ windows[1] = driver.current_window_handle
+ try:
+ return WebDriverWait(driver, 10).until(get_tree_json)
+ except TimeoutException:
+ pass
+
+ for attempt in range(2):
json_txt = run_content_script()
- if json.loads(json_txt) == {}:
+ if json_txt and json.loads(json_txt) == {}:
break;
- assert attempt != 9
+ assert attempt != 2
driver.switch_to.window(windows[0])
execute_in_page(load_script('common/indexeddb.js'))
@@ -271,12 +278,12 @@ def test_pqm_script_injection(driver, execute_in_page):
}
execute_in_page('returnval(save_items(arguments[0]));', sample_data)
- for attempt in range(10):
- tree_json = run_content_script()
+ for attempt in range(2):
+ tree_json = run_content_script() or '{}'
json.loads(tree_json)
if all([m['identifier'] in tree_json for m in sample_mappings]):
break
- assert attempt != 9
+ assert attempt != 2
driver.switch_to.window(windows[0])
execute_in_page(
diff --git a/test/unit/test_popup.py b/test/unit/test_popup.py
index da1812d..da125ec 100644
--- a/test/unit/test_popup.py
+++ b/test/unit/test_popup.py
@@ -20,6 +20,7 @@ Haketilo unit tests - repository querying
import pytest
import json
from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import ElementNotInteractableException
from ..extension_crafting import ExtraHTML
from ..script_loader import load_script
@@ -146,10 +147,16 @@ def test_popup_display(driver, execute_in_page, page_info_key):
"""
reload_with_target(driver, f'mock_page_info-{page_info_key}')
- by_id = driver.execute_script('''
- const nodes = [...document.querySelectorAll("[id]")];
- return nodes.reduce((ob, node) => Object.assign(ob, {[node.id]: node}), {});
- ''');
+ def get_nodes_by_id(driver):
+ by_id = driver.execute_script(
+ '''
+ const nodes = [...document.querySelectorAll("[id]")];
+ const reductor = (ob, node) => Object.assign(ob, {[node.id]: node});
+ return nodes.reduce(reductor, {});
+ ''');
+ return by_id if by_id and 'repo_query_container' in by_id else None
+
+ by_id = WebDriverWait(driver, 10).until(get_nodes_by_id)
if page_info_key == '':
error_msg = 'Page info not avaialable. Try reloading the page.'
@@ -213,9 +220,22 @@ def test_popup_repo_query(driver, execute_in_page):
"""
reload_with_target(driver, f'mock_page_info-blocked_rule')
+ driver.implicitly_wait(10)
search_but = driver.find_element_by_id("search_resources_but")
- WebDriverWait(driver, 10).until(lambda d: search_but.is_displayed())
- search_but.click()
+ driver.implicitly_wait(0)
+
+ # For unknown reasons waiting for search_but.is_displayed() to return True
+ # does not guarantee the button will be interactable afterwards under newer
+ # browsers. Hence, this workaround.
+ def click_search_but(driver):
+ try:
+ search_but.click()
+ return True
+ except ElementNotInteractableException:
+ pass
+
+ WebDriverWait(driver, 10).until(click_search_but)
+
containers = dict([(name, driver.find_element_by_id(f'{name}_container'))
for name in ('page_info', 'repo_query')])
assert not containers['page_info'].is_displayed()