diff options
-rw-r--r-- | html/settings.html | 95 | ||||
-rw-r--r-- | html/settings.js | 50 | ||||
-rw-r--r-- | test/haketilo_test/test_integration.py | 7 | ||||
-rw-r--r-- | test/haketilo_test/unit/test_indexeddb.py | 1 | ||||
-rw-r--r-- | test/haketilo_test/unit/test_settings.py | 61 |
5 files changed, 181 insertions, 33 deletions
diff --git a/html/settings.html b/html/settings.html index 7abb870..e33f112 100644 --- a/html/settings.html +++ b/html/settings.html @@ -35,6 +35,15 @@ #LOADCSS html/base.css #LOADCSS html/grid.css <style> + #loader, #indexeddb_error { + margin: auto; + padding: 1em; + max-width: 800px; + } + #indexeddb_error p { + margin-bottom: 1em; + } + /* Style top menu items. */ #tab_heads>* { background-color: #70AF70; @@ -114,34 +123,72 @@ #INCLUDE html/item_preview.html #INCLUDE html/text_entry_list.html #INCLUDE html/payload_create.html - <ul id="tab_heads"> - <li id="blocking_head"> Blocking </li> - <li id="mappings_head"> Mappings </li> - <li id="resources_head"> Resources </li> - <li id="new_payload_head" class="active_head"> New payload </li> - <li id="repos_head"> Repositories </li> - </ul> - <div id="top_menu_line" class="top_line"></div> - <div id="blocking_tab" class="tab"> - <div id="blocking_editable_container" class="grid_2"> - <div id="blocking_list_container"> - <h3>Block scripts on</h3> - </div> - <div id="allowing_list_container"> - <h3>Allow scripts on</h3> + <div id="loader"> + Loading settings page... + </div> + <div id="indexeddb_error" class="hide"> + <p> + Cannot display settings page. + </p> + <p> + Haketilo could not access IndexedDB. IndexedDB is an in-browser + database in which Haketilo stores all its configuration. Without it, the + settings page is non-operational. + <p> +#IF MOZILLA + <div id="indexeddb_private_mode_explanation" class="hide"> + <p> + This issue is the result of using Haketilo in Private Browsing mode. + For privacy reasons your browser blocks access to IndexedDB in + private windows and this unfortunately also affects Haketilo's + settings page. + </p> + <p> + You can sacrifice this single privacy feature and enable IndexedDB + access in private windows by navigating to "about:config" in the URL + bar, agreeing to accept the risk and setting the + "dom.indexedDB.privateBrowsing.enabled" preference to "true". Those + pages that have their scripts blocked will still be unable to access + IndexedDB. + </p> + <p> + Alternatively, you can open Haketilo's settings in a separate, + non-private window. The configuration you make there will take + effect on pages opened in Private Browsing mode as well. + </p> </div> - <div id="default_policy_dialog" class="grid_col_both text_center"> +#ENDIF + </div> + <div id="main_view" class="hide"> + <ul id="tab_heads"> + <li id="blocking_head"> Blocking </li> + <li id="mappings_head"> Mappings </li> + <li id="resources_head"> Resources </li> + <li id="new_payload_head" class="active_head"> New payload </li> + <li id="repos_head"> Repositories </li> + </ul> + <div id="top_menu_line" class="top_line"></div> + <div id="blocking_tab" class="tab"> + <div id="blocking_editable_container" class="grid_2"> + <div id="blocking_list_container"> + <h3>Block scripts on</h3> + </div> + <div id="allowing_list_container"> + <h3>Allow scripts on</h3> + </div> + <div id="default_policy_dialog" class="grid_col_both text_center"> #INCLUDE html/default_blocking_policy.html + </div> </div> + <div id="blocking_dialog_container" class="hide"></div> + </div> + <div id="mappings_tab" class="tab"></div> + <div id="resources_tab" class="tab"></div> + <div id="new_payload_tab" class="tab active_tab"></div> + <div id="repos_tab" class="tab"> + <div id="repos_list_container"></div> + <div id="repos_dialog_container" class="hide"></div> </div> - <div id="blocking_dialog_container" class="hide"></div> - </div> - <div id="mappings_tab" class="tab"></div> - <div id="resources_tab" class="tab"></div> - <div id="new_payload_tab" class="tab active_tab"></div> - <div id="repos_tab" class="tab"> - <div id="repos_list_container"></div> - <div id="repos_dialog_container" class="hide"></div> </div> #LOADJS html/settings.js </body> diff --git a/html/settings.js b/html/settings.js index 36eeb47..686cc11 100644 --- a/html/settings.js +++ b/html/settings.js @@ -41,8 +41,13 @@ * proprietary program, I am not going to enforce this in court. */ +#IMPORT common/indexeddb.js AS haketilodb #IMPORT html/dialog.js +#IF MOZILLA +#FROM common/browser.js IMPORT browser +#ENDIF + #FROM html/DOM_helpers.js IMPORT by_id #FROM html/text_entry_list.js IMPORT blocking_allowing_lists, repo_list #FROM html/item_list.js IMPORT mapping_list, resource_list @@ -118,8 +123,43 @@ async function set_up_repos_tab() { containers[0].append((await repo_list(dialog_ctx)).main_div); } -set_up_blocking_tab(); -set_up_mappings_tab(); -set_up_resources_tab(); -set_up_new_payload_tab(); -set_up_repos_tab(); +function set_up_settings_view() { + by_id("loader").remove(); + by_id("main_view").classList.remove("hide"); + + set_up_blocking_tab(); + set_up_mappings_tab(); + set_up_resources_tab(); + set_up_new_payload_tab(); + set_up_repos_tab(); +} + +#IF MOZILLA +async function show_indexeddb_error() { + const this_tab = await browser.tabs.getCurrent(); + if (this_tab.incognito) + by_id("indexeddb_private_mode_explanation").classList.remove("hide"); +#ELSE +function show_indexeddb_error() { +#ENDIF + by_id("loader").remove(); + by_id("indexeddb_error").classList.remove("hide"); +} + +async function init_settings_page() { + try { + await haketilodb.get(); + } catch(e) { + console.error(e); + show_indexeddb_error(); + return; + } + + set_up_settings_view(); +} + +#IF UNIT_TEST +window.init_settings_page = init_settings_page; +#ELSE +init_settings_page(); +#ENDIF diff --git a/test/haketilo_test/test_integration.py b/test/haketilo_test/test_integration.py index 87d1827..b77afbd 100644 --- a/test/haketilo_test/test_integration.py +++ b/test/haketilo_test/test_integration.py @@ -19,6 +19,10 @@ Haketilo integration tests import pytest +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By + from .extension_crafting import get_extension_base_url @pytest.mark.usefixtures('haketilo') @@ -30,6 +34,9 @@ def test_integration(driver): base_url = get_extension_base_url(driver) driver.get(base_url + 'html/settings.html') + WebDriverWait(driver, 10)\ + .until(EC.visibility_of_element_located((By.ID, "main_view"))) + for tab_head_id, item_text in [ ('resources_head', 'Haketilo demonstrational script'), ('mappings_head', 'Haketilo demonstrational message'), diff --git a/test/haketilo_test/unit/test_indexeddb.py b/test/haketilo_test/unit/test_indexeddb.py index c2d5427..773f5c8 100644 --- a/test/haketilo_test/unit/test_indexeddb.py +++ b/test/haketilo_test/unit/test_indexeddb.py @@ -21,7 +21,6 @@ import pytest import json from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import WebDriverException from ..script_loader import load_script diff --git a/test/haketilo_test/unit/test_settings.py b/test/haketilo_test/unit/test_settings.py index 7cdb76f..92ec4b6 100644 --- a/test/haketilo_test/unit/test_settings.py +++ b/test/haketilo_test/unit/test_settings.py @@ -20,18 +20,24 @@ Haketilo unit tests - entire settings page import pytest from .utils import * +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By + from ..extension_crafting import ExtraHTML from ..script_loader import load_script from .utils import * -@pytest.mark.ext_data({ +ext_data = { 'background_script': broker_js, 'extra_html': ExtraHTML('html/settings.html', wrap_into_htmldoc=False) -}) +} + +@pytest.mark.ext_data(ext_data) @pytest.mark.usefixtures('webextension') def test_settings_page_tabs(driver, execute_in_page): """ - Test navigation throught the tabs of the settings page. + Test navigation through the tabs of the settings page. """ # First, put some sample data in IndexedDB. execute_in_page(load_script('common/indexeddb.js')) @@ -45,6 +51,13 @@ def test_settings_page_tabs(driver, execute_in_page): # Now navigate to settings page. testpage_url = driver.execute_script('return window.location.href;') driver.get(testpage_url.replace('testpage.html', 'html/settings.html')) + execute_in_page('init_settings_page();') + + WebDriverWait(driver, 10)\ + .until(EC.visibility_of_element_located((By.ID, "main_view"))) + + assert driver.find_elements_by_id('loader') == [] + assert not driver.find_element_by_id('indexeddb_error').is_displayed() names = ['blocking', 'mappings', 'resources', 'new_payload', 'repos'] tabs = dict([(n, driver.find_element_by_id(f'{n}_tab')) for n in names]) @@ -61,3 +74,45 @@ def test_settings_page_tabs(driver, execute_in_page): assert 'active_head' not in heads[tab_name_2].get_attribute('class') assert 'active_tab' not in tabs[tab_name_2].get_attribute('class') assert not tabs[tab_name_2].is_displayed() + +@pytest.mark.ext_data({ + **ext_data, + 'navigate_to': 'html/settings.html' +}) +@pytest.mark.usefixtures('webextension') +@pytest.mark.parametrize('incognito', [True, False]) +def test_settings_page_indexeddb_error(driver, execute_in_page, incognito): + """ + Test if failure to access IndexedDB in settings page results in the + appropriate message being shown. + """ + execute_in_page( + '''{ + /* + * Mock an unavailable IndexedDB. Calling onerror() without having set + * "errorCode" on the request is the behavior observed under Mozilla. + */ + indexedDB.open = function() { + const dummy_open_request = {}; + const dummy_event = {target: dummy_open_request}; + setTimeout(() => dummy_open_request.onerror(dummy_event), 0); + + return dummy_open_request; + } + + const dummy_tab = {incognito: arguments[0]}; + browser.tabs.getCurrent = () => Promise.resolve(dummy_tab); + + init_settings_page(); + }''', + incognito) + + WebDriverWait(driver, 10)\ + .until(EC.visibility_of_element_located((By.ID, 'indexeddb_error'))) + + assert driver.find_elements_by_id('loader') == [] + assert not driver.find_element_by_id('main_view').is_displayed() + + if incognito: + assert driver.find_element_by_id('indexeddb_private_mode_explanation')\ + .is_displayed() |