mirror of
				https://github.com/zen-browser/desktop.git
				synced 2025-10-26 12:27:50 +00:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
			window-syn
			...
			test/5880
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c5058746de | ||
|   | 1d509e30a9 | ||
|   | fbad844a08 | ||
|   | f52f913043 | ||
|   | 346ef9c158 | ||
|   | 5038bd5cf7 | ||
|   | b63cefa98b | ||
|   | f8375a1155 | ||
|   | 34424a8f95 | ||
|   | c883572de8 | ||
|   | a53b4f9c94 | ||
|   | d1bba0a8e2 | ||
|   | d21e127bd7 | 
| @@ -17,6 +17,8 @@ engine/ | ||||
|  | ||||
| surfer.json | ||||
|  | ||||
| src/zen/tests/mochitests/* | ||||
|  | ||||
| src/browser/app/profile/*.js | ||||
| pnpm-lock.yaml | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  | ||||
| import js from '@eslint/js'; | ||||
| import globals from 'globals'; | ||||
| import { defineConfig } from 'eslint/config'; | ||||
| import { defineConfig, globalIgnores } from 'eslint/config'; | ||||
| import zenGlobals from './src/zen/zen.globals.js'; | ||||
|  | ||||
| export default defineConfig([ | ||||
| @@ -23,4 +23,5 @@ export default defineConfig([ | ||||
|     }, | ||||
|     ignores: ['**/vendor/**', '**/tests/**'], | ||||
|   }, | ||||
|   globalIgnores(['**/mochitests/**']), | ||||
| ]); | ||||
|   | ||||
| @@ -38,7 +38,7 @@ zen-library-sidebar-workspaces = | ||||
| zen-library-sidebar-mods =  | ||||
|     .label = Mods | ||||
| zen-toggle-compact-mode-button =  | ||||
|     .label = Compact Mode | ||||
|     .label = Kompakter Modus | ||||
|     .tooltiptext = Compact Mode umschalten | ||||
|  | ||||
| # note: Do not translate the "<br/>" tags in the following string | ||||
|   | ||||
| @@ -179,6 +179,9 @@ category-zen-CKS = | ||||
|     .tooltiptext = { pane-zen-CKS-title } | ||||
| pane-settings-CKS-title = { -brand-short-name } Keyboard Shortcuts | ||||
|  | ||||
| category-zen-marketplace = | ||||
|     .tooltiptext = Zen Mods | ||||
|  | ||||
| zen-settings-CKS-header = Customize your keyboard shortcuts | ||||
| zen-settings-CKS-description = Change the default keyboard shortcuts to your liking and improve your browsing experience | ||||
|  | ||||
|   | ||||
							
								
								
									
										115
									
								
								scripts/import_external_tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								scripts/import_external_tests.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| # 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| import os | ||||
| import tomllib | ||||
| import shutil | ||||
|  | ||||
| BASE_PATH = os.path.join("src", "zen", "tests") | ||||
| EXTERNAL_TESTS_MANIFEST = os.path.join(BASE_PATH, "manifest.toml") | ||||
| EXTERNAL_TESTS_OUTPUT = os.path.join(BASE_PATH, "mochitests") | ||||
|  | ||||
| FILE_PREFIX = """ | ||||
| # 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| # This file is autogenerated by scripts/import_external_tests.py | ||||
| # Do not edit manually. | ||||
|  | ||||
| BROWSER_CHROME_MANIFESTS += [ | ||||
| """ | ||||
|  | ||||
| FILE_SUFFIX = "]" | ||||
|  | ||||
| def get_tests_manifest(): | ||||
|   with open(EXTERNAL_TESTS_MANIFEST, "rb") as f: | ||||
|     return tomllib.load(f) | ||||
|  | ||||
| def die_with_error(message): | ||||
|   print(f"ERROR: {message}") | ||||
|   exit(1) | ||||
|  | ||||
| def validate_tests_path(path, files, ignore_list): | ||||
|   for ignore in ignore_list: | ||||
|     if ignore not in files: | ||||
|       die_with_error(f"Ignore file '{ignore}' not found in tests folder '{path}'") | ||||
|   if "browser.toml" not in files or "browser.js" in ignore_list: | ||||
|     die_with_error(f"'browser.toml' not found in tests folder '{path}'") | ||||
|  | ||||
| def disable_and_replace_manifest(manifest, output_path): | ||||
|   toml_file = os.path.join(output_path, "browser.toml") | ||||
|   disabled_tests = manifest.get("disable", []) | ||||
|   with open(toml_file, "r") as f: | ||||
|     data = f.read() | ||||
|   for test in disabled_tests: | ||||
|     segment = f'["{test}"]' | ||||
|     if segment not in data: | ||||
|       die_with_error(f"Could not disable test '{test}' as it was not found in '{toml_file}'") | ||||
|     replace_with = f'["{test}"]\ndisabled="Disabled by import_external_tests.py"' | ||||
|     data = data.replace(segment, replace_with) | ||||
|   for replacement in manifest.get("replace-manifest", {}).keys(): | ||||
|     if replacement not in data: | ||||
|       die_with_error(f"Could not replace manifest entry '{replacement}' as it was not found in '{toml_file}'") | ||||
|     data = data.replace(replacement, manifest["replace-manifest"][replacement]) | ||||
|   with open(toml_file, "w") as f: | ||||
|     f.write(data) | ||||
|  | ||||
| def import_test_suite(test_suite, source_path, output_path, ignore_list, manifest, is_direct_path=False): | ||||
|   print(f"Importing test suite '{test_suite}' from '{source_path}'") | ||||
|   tests_folder = os.path.join("engine", source_path) | ||||
|   if not is_direct_path: | ||||
|     tests_folder = os.path.join(tests_folder, "tests") | ||||
|   if not os.path.exists(tests_folder): | ||||
|     die_with_error(f"Tests folder not found: {tests_folder}") | ||||
|   files = os.listdir(tests_folder) | ||||
|   validate_tests_path(tests_folder, files, ignore_list) | ||||
|   if os.path.exists(output_path): | ||||
|     shutil.rmtree(output_path) | ||||
|   os.makedirs(output_path, exist_ok=True) | ||||
|   for item in files: | ||||
|     if item in ignore_list: | ||||
|       continue | ||||
|     s = os.path.join(tests_folder, item) | ||||
|     d = os.path.join(output_path, item) | ||||
|     if os.path.isdir(s): | ||||
|       shutil.copytree(s, d) | ||||
|     else: | ||||
|       shutil.copy2(s, d) | ||||
|   disable_and_replace_manifest(manifest[test_suite], output_path) | ||||
|  | ||||
| def write_moz_build_file(manifest): | ||||
|   moz_build_path = os.path.join(EXTERNAL_TESTS_OUTPUT, "moz.build") | ||||
|   print(f"Writing moz.build file to '{moz_build_path}'") | ||||
|   with open(moz_build_path, "w") as f: | ||||
|     f.write(FILE_PREFIX) | ||||
|     for test_suite in manifest.keys(): | ||||
|       f.write(f'\t"{test_suite}/browser.toml",\n') | ||||
|     f.write(FILE_SUFFIX) | ||||
|  | ||||
| def make_sure_ordered_tests(manifest): | ||||
|   ordered_tests = sorted(manifest.keys()) | ||||
|   if list(manifest.keys()) != ordered_tests: | ||||
|     die_with_error("Test suites in manifest.toml are not in alphabetical order.") | ||||
|  | ||||
| def main(): | ||||
|   manifest = get_tests_manifest() | ||||
|   if os.path.exists(EXTERNAL_TESTS_OUTPUT): | ||||
|     shutil.rmtree(EXTERNAL_TESTS_OUTPUT) | ||||
|   os.makedirs(EXTERNAL_TESTS_OUTPUT, exist_ok=True) | ||||
|  | ||||
|   make_sure_ordered_tests(manifest) | ||||
|   for test_suite, config in manifest.items(): | ||||
|     import_test_suite( | ||||
|       test_suite=test_suite, | ||||
|       source_path=config["source"], | ||||
|       output_path=os.path.join(EXTERNAL_TESTS_OUTPUT, test_suite), | ||||
|       ignore_list=config.get("ignore", []), | ||||
|       is_direct_path=config.get("is_direct_path", False), | ||||
|       manifest=manifest | ||||
|     ) | ||||
|   write_moz_build_file(manifest) | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|   main() | ||||
| @@ -15,6 +15,7 @@ IGNORE_PREFS_FILE_OUT = os.path.join( | ||||
|     'engine', 'testing', 'mochitest', 'ignorePrefs.json' | ||||
| ) | ||||
|  | ||||
| MOCHITEST_NAME = "mochitests" | ||||
|  | ||||
| class JSONWithCommentsDecoder(json.JSONDecoder): | ||||
|   def __init__(self, **kw): | ||||
| @@ -68,7 +69,9 @@ def main(): | ||||
|     os.execvp(command[0], command) | ||||
|  | ||||
|   if path in ("", "all"): | ||||
|     test_dirs = [p for p in Path("zen/tests").iterdir() if p.is_dir()] | ||||
|     test_dirs = [p for p in Path("zen/tests").iterdir() if p.is_dir() and p.name != MOCHITEST_NAME] | ||||
|     mochitest_dirs = [p for p in Path(f"zen/tests/{MOCHITEST_NAME}").iterdir() if p.is_dir()] | ||||
|     test_dirs.extend(mochitest_dirs) | ||||
|     test_paths = [str(p) for p in test_dirs] | ||||
|     run_mach_with_paths(test_paths) | ||||
|   else: | ||||
|   | ||||
| @@ -58,4 +58,3 @@ | ||||
| <script type="text/javascript" src="chrome://browser/content/zen-components/ZenDownloadAnimation.mjs"></script> | ||||
| <script type="text/javascript" src="chrome://browser/content/zen-components/ZenEmojiPicker.mjs"></script> | ||||
| <script type="text/javascript" src="chrome://browser/content/zen-components/ZenWorkspaceCreation.mjs"></script> | ||||
| <script type="text/javascript" src="chrome://browser/content/zen-components/ZenWindowSyncing.mjs"></script> | ||||
|   | ||||
| @@ -42,7 +42,6 @@ | ||||
|         content/browser/zen-components/ZenWorkspaceIcons.mjs                    (../../zen/workspaces/ZenWorkspaceIcons.mjs) | ||||
|         content/browser/zen-components/ZenWorkspace.mjs                         (../../zen/workspaces/ZenWorkspace.mjs) | ||||
|         content/browser/zen-components/ZenWorkspaces.mjs                        (../../zen/workspaces/ZenWorkspaces.mjs) | ||||
|         content/browser/zen-components/ZenWindowSyncing.mjs                     (../../zen/workspaces/ZenWindowSyncing.mjs) | ||||
|         content/browser/zen-components/ZenWorkspaceCreation.mjs                 (../../zen/workspaces/ZenWorkspaceCreation.mjs) | ||||
|         content/browser/zen-components/ZenWorkspacesStorage.mjs                 (../../zen/workspaces/ZenWorkspacesStorage.mjs) | ||||
|         content/browser/zen-components/ZenWorkspacesSync.mjs                    (../../zen/workspaces/ZenWorkspacesSync.mjs) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| diff --git a/browser/components/customizableui/CustomizableUI.sys.mjs b/browser/components/customizableui/CustomizableUI.sys.mjs | ||||
| index d9a059f608779fea7cd8c595a432f6fe95183e0c..09a7c4045afd0b96027d0bbbad22e02e52fd7b22 100644 | ||||
| index d9a059f608779fea7cd8c595a432f6fe95183e0c..31c43bc3d5b05713299c1b822b9774909445e862 100644 | ||||
| --- a/browser/components/customizableui/CustomizableUI.sys.mjs | ||||
| +++ b/browser/components/customizableui/CustomizableUI.sys.mjs | ||||
| @@ -14,6 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, { | ||||
| @@ -158,7 +158,7 @@ index d9a059f608779fea7cd8c595a432f6fe95183e0c..09a7c4045afd0b96027d0bbbad22e02e | ||||
|            continue; | ||||
|          } | ||||
| -        sum += parseFloat(style.marginLeft) + parseFloat(style.marginRight); | ||||
| +        sum += parseFloat(style.marginLeft) + Math.max(0, parseFloat(style.marginRight)); | ||||
| +        sum += parseFloat(style.marginLeft) + (win.gZenVerticalTabsManager._hasSetSingleToolbar ? Math.max(0, parseFloat(style.marginRight)) : parseFloat(style.marginRight)); | ||||
|          if (child != aExceptChild) { | ||||
|            sum += getInlineSize(child); | ||||
|          } | ||||
|   | ||||
| @@ -1,8 +1,16 @@ | ||||
| diff --git a/browser/themes/shared/tabbrowser/content-area.css b/browser/themes/shared/tabbrowser/content-area.css | ||||
| index e06addf1602dc26ff4e75a8db6251231690f3f80..86e2cd0194bb37fa140a2f93eccfdd61419a9aec 100644 | ||||
| index e06addf1602dc26ff4e75a8db6251231690f3f80..ffac005d5040852eda8f574f65f2eadf5ecbd642 100644 | ||||
| --- a/browser/themes/shared/tabbrowser/content-area.css | ||||
| +++ b/browser/themes/shared/tabbrowser/content-area.css | ||||
| @@ -276,7 +276,7 @@ | ||||
| @@ -134,7 +134,6 @@ | ||||
|    } | ||||
|   | ||||
|    browser:is([blank], [pendingpaint]) { | ||||
| -    opacity: 0; | ||||
|    } | ||||
|   | ||||
|    browser[type="content"] { | ||||
| @@ -276,7 +275,7 @@ | ||||
|   | ||||
|  .dialogStack { | ||||
|    z-index: var(--browser-stack-z-index-dialog-stack); | ||||
|   | ||||
| @@ -24,11 +24,11 @@ | ||||
|         const { exists: shouldExist = true } = descendantSelectors; | ||||
|         if (exists === shouldExist) { | ||||
|           if (!element.hasAttribute(stateAttribute)) { | ||||
|             element.setAttribute(stateAttribute, 'true'); | ||||
|             gZenCompactModeManager._setElementExpandAttribute(element, true, stateAttribute); | ||||
|           } | ||||
|         } else { | ||||
|           if (element.hasAttribute(stateAttribute)) { | ||||
|             element.removeAttribute(stateAttribute); | ||||
|             gZenCompactModeManager._setElementExpandAttribute(element, false, stateAttribute); | ||||
|           } | ||||
|         } | ||||
|       }; | ||||
|   | ||||
| @@ -143,6 +143,7 @@ var gZenCompactModeManager = { | ||||
|   }, | ||||
|  | ||||
|   addHasPolyfillObserver() { | ||||
|     const attributes = ['panelopen', 'open', 'breakout-extend', 'zen-floating-urlbar']; | ||||
|     this.sidebarObserverId = ZenHasPolyfill.observeSelectorExistence( | ||||
|       this.sidebar, | ||||
|       [ | ||||
| @@ -152,8 +153,21 @@ var gZenCompactModeManager = { | ||||
|         }, | ||||
|       ], | ||||
|       'zen-compact-mode-active', | ||||
|       ['panelopen', 'open', 'breakout-extend', 'zen-floating-urlbar'] | ||||
|       attributes | ||||
|     ); | ||||
|     this.toolbarObserverId = ZenHasPolyfill.observeSelectorExistence( | ||||
|       document.getElementById('zen-appcontent-navbar-wrapper'), | ||||
|       [ | ||||
|         { | ||||
|           selector: | ||||
|             ":is([panelopen='true'], [open='true'], #urlbar:focus-within, [breakout-extend='true']):not(.zen-compact-mode-ignore)", | ||||
|         }, | ||||
|       ], | ||||
|       'zen-compact-mode-active', | ||||
|       attributes | ||||
|     ); | ||||
|     // Always connect this observer, we need it even if compact mode is disabled | ||||
|     ZenHasPolyfill.connectObserver(this.toolbarObserverId); | ||||
|   }, | ||||
|  | ||||
|   flashSidebarIfNecessary(aInstant = false) { | ||||
| @@ -202,7 +216,7 @@ var gZenCompactModeManager = { | ||||
|   }, | ||||
|  | ||||
|   updateCompactModeContext(isSingleToolbar) { | ||||
|     isSingleToolbar ||= this.checkIfIllegalState(); | ||||
|     const isIllegalState = this.checkIfIllegalState(); | ||||
|     const menuitem = document.getElementById('zen-context-menu-compact-mode-toggle'); | ||||
|     const menu = document.getElementById('zen-context-menu-compact-mode'); | ||||
|     if (isSingleToolbar) { | ||||
| @@ -212,6 +226,14 @@ var gZenCompactModeManager = { | ||||
|       menu.removeAttribute('hidden'); | ||||
|       menu.querySelector('menupopup').prepend(menuitem); | ||||
|     } | ||||
|     const hideToolbarMenuItem = document.getElementById( | ||||
|       'zen-context-menu-compact-mode-hide-toolbar' | ||||
|     ); | ||||
|     if (isIllegalState) { | ||||
|       hideToolbarMenuItem.setAttribute('disabled', 'true'); | ||||
|     } else { | ||||
|       hideToolbarMenuItem.removeAttribute('disabled'); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   hideSidebar() { | ||||
| @@ -601,7 +623,7 @@ var gZenCompactModeManager = { | ||||
|   }, | ||||
|  | ||||
|   _setElementExpandAttribute(element, value, attr = 'zen-has-hover') { | ||||
|     const kVerifiedAttributes = ['zen-has-hover', 'has-popup-menu']; | ||||
|     const kVerifiedAttributes = ['zen-has-hover', 'has-popup-menu', 'zen-compact-mode-active']; | ||||
|     const isToolbar = element.id === 'zen-appcontent-navbar-wrapper'; | ||||
|     if (value) { | ||||
|       element.setAttribute(attr, 'true'); | ||||
| @@ -612,8 +634,7 @@ var gZenCompactModeManager = { | ||||
|             document.documentElement.hasAttribute('zen-has-bookmarks'))) || | ||||
|           (this.preference && | ||||
|             Services.prefs.getBoolPref('zen.view.compact.hide-toolbar') && | ||||
|             !gZenVerticalTabsManager._hasSetSingleToolbar && | ||||
|             !gURLBar.hasAttribute('breakout-extend'))) | ||||
|             !gZenVerticalTabsManager._hasSetSingleToolbar)) | ||||
|       ) { | ||||
|         gBrowser.tabpanels.setAttribute('has-toolbar-hovered', 'true'); | ||||
|       } | ||||
|   | ||||
| @@ -35,7 +35,8 @@ | ||||
|     overflow: clip; | ||||
|  | ||||
|     & #urlbar:not([breakout-extend='true']) { | ||||
|       opacity: 0; | ||||
|       /* Sometimes, "opacity: 1" is forced elsewhere */ | ||||
|       opacity: 0 !important; | ||||
|       pointer-events: none; | ||||
|       transition: opacity var(--zen-hidden-toolbar-transition); | ||||
|     } | ||||
| @@ -48,20 +49,18 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   & #zen-appcontent-navbar-wrapper[zen-has-hover], | ||||
|   & #zen-appcontent-navbar-wrapper[has-popup-menu], | ||||
|   & | ||||
|     #zen-appcontent-navbar-wrapper:has( | ||||
|       *:is([panelopen='true'], [open='true'], #urlbar:focus-within, [breakout-extend='true']):not(.zen-compact-mode-ignore) | ||||
|   & #zen-appcontent-navbar-wrapper:is( | ||||
|     [zen-has-hover], | ||||
|     [has-popup-menu], | ||||
|     [zen-compact-mode-active] | ||||
|   ) { | ||||
|  | ||||
|     height: var(--zen-toolbar-height-with-bookmarks); | ||||
|     overflow: inherit; | ||||
|  | ||||
| %include windows-captions-fix-active.inc.css | ||||
|  | ||||
|     & #urlbar { | ||||
|       opacity: 1; | ||||
|       opacity: 1 !important; | ||||
|       pointer-events: auto; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -24,6 +24,9 @@ | ||||
|     #duringOpening = false; | ||||
|     #ignoreClose = false; | ||||
|  | ||||
|     // Click handling | ||||
|     #lastLinkClickData = { clientX: 0, clientY: 0, height: 0, width: 0 }; | ||||
|  | ||||
|     // Arc animation configuration | ||||
|     #ARC_CONFIG = Object.freeze({ | ||||
|       ARC_STEPS: 70, // Increased for smoother bounce | ||||
| @@ -268,10 +271,31 @@ | ||||
|         data.height | ||||
|       ); | ||||
|       return await this.#imageBitmapToBase64( | ||||
|         await window.browsingContext.currentWindowGlobal.drawSnapshot(rect, 1, 'transparent', true) | ||||
|         await window.browsingContext.currentWindowGlobal.drawSnapshot( | ||||
|           rect, | ||||
|           1, | ||||
|           'transparent', | ||||
|           undefined | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Set the last link click data | ||||
|      * @param {Object} data - The link click data | ||||
|      */ | ||||
|     set lastLinkClickData(data) { | ||||
|       this.#lastLinkClickData = data; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the last link click data | ||||
|      * @returns {Object} The last link click data | ||||
|      */ | ||||
|     get lastLinkClickData() { | ||||
|       return this.#lastLinkClickData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Open a glance overlay with the specified data | ||||
|      * @param {Object} data - Glance data including URL, position, and dimensions | ||||
| @@ -289,6 +313,13 @@ | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (!data.height || !data.width) { | ||||
|         data = { | ||||
|           ...data, | ||||
|           ...this.lastLinkClickData, | ||||
|         }; | ||||
|       } | ||||
|  | ||||
|       this.#setAnimationState(true); | ||||
|       const currentTab = ownerTab ?? gBrowser.selectedTab; | ||||
|       const browserElement = this.#createBrowserElement(data.url, currentTab, existingTab); | ||||
| @@ -325,13 +356,14 @@ | ||||
|         gZenViewSplitter.onLocationChange(browserElement); | ||||
|         this.#prepareGlanceAnimation(data, browserElement); | ||||
|         if (data.width && data.height) { | ||||
|           // It is guaranteed that we will animate this opacity later on | ||||
|           // when we start animating the glance. | ||||
|           this.contentWrapper.style.opacity = 0; | ||||
|           data.elementData = await this.#getElementPreviewData(data); | ||||
|         } | ||||
|         this.#glances.get(this.#currentGlanceID).elementData = data.elementData; | ||||
|         window.requestAnimationFrame(() => { | ||||
|         this.#executeGlanceAnimation(data, browserElement, resolve); | ||||
|       }); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -344,7 +376,6 @@ | ||||
|       const newButtons = this.#createNewOverlayButtons(); | ||||
|       this.browserWrapper.appendChild(newButtons); | ||||
|  | ||||
|       this.#animateParentBackground(); | ||||
|       this.#setupGlancePositioning(data); | ||||
|       this.#configureBrowserElement(browserElement); | ||||
|     } | ||||
| @@ -488,7 +519,6 @@ | ||||
|       // nice fade-in effect to the content. But if it doesn't exist, | ||||
|       // we just fall back to always showing the browser directly. | ||||
|       if (data.elementData) { | ||||
|         this.contentWrapper.style.opacity = 0; | ||||
|         gZenUIManager.motion | ||||
|           .animate( | ||||
|             this.contentWrapper, | ||||
| @@ -503,6 +533,7 @@ | ||||
|           }); | ||||
|       } | ||||
|  | ||||
|       this.#animateParentBackground(); | ||||
|       gZenUIManager.motion | ||||
|         .animate(this.browserWrapper, arcSequence, { | ||||
|           duration: gZenUIManager.testingEnabled ? 0 : 0.4, | ||||
| @@ -990,7 +1021,7 @@ | ||||
|       if (!onTabClose) { | ||||
|         this.quickCloseGlance({ clearID: false }); | ||||
|       } | ||||
|       this.browserWrapper.style.display = 'none'; | ||||
|       this.overlay.style.display = 'none'; | ||||
|       this.overlay.removeAttribute('fade-out'); | ||||
|       this.browserWrapper.removeAttribute('animate'); | ||||
|  | ||||
| @@ -1357,18 +1388,9 @@ | ||||
|      * @param {Tab} tab - The tab to open glance for | ||||
|      */ | ||||
|     #openGlanceForTab(tab) { | ||||
|       const browserRect = window.windowUtils.getBoundsWithoutFlushing(gBrowser.tabbox); | ||||
|       const clickPosition = gZenUIManager._lastClickPosition || { | ||||
|         clientX: browserRect.width / 2, | ||||
|         clientY: browserRect.height / 2, | ||||
|       }; | ||||
|  | ||||
|       this.openGlance( | ||||
|         { | ||||
|           url: undefined, | ||||
|           ...clickPosition, | ||||
|           width: 0, | ||||
|           height: 0, | ||||
|         }, | ||||
|         tab, | ||||
|         tab.owner | ||||
|   | ||||
| @@ -35,22 +35,30 @@ export class ZenGlanceChild extends JSWindowActorChild { | ||||
|     return !(event.ctrlKey ^ event.altKey ^ event.shiftKey ^ event.metaKey); | ||||
|   } | ||||
|  | ||||
|   openGlance(target, originalTarget) { | ||||
|   #openGlance(target) { | ||||
|     let url = target.href; | ||||
|     // Add domain to relative URLs | ||||
|     if (!url.match(/^(?:[a-z]+:)?\/\//i)) { | ||||
|       url = this.contentWindow.location.origin + url; | ||||
|     } | ||||
|     this.sendAsyncMessage('ZenGlance:OpenGlance', { | ||||
|       url, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   #sendClickDataToParent(target, element) { | ||||
|     if (!element || !target) { | ||||
|       return; | ||||
|     } | ||||
|     // Get the largest element we can get. If the `A` element | ||||
|     // is a parent of the original target, use the anchor element, | ||||
|     // otherwise use the original target. | ||||
|     let rect = originalTarget.getBoundingClientRect(); | ||||
|     let rect = element.getBoundingClientRect(); | ||||
|     const anchorRect = target.getBoundingClientRect(); | ||||
|     if (anchorRect.width * anchorRect.height > rect.width * rect.height) { | ||||
|       rect = anchorRect; | ||||
|     } | ||||
|     this.sendAsyncMessage('ZenGlance:OpenGlance', { | ||||
|       url, | ||||
|     this.sendAsyncMessage('ZenGlance:RecordLinkClickData', { | ||||
|       clientX: rect.left, | ||||
|       clientY: rect.top, | ||||
|       width: rect.width, | ||||
| @@ -59,7 +67,19 @@ export class ZenGlanceChild extends JSWindowActorChild { | ||||
|   } | ||||
|  | ||||
|   handleClick(event) { | ||||
|     if (this.ensureOnlyKeyModifiers(event) || event.button !== 0 || event.defaultPrevented) { | ||||
|     if (event.button !== 0 || event.defaultPrevented) { | ||||
|       return; | ||||
|     } | ||||
|     // get closest A element | ||||
|     const target = event.target.closest('A'); | ||||
|     const elementToRecord = event.originalTarget || event.target; | ||||
|     // We record the link data anyway, even if the glance may be invoked | ||||
|     // or not. We have some cases where glance would open, for example, | ||||
|     // when clicking on a link with a different domain where glance would open. | ||||
|     // The problem is that at that stage we don't know the rect or even what | ||||
|     // element has been clicked, so we send the data here. | ||||
|     this.#sendClickDataToParent(target, elementToRecord); | ||||
|     if (this.ensureOnlyKeyModifiers(event)) { | ||||
|       return; | ||||
|     } | ||||
|     const activationMethod = this.#activationMethod; | ||||
| @@ -72,13 +92,11 @@ export class ZenGlanceChild extends JSWindowActorChild { | ||||
|     } else if (activationMethod === 'meta' && !event.metaKey) { | ||||
|       return; | ||||
|     } | ||||
|     // get closest A element | ||||
|     const target = event.target.closest('A'); | ||||
|     if (target) { | ||||
|       event.preventDefault(); | ||||
|       event.stopPropagation(); | ||||
|  | ||||
|       this.openGlance(target, event.originalTarget || event.target); | ||||
|       this.#openGlance(target); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,10 @@ export class ZenGlanceParent extends JSWindowActorParent { | ||||
|         this.browsingContext.topChromeWindow.gZenGlanceManager.closeGlance(params); | ||||
|         break; | ||||
|       } | ||||
|       case 'ZenGlance:RecordLinkClickData': { | ||||
|         this.browsingContext.topChromeWindow.gZenGlanceManager.lastLinkClickData = message.data; | ||||
|         break; | ||||
|       } | ||||
|       default: | ||||
|         console.warn(`[glance]: Unknown message: ${message.name}`); | ||||
|     } | ||||
|   | ||||
| @@ -171,10 +171,10 @@ | ||||
|   position: absolute; | ||||
|   pointer-events: none; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   z-index: 0; | ||||
|   border-radius: var(--zen-native-inner-radius); | ||||
|   inset: 50%; | ||||
|   translate: -50% -50%; | ||||
|   top: 0%; | ||||
|   left: 50%; | ||||
|   translate: -50% 0%; | ||||
|   will-change: transform, opacity; | ||||
| } | ||||
|   | ||||
| @@ -1,27 +0,0 @@ | ||||
| // 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| const FILE_NAME = 'zen-sessions.jsonlz4'; | ||||
|  | ||||
| export class nsZenSessionFile { | ||||
|   #path; | ||||
|  | ||||
|   #windows; | ||||
|  | ||||
|   constructor() { | ||||
|     this.#path = PathUtils.join(profileDir, FILE_NAME); | ||||
|   } | ||||
|  | ||||
|   async read() { | ||||
|     try { | ||||
|       return await IOUtils.readJSON(this.#path, { compress: true }); | ||||
|     } catch (e) { | ||||
|       return {}; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async write(data) { | ||||
|     await IOUtils.writeJSON(this.#path, data, { compress: true }); | ||||
|   } | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| // 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| import { | ||||
|   cancelIdleCallback, | ||||
|   clearTimeout, | ||||
|   requestIdleCallback, | ||||
|   setTimeout, | ||||
| } from 'resource://gre/modules/Timer.sys.mjs'; | ||||
|  | ||||
| const lazy = {}; | ||||
|  | ||||
| ChromeUtils.defineESModuleGetters(lazy, { | ||||
|   ZenSessionFile: 'resource://gre/modules/ZenSessionFile.sys.mjs', | ||||
|   PrivateBrowsingUtils: 'resource://gre/modules/PrivateBrowsingUtils.sys.mjs', | ||||
|   RunState: 'resource:///modules/sessionstore/RunState.sys.mjs', | ||||
| }); | ||||
|  | ||||
| class nsZenSessionManager { | ||||
|   #file; | ||||
|  | ||||
|   constructor() { | ||||
|     this.#file = null; | ||||
|   } | ||||
|  | ||||
|   get file() { | ||||
|     if (!this.#file) { | ||||
|       this.#file = lazy.ZenSessionFile; | ||||
|     } | ||||
|     return this.#file; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Saves the current session state. Collects data and writes to disk. | ||||
|    * | ||||
|    * @param forceUpdateAllWindows (optional) | ||||
|    *        Forces us to recollect data for all windows and will bypass and | ||||
|    *        update the corresponding caches. | ||||
|    */ | ||||
|   saveState(forceUpdateAllWindows = false) { | ||||
|     if (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) { | ||||
|       // Don't save (or even collect) anything in permanent private | ||||
|       // browsing mode | ||||
|       return Promise.resolve(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const ZenSessionStore = new nsZenSessionManager(); | ||||
| @@ -1,35 +0,0 @@ | ||||
| // 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| export class ZenSessionWindow { | ||||
|   #id; | ||||
|   #selectedWorkspace; | ||||
|   #selectedTab; | ||||
|  | ||||
|   constructor(id) { | ||||
|     this.#id = id; | ||||
|     this.#selectedWorkspace = null; | ||||
|     this.#selectedTab = null; | ||||
|   } | ||||
|  | ||||
|   get id() { | ||||
|     return this.#id; | ||||
|   } | ||||
|  | ||||
|   get selectedWorkspace() { | ||||
|     return this.#selectedWorkspace; | ||||
|   } | ||||
|  | ||||
|   set selectedWorkspace(workspace) { | ||||
|     this.#selectedWorkspace = workspace; | ||||
|   } | ||||
|  | ||||
|   get selectedTab() { | ||||
|     return this.#selectedTab; | ||||
|   } | ||||
|  | ||||
|   set selectedTab(tab) { | ||||
|     this.#selectedTab = tab; | ||||
|   } | ||||
| } | ||||
| @@ -101,7 +101,6 @@ | ||||
|     } | ||||
|  | ||||
|     onTabIconChanged(tab, url = null) { | ||||
|       tab.dispatchEvent(new CustomEvent('ZenTabIconChanged', { bubbles: true, detail: { tab } })); | ||||
|       const iconUrl = url ?? tab.iconImage.src; | ||||
|       if (!iconUrl && tab.hasAttribute('zen-pin-id')) { | ||||
|         try { | ||||
| @@ -1556,7 +1555,6 @@ | ||||
|     } | ||||
|  | ||||
|     async onTabLabelChanged(tab) { | ||||
|       tab.dispatchEvent(new CustomEvent('ZenTabLabelChanged', { detail: { tab } })); | ||||
|       if (!this._pinsCache) { | ||||
|         return; | ||||
|       } | ||||
|   | ||||
| @@ -22,9 +22,7 @@ z-index: 1; | ||||
|  | ||||
| %include ../../compact-mode/windows-captions-fix-active.inc.css | ||||
|  | ||||
|   &:not([zen-has-hover='true']):not([has-popup-menu]):not(:focus-within):not( | ||||
|       :has(*:is([panelopen='true'], [open='true'])) | ||||
|     ) { | ||||
|   &:not([zen-has-hover='true']):not([has-popup-menu]):not([zen-compact-mode-active]) { | ||||
|     height: var(--zen-element-separation); | ||||
|     opacity: 0; | ||||
|     & #zen-appcontent-navbar-container { | ||||
|   | ||||
| @@ -963,7 +963,7 @@ | ||||
|   :root[zen-single-toolbar='true'] & { | ||||
|     --zen-toolbar-height: 36px; | ||||
|     @media (-moz-platform: macos) { | ||||
|       --zen-toolbar-height: 42px; | ||||
|       --zen-toolbar-height: 38px; | ||||
|     } | ||||
|  | ||||
|     & #PanelUI-button { | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/zen/tests/manifest.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/zen/tests/manifest.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
|  | ||||
| # 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| [reportbrokensite] | ||||
| source = "browser/components/reportbrokensite/test/browser" | ||||
| is_direct_path = true | ||||
| disable = [ | ||||
|   "browser_addon_data_sent.js" | ||||
| ] | ||||
|  | ||||
| [reportbrokensite.replace-manifest] | ||||
| "../../../../../" = "../../../../" | ||||
|  | ||||
| [safebrowsing] | ||||
| source = "browser/components/safebrowsing/content/test" | ||||
| is_direct_path = true | ||||
|  | ||||
| [shell] | ||||
| source = "browser/components/shell/test" | ||||
| is_direct_path = true | ||||
|  | ||||
| [tooltiptext] | ||||
| source = "toolkit/components/tooltiptext" | ||||
|  | ||||
| [translations] | ||||
| source = "browser/components/translations/tests/browser" | ||||
| is_direct_path = true | ||||
							
								
								
									
										15
									
								
								src/zen/tests/mochitests/moz.build
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/zen/tests/mochitests/moz.build
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
|  | ||||
| # 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| # This file is autogenerated by scripts/import_external_tests.py | ||||
| # Do not edit manually. | ||||
|  | ||||
| BROWSER_CHROME_MANIFESTS += [ | ||||
| 	"reportbrokensite/browser.toml", | ||||
| 	"safebrowsing/browser.toml", | ||||
| 	"shell/browser.toml", | ||||
| 	"tooltiptext/browser.toml", | ||||
| 	"translations/browser.toml", | ||||
| ] | ||||
							
								
								
									
										50
									
								
								src/zen/tests/mochitests/reportbrokensite/browser.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/zen/tests/mochitests/reportbrokensite/browser.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| [DEFAULT] | ||||
| tags = "report-broken-site" | ||||
| support-files = [ | ||||
|   "example_report_page.html", | ||||
|   "head.js", | ||||
|   "sendMoreInfoTestEndpoint.html", | ||||
| ] | ||||
|  | ||||
| ["browser_addon_data_sent.js"] | ||||
| disabled="Disabled by import_external_tests.py" | ||||
| support-files = [ "send_more_info.js" ] | ||||
| skip-if = ["os == 'win' && os_version == '11.26100' && processor == 'x86_64' && opt"] # Bug 1955805 | ||||
|  | ||||
| ["browser_antitracking_data_sent.js"] | ||||
| support-files = [ "send_more_info.js" ] | ||||
|  | ||||
| ["browser_back_buttons.js"] | ||||
|  | ||||
| ["browser_error_messages.js"] | ||||
|  | ||||
| ["browser_experiment_data_sent.js"] | ||||
| support-files = [ "send_more_info.js" ] | ||||
|  | ||||
| ["browser_keyboard_navigation.js"] | ||||
| skip-if = ["os == 'linux' && os_version == '24.04' && processor == 'x86_64' && tsan"] # Bug 1867132 | ||||
|  | ||||
| ["browser_parent_menuitems.js"] | ||||
|  | ||||
| ["browser_prefers_contrast.js"] | ||||
|  | ||||
| ["browser_reason_dropdown.js"] | ||||
|  | ||||
| ["browser_report_send.js"] | ||||
| support-files = [ "send.js" ] | ||||
|  | ||||
| ["browser_send_more_info.js"] | ||||
| support-files = [ | ||||
|   "send_more_info.js", | ||||
|   "../../../../toolkit/components/gfx/content/videotest.mp4", | ||||
| ] | ||||
|  | ||||
| ["browser_tab_key_order.js"] | ||||
|  | ||||
| ["browser_tab_switch_handling.js"] | ||||
|  | ||||
| ["browser_webcompat.com_fallback.js"] | ||||
| support-files = [ | ||||
|   "send_more_info.js", | ||||
|   "../../../../toolkit/components/gfx/content/videotest.mp4", | ||||
| ] | ||||
| @@ -0,0 +1,99 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that the right data is sent for | ||||
|  * private windows and when ETP blocks content. | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from send.js */ | ||||
| /* import-globals-from send_more_info.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| const { AddonTestUtils } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/AddonTestUtils.sys.mjs" | ||||
| ); | ||||
| AddonTestUtils.initMochitest(this); | ||||
|  | ||||
| Services.scriptloader.loadSubScript( | ||||
|   getRootDirectory(gTestPath) + "send_more_info.js", | ||||
|   this | ||||
| ); | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| const TEMP_ID = "testtempaddon@tests.mozilla.org"; | ||||
| const TEMP_NAME = "Temporary Addon"; | ||||
| const TEMP_VERSION = "0.1.0"; | ||||
|  | ||||
| const PERM_ID = "testpermaddon@tests.mozilla.org"; | ||||
| const PERM_NAME = "Permanent Addon"; | ||||
| const PERM_VERSION = "0.2.0"; | ||||
|  | ||||
| const DISABLED_ID = "testdisabledaddon@tests.mozilla.org"; | ||||
| const DISABLED_NAME = "Disabled Addon"; | ||||
| const DISABLED_VERSION = "0.3.0"; | ||||
|  | ||||
| const EXPECTED_ADDONS = [ | ||||
|   { id: PERM_ID, name: PERM_NAME, temporary: false, version: PERM_VERSION }, | ||||
|   { id: TEMP_ID, name: TEMP_NAME, temporary: true, version: TEMP_VERSION }, | ||||
| ]; | ||||
|  | ||||
| function loadAddon(id, name, version, isTemp = false) { | ||||
|   return ExtensionTestUtils.loadExtension({ | ||||
|     manifest: { | ||||
|       browser_specific_settings: { gecko: { id } }, | ||||
|       name, | ||||
|       version, | ||||
|     }, | ||||
|     useAddonManager: isTemp ? "temporary" : "permanent", | ||||
|   }); | ||||
| } | ||||
|  | ||||
| async function installAddons() { | ||||
|   const temp = await loadAddon(TEMP_ID, TEMP_NAME, TEMP_VERSION, true); | ||||
|   await temp.startup(); | ||||
|  | ||||
|   const perm = await loadAddon(PERM_ID, PERM_NAME, PERM_VERSION); | ||||
|   await perm.startup(); | ||||
|  | ||||
|   const dis = await loadAddon(DISABLED_ID, DISABLED_NAME, DISABLED_VERSION); | ||||
|   await dis.startup(); | ||||
|   await (await AddonManager.getAddonByID(DISABLED_ID)).disable(); | ||||
|  | ||||
|   return async () => { | ||||
|     await temp.unload(); | ||||
|     await perm.unload(); | ||||
|     await dis.unload(); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| add_task(async function testSendButton() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReasonOptional(); | ||||
|   const addonCleanup = await installAddons(); | ||||
|  | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testSend(tab, AppMenu(), { | ||||
|     addons: EXPECTED_ADDONS, | ||||
|   }); | ||||
|  | ||||
|   closeTab(tab); | ||||
|   await addonCleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function testSendingMoreInfo() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureSendMoreInfoEnabled(); | ||||
|   const addonCleanup = await installAddons(); | ||||
|  | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testSendMoreInfo(tab, HelpMenu(), { | ||||
|     addons: EXPECTED_ADDONS, | ||||
|   }); | ||||
|  | ||||
|   closeTab(tab); | ||||
|   await addonCleanup(); | ||||
| }); | ||||
| @@ -0,0 +1,117 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that the right data is sent for | ||||
|  * private windows and when ETP blocks content. | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from send.js */ | ||||
| /* import-globals-from send_more_info.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| Services.scriptloader.loadSubScript( | ||||
|   getRootDirectory(gTestPath) + "send_more_info.js", | ||||
|   this | ||||
| ); | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| add_task(setupStrictETP); | ||||
|  | ||||
| add_task(async function testSendButton() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReasonOptional(); | ||||
|  | ||||
|   const win = await BrowserTestUtils.openNewBrowserWindow({ private: true }); | ||||
|   const blockedPromise = waitForContentBlockingEvent(3, win); | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL3, win); | ||||
|   await blockedPromise; | ||||
|  | ||||
|   await testSend(tab, AppMenu(win), { | ||||
|     breakageCategory: "adblocker", | ||||
|     description: "another test description", | ||||
|     antitracking: { | ||||
|       blockList: "strict", | ||||
|       isPrivateBrowsing: true, | ||||
|       hasTrackingContentBlocked: true, | ||||
|       hasMixedActiveContentBlocked: true, | ||||
|       hasMixedDisplayContentBlocked: true, | ||||
|       btpHasPurgedSite: false, | ||||
|       etpCategory: "strict", | ||||
|     }, | ||||
|     frameworks: { | ||||
|       fastclick: true, | ||||
|       marfeel: true, | ||||
|       mobify: true, | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   await BrowserTestUtils.closeWindow(win); | ||||
| }); | ||||
|  | ||||
| add_task(async function testSendingMoreInfo() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureSendMoreInfoEnabled(); | ||||
|  | ||||
|   const win = await BrowserTestUtils.openNewBrowserWindow({ private: true }); | ||||
|   const blockedPromise = waitForContentBlockingEvent(3, win); | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL3, win); | ||||
|   await blockedPromise; | ||||
|  | ||||
|   await testSendMoreInfo(tab, HelpMenu(win), { | ||||
|     antitracking: { | ||||
|       blockList: "strict", | ||||
|       isPrivateBrowsing: true, | ||||
|       hasTrackingContentBlocked: true, | ||||
|       hasMixedActiveContentBlocked: true, | ||||
|       hasMixedDisplayContentBlocked: true, | ||||
|       btpHasPurgedSite: false, | ||||
|       etpCategory: "strict", | ||||
|     }, | ||||
|     frameworks: { fastclick: true, mobify: true, marfeel: true }, | ||||
|     consoleLog: [ | ||||
|       { | ||||
|         level: "error", | ||||
|         log(actual) { | ||||
|           // "Blocked loading mixed display content http://example.com/tests/image/test/mochitest/blue.png" | ||||
|           return ( | ||||
|             Array.isArray(actual) && | ||||
|             actual.length == 1 && | ||||
|             actual[0].includes("blue.png") | ||||
|           ); | ||||
|         }, | ||||
|         pos: "0:1", | ||||
|         uri: REPORTABLE_PAGE_URL3, | ||||
|       }, | ||||
|       { | ||||
|         level: "error", | ||||
|         log(actual) { | ||||
|           // "Blocked loading mixed active content http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html", | ||||
|           return ( | ||||
|             Array.isArray(actual) && | ||||
|             actual.length == 1 && | ||||
|             actual[0].includes("benignPage.html") | ||||
|           ); | ||||
|         }, | ||||
|         pos: "0:1", | ||||
|         uri: REPORTABLE_PAGE_URL3, | ||||
|       }, | ||||
|       { | ||||
|         level: "warn", | ||||
|         log(actual) { | ||||
|           // "The resource at https://trackertest.org/ was blocked because content blocking is enabled.", | ||||
|           return ( | ||||
|             Array.isArray(actual) && | ||||
|             actual.length == 1 && | ||||
|             actual[0].includes("trackertest.org") | ||||
|           ); | ||||
|         }, | ||||
|         pos: "0:1", | ||||
|         uri: REPORTABLE_PAGE_URL3, | ||||
|       }, | ||||
|     ], | ||||
|   }); | ||||
|  | ||||
|   await BrowserTestUtils.closeWindow(win); | ||||
| }); | ||||
| @@ -0,0 +1,34 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that Report Broken Site popups will be | ||||
|  * reset to whichever tab the user is on as they change | ||||
|  * between windows and tabs. */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| add_task(async function testBackButtonsAreAdded() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     let rbs = await AppMenu().openReportBrokenSite(); | ||||
|     rbs.isBackButtonEnabled(); | ||||
|     await rbs.clickBack(); | ||||
|     await rbs.close(); | ||||
|  | ||||
|     rbs = await HelpMenu().openReportBrokenSite(); | ||||
|     ok(!rbs.backButton, "Back button is not shown for Help Menu"); | ||||
|     await rbs.close(); | ||||
|  | ||||
|     rbs = await ProtectionsPanel().openReportBrokenSite(); | ||||
|     rbs.isBackButtonEnabled(); | ||||
|     await rbs.clickBack(); | ||||
|     await rbs.close(); | ||||
|  | ||||
|     rbs = await HelpMenu().openReportBrokenSite(); | ||||
|     ok(!rbs.backButton, "Back button is not shown for Help Menu"); | ||||
|     await rbs.close(); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,64 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Test that the Report Broken Site errors messages are shown on | ||||
|  * the UI if the user enters an invalid URL or clicks the send | ||||
|  * button while it is disabled due to not selecting a "reason" | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| add_task(async function test() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReasonRequired(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     for (const menu of [AppMenu(), ProtectionsPanel(), HelpMenu()]) { | ||||
|       const rbs = await menu.openReportBrokenSite(); | ||||
|       const { sendButton, URLInput } = rbs; | ||||
|  | ||||
|       rbs.isURLInvalidMessageHidden(); | ||||
|       rbs.isReasonNeededMessageHidden(); | ||||
|  | ||||
|       rbs.setURL(""); | ||||
|       window.document.activeElement.blur(); | ||||
|       rbs.isURLInvalidMessageShown(); | ||||
|       rbs.isReasonNeededMessageHidden(); | ||||
|  | ||||
|       rbs.setURL("https://asdf"); | ||||
|       window.document.activeElement.blur(); | ||||
|       rbs.isURLInvalidMessageHidden(); | ||||
|       rbs.isReasonNeededMessageHidden(); | ||||
|  | ||||
|       rbs.setURL("http:/ /asdf"); | ||||
|       window.document.activeElement.blur(); | ||||
|       rbs.isURLInvalidMessageShown(); | ||||
|       rbs.isReasonNeededMessageHidden(); | ||||
|  | ||||
|       rbs.setURL("https://asdf"); | ||||
|       const selectPromise = BrowserTestUtils.waitForSelectPopupShown(window); | ||||
|       EventUtils.synthesizeMouseAtCenter(sendButton, {}, window); | ||||
|       await selectPromise; | ||||
|       rbs.isURLInvalidMessageHidden(); | ||||
|       rbs.isReasonNeededMessageShown(); | ||||
|       await rbs.dismissDropdownPopup(); | ||||
|  | ||||
|       rbs.chooseReason("slow"); | ||||
|       rbs.isURLInvalidMessageHidden(); | ||||
|       rbs.isReasonNeededMessageHidden(); | ||||
|  | ||||
|       rbs.setURL(""); | ||||
|       rbs.chooseReason("choose"); | ||||
|       window.ownerGlobal.document.activeElement?.blur(); | ||||
|       const focusPromise = BrowserTestUtils.waitForEvent(URLInput, "focus"); | ||||
|       EventUtils.synthesizeMouseAtCenter(sendButton, {}, window); | ||||
|       await focusPromise; | ||||
|       rbs.isURLInvalidMessageShown(); | ||||
|       rbs.isReasonNeededMessageShown(); | ||||
|  | ||||
|       rbs.clickCancel(); | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,88 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that the right data is sent for | ||||
|  * private windows and when ETP blocks content. | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from send.js */ | ||||
| /* import-globals-from send_more_info.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| Services.scriptloader.loadSubScript( | ||||
|   getRootDirectory(gTestPath) + "send_more_info.js", | ||||
|   this | ||||
| ); | ||||
|  | ||||
| const { ExperimentAPI } = ChromeUtils.importESModule( | ||||
|   "resource://nimbus/ExperimentAPI.sys.mjs" | ||||
| ); | ||||
| const { NimbusTestUtils } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/NimbusTestUtils.sys.mjs" | ||||
| ); | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| const EXPECTED_EXPERIMENTS_IN_REPORT = [ | ||||
|   { slug: "test-experiment", branch: "branch", kind: "nimbusExperiment" }, | ||||
|   { slug: "test-experiment-rollout", branch: "branch", kind: "nimbusRollout" }, | ||||
| ]; | ||||
|  | ||||
| let EXPERIMENT_CLEANUPS; | ||||
|  | ||||
| add_setup(async function () { | ||||
|   await ExperimentAPI.ready(); | ||||
|   EXPERIMENT_CLEANUPS = [ | ||||
|     await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|       { featureId: "no-feature-firefox-desktop", value: {} }, | ||||
|       { slug: "test-experiment", branchSlug: "branch" } | ||||
|     ), | ||||
|     await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|       { featureId: "no-feature-firefox-desktop", value: {} }, | ||||
|       { slug: "test-experiment-rollout", isRollout: true, branchSlug: "branch" } | ||||
|     ), | ||||
|     async () => { | ||||
|       ExperimentAPI.manager.store._deleteForTests("test-experiment-disabled"); | ||||
|       await NimbusTestUtils.flushStore(); | ||||
|     }, | ||||
|   ]; | ||||
|  | ||||
|   await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { featureId: "no-feature-firefox-desktop", value: {} }, | ||||
|     { slug: "test-experiment-disabled" } | ||||
|   ); | ||||
|   await ExperimentAPI.manager.unenroll("test-experiment-disabled"); | ||||
| }); | ||||
|  | ||||
| add_task(async function testSendButton() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReasonOptional(); | ||||
|  | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testSend(tab, AppMenu(), { | ||||
|     experiments: EXPECTED_EXPERIMENTS_IN_REPORT, | ||||
|   }); | ||||
|  | ||||
|   closeTab(tab); | ||||
| }); | ||||
|  | ||||
| add_task(async function testSendingMoreInfo() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureSendMoreInfoEnabled(); | ||||
|  | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testSendMoreInfo(tab, HelpMenu(), { | ||||
|     experiments: EXPECTED_EXPERIMENTS_IN_REPORT, | ||||
|   }); | ||||
|  | ||||
|   closeTab(tab); | ||||
| }); | ||||
|  | ||||
| add_task(async function teardown() { | ||||
|   for (const cleanup of EXPERIMENT_CLEANUPS) { | ||||
|     await cleanup(); | ||||
|   } | ||||
| }); | ||||
| @@ -0,0 +1,107 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that sending or canceling reports with | ||||
|  * the Send and Cancel buttons work (as well as the Okay button) | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| requestLongerTimeout(2); | ||||
|  | ||||
| async function testPressingKey(key, tabToMatch, makePromise, followUp) { | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     for (const menu of [AppMenu(), ProtectionsPanel(), HelpMenu()]) { | ||||
|       info( | ||||
|         `Opening RBS to test pressing ${key} for ${tabToMatch} on ${menu.menuDescription}` | ||||
|       ); | ||||
|       const rbs = await menu.openReportBrokenSite(); | ||||
|       const promise = makePromise(rbs); | ||||
|       if (tabToMatch) { | ||||
|         if (await tabTo(tabToMatch)) { | ||||
|           await pressKeyAndAwait(promise, key); | ||||
|           followUp && (await followUp(rbs)); | ||||
|           await rbs.close(); | ||||
|           ok(true, `was able to activate ${tabToMatch} with keyboard`); | ||||
|         } else { | ||||
|           await rbs.close(); | ||||
|           ok(false, `could not tab to ${tabToMatch}`); | ||||
|         } | ||||
|       } else { | ||||
|         await pressKeyAndAwait(promise, key); | ||||
|         followUp && (await followUp(rbs)); | ||||
|         await rbs.close(); | ||||
|         ok(true, `was able to use keyboard`); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  | ||||
| add_task(async function testSendMoreInfo() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureSendMoreInfoEnabled(); | ||||
|   await testPressingKey( | ||||
|     "KEY_Enter", | ||||
|     "#report-broken-site-popup-send-more-info-link", | ||||
|     rbs => rbs.waitForSendMoreInfoTab(), | ||||
|     () => gBrowser.removeCurrentTab() | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function testCancel() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   await testPressingKey( | ||||
|     "KEY_Enter", | ||||
|     "#report-broken-site-popup-cancel-button", | ||||
|     rbs => BrowserTestUtils.waitForEvent(rbs.mainView, "ViewHiding") | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function testSendAndOkay() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   await testPressingKey( | ||||
|     "KEY_Enter", | ||||
|     "#report-broken-site-popup-send-button", | ||||
|     rbs => rbs.awaitReportSentViewOpened(), | ||||
|     async rbs => { | ||||
|       await tabTo("#report-broken-site-popup-okay-button"); | ||||
|       const promise = BrowserTestUtils.waitForEvent(rbs.sentView, "ViewHiding"); | ||||
|       await pressKeyAndAwait(promise, "KEY_Enter"); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function testESCOnMain() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   await testPressingKey("KEY_Escape", undefined, rbs => | ||||
|     BrowserTestUtils.waitForEvent(rbs.mainView, "ViewHiding") | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function testESCOnSent() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   await testPressingKey( | ||||
|     "KEY_Enter", | ||||
|     "#report-broken-site-popup-send-button", | ||||
|     rbs => rbs.awaitReportSentViewOpened(), | ||||
|     async rbs => { | ||||
|       const promise = BrowserTestUtils.waitForEvent(rbs.sentView, "ViewHiding"); | ||||
|       await pressKeyAndAwait(promise, "KEY_Escape"); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function testBackButtons() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     for (const menu of [AppMenu(), ProtectionsPanel()]) { | ||||
|       await menu.openReportBrokenSite(); | ||||
|       await tabTo("#report-broken-site-popup-mainView .subviewbutton-back"); | ||||
|       const promise = BrowserTestUtils.waitForEvent(menu.popup, "ViewShown"); | ||||
|       await pressKeyAndAwait(promise, "KEY_Enter"); | ||||
|       menu.close(); | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,96 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Test that the Report Broken Site menu items are disabled | ||||
|  * when the active tab is not on a reportable URL, and is hidden | ||||
|  * when the feature is disabled via pref. Also ensure that the | ||||
|  * Report Broken Site item that is automatically generated in | ||||
|  * the app menu's help sub-menu is hidden. | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| add_task(async function testMenus() { | ||||
|   ensureReportBrokenSitePreffedOff(); | ||||
|  | ||||
|   const appMenu = AppMenu(); | ||||
|   const menus = [appMenu, ProtectionsPanel(), HelpMenu()]; | ||||
|  | ||||
|   async function forceMenuItemStateUpdate() { | ||||
|     ReportBrokenSite.enableOrDisableMenuitems(window); | ||||
|  | ||||
|     // the hidden/disabled state of all of the menuitems may not update until one | ||||
|     // is rendered; then the related <command>'s state is propagated to them all. | ||||
|     await appMenu.open(); | ||||
|     await appMenu.close(); | ||||
|   } | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab("about:blank", async function () { | ||||
|     await forceMenuItemStateUpdate(); | ||||
|     for (const { menuDescription, reportBrokenSite } of menus) { | ||||
|       isMenuItemHidden( | ||||
|         reportBrokenSite, | ||||
|         `${menuDescription} option hidden on invalid page when preffed off` | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     await forceMenuItemStateUpdate(); | ||||
|     for (const { menuDescription, reportBrokenSite } of menus) { | ||||
|       isMenuItemHidden( | ||||
|         reportBrokenSite, | ||||
|         `${menuDescription} option hidden on valid page when preffed off` | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab("about:blank", async function () { | ||||
|     await forceMenuItemStateUpdate(); | ||||
|     for (const { menuDescription, reportBrokenSite } of menus) { | ||||
|       isMenuItemDisabled( | ||||
|         reportBrokenSite, | ||||
|         `${menuDescription} option disabled on invalid page when preffed on` | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     await forceMenuItemStateUpdate(); | ||||
|     for (const { menuDescription, reportBrokenSite } of menus) { | ||||
|       isMenuItemEnabled( | ||||
|         reportBrokenSite, | ||||
|         `${menuDescription} option enabled on valid page when preffed on` | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   ensureReportBrokenSitePreffedOff(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     await forceMenuItemStateUpdate(); | ||||
|     for (const { menuDescription, reportBrokenSite } of menus) { | ||||
|       isMenuItemHidden( | ||||
|         reportBrokenSite, | ||||
|         `${menuDescription} option hidden again when pref toggled back off` | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReportBrokenSiteDisabledByPolicy(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     await forceMenuItemStateUpdate(); | ||||
|     for (const { menuDescription, reportBrokenSite } of menus) { | ||||
|       isMenuItemHidden( | ||||
|         reportBrokenSite, | ||||
|         `${menuDescription} option hidden when disabled by DisableFeedbackCommands enterprise policy` | ||||
|       ); | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,56 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Test that the background color of the "report sent" | ||||
|  * view is not green in non-default contrast modes. | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| const HIGH_CONTRAST_MODE_OFF = [[PREFS.USE_ACCESSIBILITY_THEME, 0]]; | ||||
|  | ||||
| const HIGH_CONTRAST_MODE_ON = [[PREFS.USE_ACCESSIBILITY_THEME, 1]]; | ||||
|  | ||||
| add_task(async function testReportSentViewBGColor() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReasonDisabled(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     REPORTABLE_PAGE_URL, | ||||
|     async function (browser) { | ||||
|       const { defaultView } = browser.ownerGlobal.document; | ||||
|  | ||||
|       const menu = AppMenu(); | ||||
|  | ||||
|       await SpecialPowers.pushPrefEnv({ set: HIGH_CONTRAST_MODE_OFF }); | ||||
|       const rbs = await menu.openReportBrokenSite(); | ||||
|       const { mainView, sentView } = rbs; | ||||
|       mainView.style.backgroundColor = "var(--background-color-success)"; | ||||
|       const expectedReportSentBGColor = | ||||
|         defaultView.getComputedStyle(mainView).backgroundColor; | ||||
|       mainView.style.backgroundColor = ""; | ||||
|       const expectedPrefersReducedBGColor = | ||||
|         defaultView.getComputedStyle(mainView).backgroundColor; | ||||
|  | ||||
|       await rbs.clickSend(); | ||||
|       is( | ||||
|         defaultView.getComputedStyle(sentView).backgroundColor, | ||||
|         expectedReportSentBGColor, | ||||
|         "Using green bgcolor when not prefers-contrast" | ||||
|       ); | ||||
|       await rbs.clickOkay(); | ||||
|  | ||||
|       await SpecialPowers.pushPrefEnv({ set: HIGH_CONTRAST_MODE_ON }); | ||||
|       await menu.openReportBrokenSite(); | ||||
|       await rbs.clickSend(); | ||||
|       is( | ||||
|         defaultView.getComputedStyle(sentView).backgroundColor, | ||||
|         expectedPrefersReducedBGColor, | ||||
|         "Using default bgcolor when prefers-contrast" | ||||
|       ); | ||||
|       await rbs.clickOkay(); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,156 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that the reason dropdown is shown or hidden | ||||
|  * based on its pref, and that its optional and required modes affect | ||||
|  * the Send button and report appropriately. | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| requestLongerTimeout(2); | ||||
|  | ||||
| async function clickSendAndCheckPing(rbs, expectedReason = null) { | ||||
|   await GleanPings.brokenSiteReport.testSubmission( | ||||
|     () => | ||||
|       Assert.equal( | ||||
|         Glean.brokenSiteReport.breakageCategory.testGetValue(), | ||||
|         expectedReason | ||||
|       ), | ||||
|     () => rbs.clickSend() | ||||
|   ); | ||||
| } | ||||
|  | ||||
| add_task(async function testReasonDropdown() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     ensureReasonDisabled(); | ||||
|  | ||||
|     let rbs = await AppMenu().openReportBrokenSite(); | ||||
|     await rbs.isReasonHidden(); | ||||
|     await rbs.isSendButtonEnabled(); | ||||
|     await clickSendAndCheckPing(rbs); | ||||
|     await rbs.clickOkay(); | ||||
|  | ||||
|     ensureReasonOptional(); | ||||
|     rbs = await AppMenu().openReportBrokenSite(); | ||||
|     await rbs.isReasonOptional(); | ||||
|     await rbs.isSendButtonEnabled(); | ||||
|     await clickSendAndCheckPing(rbs); | ||||
|     await rbs.clickOkay(); | ||||
|  | ||||
|     rbs = await AppMenu().openReportBrokenSite(); | ||||
|     await rbs.isReasonOptional(); | ||||
|     rbs.chooseReason("slow"); | ||||
|     await rbs.isSendButtonEnabled(); | ||||
|     await clickSendAndCheckPing(rbs, "slow"); | ||||
|     await rbs.clickOkay(); | ||||
|  | ||||
|     ensureReasonRequired(); | ||||
|     rbs = await AppMenu().openReportBrokenSite(); | ||||
|     await rbs.isReasonRequired(); | ||||
|     await rbs.isSendButtonEnabled(); | ||||
|     const selectPromise = BrowserTestUtils.waitForSelectPopupShown(window); | ||||
|     EventUtils.synthesizeMouseAtCenter(rbs.sendButton, {}, window); | ||||
|     await selectPromise; | ||||
|     rbs.chooseReason("media"); | ||||
|     await rbs.dismissDropdownPopup(); | ||||
|     await rbs.isSendButtonEnabled(); | ||||
|     await clickSendAndCheckPing(rbs, "media"); | ||||
|     await rbs.clickOkay(); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| async function getListItems(rbs) { | ||||
|   const items = Array.from(rbs.reasonInput.querySelectorAll("option")).map(i => | ||||
|     i.id.replace("report-broken-site-popup-reason-", "") | ||||
|   ); | ||||
|   Assert.equal(items[0], "choose", "First option is always 'choose'"); | ||||
|   return items.join(","); | ||||
| } | ||||
|  | ||||
| add_task(async function testReasonDropdownRandomized() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReasonOptional(); | ||||
|  | ||||
|   const USER_ID_PREF = "app.normandy.user_id"; | ||||
|   const RANDOMIZE_PREF = "ui.new-webcompat-reporter.reason-dropdown.randomized"; | ||||
|  | ||||
|   const origNormandyUserID = Services.prefs.getCharPref( | ||||
|     USER_ID_PREF, | ||||
|     undefined | ||||
|   ); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     // confirm that the default order is initially used | ||||
|     Services.prefs.setBoolPref(RANDOMIZE_PREF, false); | ||||
|     const rbs = await AppMenu().openReportBrokenSite(); | ||||
|     const defaultOrder = [ | ||||
|       "choose", | ||||
|       "checkout", | ||||
|       "load", | ||||
|       "slow", | ||||
|       "media", | ||||
|       "content", | ||||
|       "account", | ||||
|       "adblocker", | ||||
|       "notsupported", | ||||
|       "other", | ||||
|     ]; | ||||
|     Assert.deepEqual( | ||||
|       await getListItems(rbs), | ||||
|       defaultOrder, | ||||
|       "non-random order is correct" | ||||
|     ); | ||||
|  | ||||
|     // confirm that a random order happens per user | ||||
|     let randomOrder; | ||||
|     let isRandomized = false; | ||||
|     Services.prefs.setBoolPref(RANDOMIZE_PREF, true); | ||||
|  | ||||
|     // This becomes ClientEnvironment.randomizationId, which we can set to | ||||
|     // any value which results in a different order from the default ordering. | ||||
|     Services.prefs.setCharPref("app.normandy.user_id", "dummy"); | ||||
|  | ||||
|     // clicking cancel triggers a reset, which is when the randomization | ||||
|     // logic is called. so we must click cancel after pref-changes here. | ||||
|     rbs.clickCancel(); | ||||
|     await AppMenu().openReportBrokenSite(); | ||||
|     randomOrder = await getListItems(rbs); | ||||
|     Assert.notEqual( | ||||
|       randomOrder, | ||||
|       defaultOrder, | ||||
|       "options are randomized with pref on" | ||||
|     ); | ||||
|  | ||||
|     // confirm that the order doesn't change per user | ||||
|     isRandomized = false; | ||||
|     for (let attempt = 0; attempt < 5; ++attempt) { | ||||
|       rbs.clickCancel(); | ||||
|       await AppMenu().openReportBrokenSite(); | ||||
|       const order = await getListItems(rbs); | ||||
|  | ||||
|       if (order != randomOrder) { | ||||
|         isRandomized = true; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     Assert.ok(!isRandomized, "options keep the same order per user"); | ||||
|  | ||||
|     // confirm that the order reverts to the default if pref flipped to false | ||||
|     Services.prefs.setBoolPref(RANDOMIZE_PREF, false); | ||||
|     rbs.clickCancel(); | ||||
|     await AppMenu().openReportBrokenSite(); | ||||
|     Assert.deepEqual( | ||||
|       defaultOrder, | ||||
|       await getListItems(rbs), | ||||
|       "reverts to non-random order correctly" | ||||
|     ); | ||||
|     rbs.clickCancel(); | ||||
|   }); | ||||
|  | ||||
|   Services.prefs.setCharPref(USER_ID_PREF, origNormandyUserID); | ||||
| }); | ||||
| @@ -0,0 +1,79 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that sending or canceling reports with | ||||
|  * the Send and Cancel buttons work (as well as the Okay button) | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from send.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| Services.scriptloader.loadSubScript( | ||||
|   getRootDirectory(gTestPath) + "send.js", | ||||
|   this | ||||
| ); | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| requestLongerTimeout(10); | ||||
|  | ||||
| async function testCancel(menu, url, description) { | ||||
|   let rbs = await menu.openAndPrefillReportBrokenSite(url, description); | ||||
|   await rbs.clickCancel(); | ||||
|   ok(!rbs.opened, "clicking Cancel closes Report Broken Site"); | ||||
|  | ||||
|   // re-opening the panel, the url and description should be reset | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   rbs.isMainViewResetToCurrentTab(); | ||||
|   rbs.close(); | ||||
| } | ||||
|  | ||||
| add_task(async function testSendButton() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureReasonOptional(); | ||||
|  | ||||
|   const tab1 = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testSend(tab1, AppMenu()); | ||||
|  | ||||
|   const tab2 = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testSend(tab2, ProtectionsPanel(), { | ||||
|     url: "https://test.org/test/#fake", | ||||
|     breakageCategory: "media", | ||||
|     description: "test description", | ||||
|   }); | ||||
|  | ||||
|   closeTab(tab1); | ||||
|   closeTab(tab2); | ||||
| }); | ||||
|  | ||||
| add_task(async function testCancelButton() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|  | ||||
|   const tab1 = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testCancel(AppMenu()); | ||||
|   await testCancel(ProtectionsPanel()); | ||||
|   await testCancel(HelpMenu()); | ||||
|  | ||||
|   const tab2 = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testCancel(AppMenu()); | ||||
|   await testCancel(ProtectionsPanel()); | ||||
|   await testCancel(HelpMenu()); | ||||
|  | ||||
|   const win2 = await BrowserTestUtils.openNewBrowserWindow(); | ||||
|   const tab3 = await openTab(REPORTABLE_PAGE_URL2, win2); | ||||
|  | ||||
|   await testCancel(AppMenu(win2)); | ||||
|   await testCancel(ProtectionsPanel(win2)); | ||||
|   await testCancel(HelpMenu(win2)); | ||||
|  | ||||
|   closeTab(tab3); | ||||
|   await BrowserTestUtils.closeWindow(win2); | ||||
|  | ||||
|   closeTab(tab1); | ||||
|   closeTab(tab2); | ||||
| }); | ||||
| @@ -0,0 +1,65 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests that the send more info link appears only when its pref | ||||
|  * is set to true, and that when clicked it will open a tab to | ||||
|  * the webcompat.com endpoint and send the right data. | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from send_more_info.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| const VIDEO_URL = `${BASE_URL}/videotest.mp4`; | ||||
|  | ||||
| Services.scriptloader.loadSubScript( | ||||
|   getRootDirectory(gTestPath) + "send_more_info.js", | ||||
|   this | ||||
| ); | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| requestLongerTimeout(2); | ||||
|  | ||||
| add_task(async function testSendMoreInfoPref() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     await changeTab(gBrowser.selectedTab, REPORTABLE_PAGE_URL); | ||||
|  | ||||
|     ensureSendMoreInfoDisabled(); | ||||
|     let rbs = await AppMenu().openReportBrokenSite(); | ||||
|     await rbs.isSendMoreInfoHidden(); | ||||
|     await rbs.close(); | ||||
|  | ||||
|     ensureSendMoreInfoEnabled(); | ||||
|     rbs = await AppMenu().openReportBrokenSite(); | ||||
|     await rbs.isSendMoreInfoShown(); | ||||
|     await rbs.close(); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| add_task(async function testSendingMoreInfo() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureSendMoreInfoEnabled(); | ||||
|  | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testSendMoreInfo(tab, AppMenu()); | ||||
|  | ||||
|   await changeTab(tab, REPORTABLE_PAGE_URL2); | ||||
|  | ||||
|   await testSendMoreInfo(tab, ProtectionsPanel(), { | ||||
|     url: "https://override.com", | ||||
|     description: "another", | ||||
|     expectNoTabDetails: true, | ||||
|   }); | ||||
|  | ||||
|   // also load a video to ensure system codec | ||||
|   // information is loaded and properly sent | ||||
|   const tab2 = await openTab(VIDEO_URL); | ||||
|   await testSendMoreInfo(tab2, HelpMenu()); | ||||
|   closeTab(tab2); | ||||
|  | ||||
|   closeTab(tab); | ||||
| }); | ||||
| @@ -0,0 +1,132 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests of the expected tab key element focus order */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| requestLongerTimeout(2); | ||||
|  | ||||
| async function ensureTabOrder(order, win = window) { | ||||
|   const config = { window: win }; | ||||
|   for (let matches of order) { | ||||
|     // We need to tab through all elements in each match array in any order | ||||
|     if (!Array.isArray(matches)) { | ||||
|       matches = [matches]; | ||||
|     } | ||||
|     let matchesLeft = matches.length; | ||||
|     while (matchesLeft--) { | ||||
|       const target = await pressKeyAndGetFocus("VK_TAB", config); | ||||
|       let foundMatch = false; | ||||
|       for (const [i, selector] of matches.entries()) { | ||||
|         foundMatch = selector && target.matches(selector); | ||||
|         if (foundMatch) { | ||||
|           matches[i] = ""; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       ok( | ||||
|         foundMatch, | ||||
|         `Expected [${matches}] next, got id=${target.id}, class=${target.className}, ${target}` | ||||
|       ); | ||||
|       if (!foundMatch) { | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| async function ensureExpectedTabOrder( | ||||
|   expectBackButton, | ||||
|   expectReason, | ||||
|   expectSendMoreInfo | ||||
| ) { | ||||
|   const { activeElement } = window.document; | ||||
|   is( | ||||
|     activeElement?.id, | ||||
|     "report-broken-site-popup-url", | ||||
|     "URL is already focused" | ||||
|   ); | ||||
|   const order = []; | ||||
|   if (expectReason) { | ||||
|     order.push("#report-broken-site-popup-reason"); | ||||
|   } | ||||
|   order.push("#report-broken-site-popup-description"); | ||||
|   if (expectSendMoreInfo) { | ||||
|     order.push("#report-broken-site-popup-send-more-info-link"); | ||||
|   } | ||||
|   // moz-button-groups swap the order of buttons to follow | ||||
|   // platform conventions, so the order of send/cancel will vary. | ||||
|   order.push([ | ||||
|     "#report-broken-site-popup-cancel-button", | ||||
|     "#report-broken-site-popup-send-button", | ||||
|   ]); | ||||
|   if (expectBackButton) { | ||||
|     order.push(".subviewbutton-back"); | ||||
|   } | ||||
|   order.push("#report-broken-site-popup-url"); // check that we've cycled back | ||||
|   return ensureTabOrder(order); | ||||
| } | ||||
|  | ||||
| async function testTabOrder(menu) { | ||||
|   ensureReasonDisabled(); | ||||
|   ensureSendMoreInfoDisabled(); | ||||
|  | ||||
|   const { showsBackButton } = menu; | ||||
|  | ||||
|   let rbs = await menu.openReportBrokenSite(); | ||||
|   await ensureExpectedTabOrder(showsBackButton, false, false); | ||||
|   await rbs.close(); | ||||
|  | ||||
|   ensureSendMoreInfoEnabled(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   await ensureExpectedTabOrder(showsBackButton, false, true); | ||||
|   await rbs.close(); | ||||
|  | ||||
|   ensureReasonOptional(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   await ensureExpectedTabOrder(showsBackButton, true, true); | ||||
|   await rbs.close(); | ||||
|  | ||||
|   ensureReasonRequired(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   await ensureExpectedTabOrder(showsBackButton, true, true); | ||||
|   await rbs.close(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   rbs.chooseReason("slow"); | ||||
|   await ensureExpectedTabOrder(showsBackButton, true, true); | ||||
|   await rbs.clickCancel(); | ||||
|  | ||||
|   ensureSendMoreInfoDisabled(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   await ensureExpectedTabOrder(showsBackButton, true, false); | ||||
|   await rbs.close(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   rbs.chooseReason("slow"); | ||||
|   await ensureExpectedTabOrder(showsBackButton, true, false); | ||||
|   await rbs.clickCancel(); | ||||
|  | ||||
|   ensureReasonOptional(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   await ensureExpectedTabOrder(showsBackButton, true, false); | ||||
|   await rbs.close(); | ||||
|  | ||||
|   ensureReasonDisabled(); | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   await ensureExpectedTabOrder(showsBackButton, false, false); | ||||
|   await rbs.close(); | ||||
| } | ||||
|  | ||||
| add_task(async function testTabOrdering() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|   ensureSendMoreInfoEnabled(); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () { | ||||
|     await testTabOrder(AppMenu()); | ||||
|     await testTabOrder(ProtectionsPanel()); | ||||
|     await testTabOrder(HelpMenu()); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,81 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests to ensure that Report Broken Site popups will be | ||||
|  * reset to whichever tab the user is on as they change | ||||
|  * between windows and tabs. */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| add_task(async function testResetsProperlyOnTabSwitch() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|  | ||||
|   const badTab = await openTab("about:blank"); | ||||
|   const goodTab1 = await openTab(REPORTABLE_PAGE_URL); | ||||
|   const goodTab2 = await openTab(REPORTABLE_PAGE_URL2); | ||||
|  | ||||
|   const appMenu = AppMenu(); | ||||
|   const protPanel = ProtectionsPanel(); | ||||
|  | ||||
|   let rbs = await appMenu.openReportBrokenSite(); | ||||
|   rbs.isMainViewResetToCurrentTab(); | ||||
|   rbs.close(); | ||||
|  | ||||
|   gBrowser.selectedTab = goodTab1; | ||||
|  | ||||
|   rbs = await protPanel.openReportBrokenSite(); | ||||
|   rbs.isMainViewResetToCurrentTab(); | ||||
|   rbs.close(); | ||||
|  | ||||
|   gBrowser.selectedTab = badTab; | ||||
|   await appMenu.open(); | ||||
|   appMenu.isReportBrokenSiteDisabled(); | ||||
|   await appMenu.close(); | ||||
|  | ||||
|   gBrowser.selectedTab = goodTab1; | ||||
|   rbs = await protPanel.openReportBrokenSite(); | ||||
|   rbs.isMainViewResetToCurrentTab(); | ||||
|   rbs.close(); | ||||
|  | ||||
|   closeTab(badTab); | ||||
|   closeTab(goodTab1); | ||||
|   closeTab(goodTab2); | ||||
| }); | ||||
|  | ||||
| add_task(async function testResetsProperlyOnWindowSwitch() { | ||||
|   ensureReportBrokenSitePreffedOn(); | ||||
|  | ||||
|   const tab1 = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   const win2 = await BrowserTestUtils.openNewBrowserWindow(); | ||||
|   const tab2 = await openTab(REPORTABLE_PAGE_URL2, win2); | ||||
|  | ||||
|   const appMenu1 = AppMenu(); | ||||
|   const appMenu2 = ProtectionsPanel(win2); | ||||
|  | ||||
|   let rbs2 = await appMenu2.openReportBrokenSite(); | ||||
|   rbs2.isMainViewResetToCurrentTab(); | ||||
|   rbs2.close(); | ||||
|  | ||||
|   // flip back to tab1's window and ensure its URL pops up instead of tab2's URL | ||||
|   await switchToWindow(window); | ||||
|   isSelectedTab(window, tab1); // sanity check | ||||
|  | ||||
|   let rbs1 = await appMenu1.openReportBrokenSite(); | ||||
|   rbs1.isMainViewResetToCurrentTab(); | ||||
|   rbs1.close(); | ||||
|  | ||||
|   // likewise flip back to tab2's window and ensure its URL pops up instead of tab1's URL | ||||
|   await switchToWindow(win2); | ||||
|   isSelectedTab(win2, tab2); // sanity check | ||||
|  | ||||
|   rbs2 = await appMenu2.openReportBrokenSite(); | ||||
|   rbs2.isMainViewResetToCurrentTab(); | ||||
|   rbs2.close(); | ||||
|  | ||||
|   closeTab(tab1); | ||||
|   closeTab(tab2); | ||||
|   await BrowserTestUtils.closeWindow(win2); | ||||
| }); | ||||
| @@ -0,0 +1,45 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Tests that when Report Broken Site is disabled, it will | ||||
|  * send the user to webcompat.com when clicked and it the | ||||
|  * relevant tab's report data. | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from send_more_info.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| Services.scriptloader.loadSubScript( | ||||
|   getRootDirectory(gTestPath) + "send_more_info.js", | ||||
|   this | ||||
| ); | ||||
|  | ||||
| add_common_setup(); | ||||
|  | ||||
| const VIDEO_URL = `${BASE_URL}/videotest.mp4`; | ||||
|  | ||||
| add_setup(async function () { | ||||
|   await SpecialPowers.pushPrefEnv({ | ||||
|     set: [["test.wait300msAfterTabSwitch", true]], | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| add_task(async function testWebcompatComFallbacks() { | ||||
|   ensureReportBrokenSitePreffedOff(); | ||||
|  | ||||
|   const tab = await openTab(REPORTABLE_PAGE_URL); | ||||
|  | ||||
|   await testWebcompatComFallback(tab, AppMenu()); | ||||
|  | ||||
|   await changeTab(tab, REPORTABLE_PAGE_URL2); | ||||
|   await testWebcompatComFallback(tab, ProtectionsPanel()); | ||||
|  | ||||
|   // also load a video to ensure system codec | ||||
|   // information is loaded and properly sent | ||||
|   const tab2 = await openTab(VIDEO_URL); | ||||
|   await testWebcompatComFallback(tab2, HelpMenu()); | ||||
|   closeTab(tab2); | ||||
|  | ||||
|   closeTab(tab); | ||||
| }); | ||||
| @@ -0,0 +1,22 @@ | ||||
| <!DOCTYPE HTML> | ||||
| <!-- 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 http://mozilla.org/MPL/2.0/. --> | ||||
| <html dir="ltr" xml:lang="en-US" lang="en-US"> | ||||
|   <head> | ||||
|     <meta charset="utf8"> | ||||
|     <script> | ||||
|       window.marfeel = 1; | ||||
|       window.Mobify = { Tag: 1 }; | ||||
|       window.FastClick = 1; | ||||
|     </script> | ||||
|   </head> | ||||
|   <body> | ||||
|     <!-- blocked tracking content --> | ||||
|     <iframe src="https://trackertest.org/"></iframe> | ||||
|     <!-- mixed active content --> | ||||
|     <iframe src="http://tracking.example.org/browser/browser/base/content/test/protectionsUI/benignPage.html"></iframe> | ||||
|     <!-- mixed display content --> | ||||
|     <img src="http://example.com/tests/image/test/mochitest/blue.png"></img> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										897
									
								
								src/zen/tests/mochitests/reportbrokensite/head.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										897
									
								
								src/zen/tests/mochitests/reportbrokensite/head.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,897 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| const { CustomizableUITestUtils } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/CustomizableUITestUtils.sys.mjs" | ||||
| ); | ||||
|  | ||||
| const { EnterprisePolicyTesting, PoliciesPrefTracker } = | ||||
|   ChromeUtils.importESModule( | ||||
|     "resource://testing-common/EnterprisePolicyTesting.sys.mjs" | ||||
|   ); | ||||
|  | ||||
| const { UrlClassifierTestUtils } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/UrlClassifierTestUtils.sys.mjs" | ||||
| ); | ||||
|  | ||||
| const { ReportBrokenSite } = ChromeUtils.importESModule( | ||||
|   "moz-src:///browser/components/reportbrokensite/ReportBrokenSite.sys.mjs" | ||||
| ); | ||||
|  | ||||
| const BASE_URL = | ||||
|   "https://example.com/browser/browser/components/reportbrokensite/test/browser/"; | ||||
|  | ||||
| const REPORTABLE_PAGE_URL = "https://example.com"; | ||||
|  | ||||
| const REPORTABLE_PAGE_URL2 = REPORTABLE_PAGE_URL.replace(".com", ".org"); | ||||
|  | ||||
| const REPORTABLE_PAGE_URL3 = `${BASE_URL}example_report_page.html`; | ||||
|  | ||||
| const NEW_REPORT_ENDPOINT_TEST_URL = `${BASE_URL}sendMoreInfoTestEndpoint.html`; | ||||
|  | ||||
| const PREFS = { | ||||
|   DATAREPORTING_ENABLED: "datareporting.healthreport.uploadEnabled", | ||||
|   REPORTER_ENABLED: "ui.new-webcompat-reporter.enabled", | ||||
|   REASON: "ui.new-webcompat-reporter.reason-dropdown", | ||||
|   SEND_MORE_INFO: "ui.new-webcompat-reporter.send-more-info-link", | ||||
|   NEW_REPORT_ENDPOINT: "ui.new-webcompat-reporter.new-report-endpoint", | ||||
|   TOUCH_EVENTS: "dom.w3c_touch_events.enabled", | ||||
|   USE_ACCESSIBILITY_THEME: "ui.useAccessibilityTheme", | ||||
| }; | ||||
|  | ||||
| function add_common_setup() { | ||||
|   add_setup(async function () { | ||||
|     await SpecialPowers.pushPrefEnv({ | ||||
|       set: [ | ||||
|         [PREFS.NEW_REPORT_ENDPOINT, NEW_REPORT_ENDPOINT_TEST_URL], | ||||
|  | ||||
|         // set touch events to auto-detect, as the pref gets set to 1 somewhere | ||||
|         // while tests are running, making hasTouchScreen checks unreliable. | ||||
|         [PREFS.TOUCH_EVENTS, 2], | ||||
|       ], | ||||
|     }); | ||||
|     registerCleanupFunction(function () { | ||||
|       for (const prefName of Object.values(PREFS)) { | ||||
|         Services.prefs.clearUserPref(prefName); | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| function areObjectsEqual(actual, expected, path = "") { | ||||
|   if (typeof expected == "function") { | ||||
|     try { | ||||
|       const passes = expected(actual); | ||||
|       if (!passes) { | ||||
|         info(`${path} not pass check function: ${actual}`); | ||||
|       } | ||||
|       return passes; | ||||
|     } catch (e) { | ||||
|       info(`${path} threw exception: | ||||
|         got: ${typeof actual}, ${actual} | ||||
|         expected: ${typeof expected}, ${expected} | ||||
|         exception: ${e.message} | ||||
|           ${e.stack}`); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (typeof actual != typeof expected) { | ||||
|     info(`${path} types do not match: | ||||
|       got: ${typeof actual}, ${actual} | ||||
|       expected: ${typeof expected}, ${expected}`); | ||||
|     return false; | ||||
|   } | ||||
|   if (typeof actual != "object" || actual === null || expected === null) { | ||||
|     if (actual !== expected) { | ||||
|       info(`${path} does not match | ||||
|         got: ${typeof actual}, ${actual} | ||||
|         expected: ${typeof expected}, ${expected}`); | ||||
|       return false; | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   const prefix = path ? `${path}.` : path; | ||||
|   for (const [key, val] of Object.entries(actual)) { | ||||
|     if (!(key in expected)) { | ||||
|       info(`Extra ${prefix}${key}: ${val}`); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|   let result = true; | ||||
|   for (const [key, expectedVal] of Object.entries(expected)) { | ||||
|     if (key in actual) { | ||||
|       if (!areObjectsEqual(actual[key], expectedVal, `${prefix}${key}`)) { | ||||
|         result = false; | ||||
|       } | ||||
|     } else { | ||||
|       info(`Missing ${prefix}${key} (${expectedVal})`); | ||||
|       result = false; | ||||
|     } | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| function clickAndAwait(toClick, evt, target) { | ||||
|   const menuPromise = BrowserTestUtils.waitForEvent(target, evt); | ||||
|   EventUtils.synthesizeMouseAtCenter(toClick, {}, window); | ||||
|   return menuPromise; | ||||
| } | ||||
|  | ||||
| async function openTab(url, win) { | ||||
|   const options = { | ||||
|     gBrowser: | ||||
|       win?.gBrowser || | ||||
|       Services.wm.getMostRecentWindow("navigator:browser").gBrowser, | ||||
|     url, | ||||
|   }; | ||||
|   return BrowserTestUtils.openNewForegroundTab(options); | ||||
| } | ||||
|  | ||||
| async function changeTab(tab, url) { | ||||
|   BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url); | ||||
|   await BrowserTestUtils.browserLoaded(tab.linkedBrowser); | ||||
| } | ||||
|  | ||||
| function closeTab(tab) { | ||||
|   BrowserTestUtils.removeTab(tab); | ||||
| } | ||||
|  | ||||
| function switchToWindow(win) { | ||||
|   const promises = [ | ||||
|     BrowserTestUtils.waitForEvent(win, "focus"), | ||||
|     BrowserTestUtils.waitForEvent(win, "activate"), | ||||
|   ]; | ||||
|   win.focus(); | ||||
|   return Promise.all(promises); | ||||
| } | ||||
|  | ||||
| function isSelectedTab(win, tab) { | ||||
|   const selectedTab = win.document.querySelector(".tabbrowser-tab[selected]"); | ||||
|   is(selectedTab, tab); | ||||
| } | ||||
|  | ||||
| async function setupPolicyEngineWithJson(json, customSchema) { | ||||
|   PoliciesPrefTracker.restoreDefaultValues(); | ||||
|   if (typeof json != "object") { | ||||
|     let filePath = getTestFilePath(json ? json : "non-existing-file.json"); | ||||
|     return EnterprisePolicyTesting.setupPolicyEngineWithJson( | ||||
|       filePath, | ||||
|       customSchema | ||||
|     ); | ||||
|   } | ||||
|   return EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema); | ||||
| } | ||||
|  | ||||
| async function ensureReportBrokenSiteDisabledByPolicy() { | ||||
|   await setupPolicyEngineWithJson({ | ||||
|     policies: { | ||||
|       DisableFeedbackCommands: true, | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
|  | ||||
| registerCleanupFunction(async function resetPolicies() { | ||||
|   if (Services.policies.status != Ci.nsIEnterprisePolicies.INACTIVE) { | ||||
|     await setupPolicyEngineWithJson(""); | ||||
|   } | ||||
|   EnterprisePolicyTesting.resetRunOnceState(); | ||||
|   PoliciesPrefTracker.restoreDefaultValues(); | ||||
|   PoliciesPrefTracker.stop(); | ||||
| }); | ||||
|  | ||||
| function ensureReportBrokenSitePreffedOn() { | ||||
|   Services.prefs.setBoolPref(PREFS.DATAREPORTING_ENABLED, true); | ||||
|   Services.prefs.setBoolPref(PREFS.REPORTER_ENABLED, true); | ||||
|   ensureReasonDisabled(); | ||||
| } | ||||
|  | ||||
| function ensureReportBrokenSitePreffedOff() { | ||||
|   Services.prefs.setBoolPref(PREFS.REPORTER_ENABLED, false); | ||||
| } | ||||
|  | ||||
| function ensureSendMoreInfoEnabled() { | ||||
|   Services.prefs.setBoolPref(PREFS.SEND_MORE_INFO, true); | ||||
| } | ||||
|  | ||||
| function ensureSendMoreInfoDisabled() { | ||||
|   Services.prefs.setBoolPref(PREFS.SEND_MORE_INFO, false); | ||||
| } | ||||
|  | ||||
| function ensureReasonDisabled() { | ||||
|   Services.prefs.setIntPref(PREFS.REASON, 0); | ||||
| } | ||||
|  | ||||
| function ensureReasonOptional() { | ||||
|   Services.prefs.setIntPref(PREFS.REASON, 1); | ||||
| } | ||||
|  | ||||
| function ensureReasonRequired() { | ||||
|   Services.prefs.setIntPref(PREFS.REASON, 2); | ||||
| } | ||||
|  | ||||
| function isMenuItemEnabled(menuItem, itemDesc) { | ||||
|   ok(!menuItem.hidden, `${itemDesc} menu item is shown`); | ||||
|   ok(!menuItem.disabled, `${itemDesc} menu item is enabled`); | ||||
| } | ||||
|  | ||||
| function isMenuItemHidden(menuItem, itemDesc) { | ||||
|   ok( | ||||
|     !menuItem || menuItem.hidden || !BrowserTestUtils.isVisible(menuItem), | ||||
|     `${itemDesc} menu item is hidden` | ||||
|   ); | ||||
| } | ||||
|  | ||||
| function isMenuItemDisabled(menuItem, itemDesc) { | ||||
|   ok(!menuItem.hidden, `${itemDesc} menu item is shown`); | ||||
|   ok(menuItem.disabled, `${itemDesc} menu item is disabled`); | ||||
| } | ||||
|  | ||||
| function waitForWebcompatComTab(gBrowser) { | ||||
|   return BrowserTestUtils.waitForNewTab(gBrowser, NEW_REPORT_ENDPOINT_TEST_URL); | ||||
| } | ||||
|  | ||||
| class ReportBrokenSiteHelper { | ||||
|   sourceMenu = undefined; | ||||
|   win = undefined; | ||||
|  | ||||
|   constructor(sourceMenu) { | ||||
|     this.sourceMenu = sourceMenu; | ||||
|     this.win = sourceMenu.win; | ||||
|   } | ||||
|  | ||||
|   getViewNode(id) { | ||||
|     return PanelMultiView.getViewNode(this.win.document, id); | ||||
|   } | ||||
|  | ||||
|   get mainView() { | ||||
|     return this.getViewNode("report-broken-site-popup-mainView"); | ||||
|   } | ||||
|  | ||||
|   get sentView() { | ||||
|     return this.getViewNode("report-broken-site-popup-reportSentView"); | ||||
|   } | ||||
|  | ||||
|   get openPanel() { | ||||
|     return this.mainView?.closest("panel"); | ||||
|   } | ||||
|  | ||||
|   get opened() { | ||||
|     return this.openPanel?.hasAttribute("panelopen"); | ||||
|   } | ||||
|  | ||||
|   async click(triggerMenuItem) { | ||||
|     const window = triggerMenuItem.ownerGlobal; | ||||
|     await EventUtils.synthesizeMouseAtCenter(triggerMenuItem, {}, window); | ||||
|   } | ||||
|  | ||||
|   async open(triggerMenuItem) { | ||||
|     const shownPromise = BrowserTestUtils.waitForEvent( | ||||
|       this.mainView, | ||||
|       "ViewShown" | ||||
|     ); | ||||
|     const focusPromise = BrowserTestUtils.waitForEvent(this.URLInput, "focus"); | ||||
|     await this.click(triggerMenuItem); | ||||
|     await shownPromise; | ||||
|     await focusPromise; | ||||
|     await BrowserTestUtils.waitForCondition( | ||||
|       () => this.URLInput.selectionStart === 0 | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   async #assertClickAndViewChanges(button, view, newView, newFocus) { | ||||
|     ok(view.closest("panel").hasAttribute("panelopen"), "Panel is open"); | ||||
|     ok(BrowserTestUtils.isVisible(button), "Button is visible"); | ||||
|     ok(!button.disabled, "Button is enabled"); | ||||
|     const promises = []; | ||||
|     if (newView) { | ||||
|       if (newView.nodeName == "panel") { | ||||
|         promises.push(BrowserTestUtils.waitForEvent(newView, "popupshown")); | ||||
|       } else { | ||||
|         promises.push(BrowserTestUtils.waitForEvent(newView, "ViewShown")); | ||||
|       } | ||||
|     } else { | ||||
|       promises.push(BrowserTestUtils.waitForEvent(view, "ViewHiding")); | ||||
|     } | ||||
|     if (newFocus) { | ||||
|       promises.push(BrowserTestUtils.waitForEvent(newFocus, "focus")); | ||||
|     } | ||||
|     EventUtils.synthesizeMouseAtCenter(button, {}, this.win); | ||||
|     await Promise.all(promises); | ||||
|   } | ||||
|  | ||||
|   async awaitReportSentViewOpened() { | ||||
|     await Promise.all([ | ||||
|       BrowserTestUtils.waitForEvent(this.sentView, "ViewShown"), | ||||
|       BrowserTestUtils.waitForEvent(this.okayButton, "focus"), | ||||
|     ]); | ||||
|   } | ||||
|  | ||||
|   async clickSend() { | ||||
|     await this.#assertClickAndViewChanges( | ||||
|       this.sendButton, | ||||
|       this.mainView, | ||||
|       this.sentView, | ||||
|       this.okayButton | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   waitForSendMoreInfoTab() { | ||||
|     return BrowserTestUtils.waitForNewTab( | ||||
|       this.win.gBrowser, | ||||
|       NEW_REPORT_ENDPOINT_TEST_URL | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   async clickSendMoreInfo() { | ||||
|     const newTabPromise = waitForWebcompatComTab(this.win.gBrowser); | ||||
|     EventUtils.synthesizeMouseAtCenter(this.sendMoreInfoLink, {}, this.win); | ||||
|     const newTab = await newTabPromise; | ||||
|     const receivedData = await SpecialPowers.spawn( | ||||
|       newTab.linkedBrowser, | ||||
|       [], | ||||
|       async function () { | ||||
|         await content.wrappedJSObject.messageArrived; | ||||
|         return content.wrappedJSObject.message; | ||||
|       } | ||||
|     ); | ||||
|     this.win.gBrowser.removeCurrentTab(); | ||||
|     return receivedData; | ||||
|   } | ||||
|  | ||||
|   async clickCancel() { | ||||
|     await this.#assertClickAndViewChanges(this.cancelButton, this.mainView); | ||||
|   } | ||||
|  | ||||
|   async clickOkay() { | ||||
|     await this.#assertClickAndViewChanges(this.okayButton, this.sentView); | ||||
|   } | ||||
|  | ||||
|   async clickBack() { | ||||
|     await this.#assertClickAndViewChanges( | ||||
|       this.backButton, | ||||
|       this.sourceMenu.popup | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isBackButtonEnabled() { | ||||
|     ok(BrowserTestUtils.isVisible(this.backButton), "Back button is visible"); | ||||
|     ok(!this.backButton.disabled, "Back button is enabled"); | ||||
|   } | ||||
|  | ||||
|   close() { | ||||
|     if (this.opened) { | ||||
|       this.openPanel?.hidePopup(false); | ||||
|     } | ||||
|     this.sourceMenu?.close(); | ||||
|   } | ||||
|  | ||||
|   // UI element getters | ||||
|   get URLInput() { | ||||
|     return this.getViewNode("report-broken-site-popup-url"); | ||||
|   } | ||||
|  | ||||
|   get URLInvalidMessage() { | ||||
|     return this.getViewNode("report-broken-site-popup-invalid-url-msg"); | ||||
|   } | ||||
|  | ||||
|   get reasonInput() { | ||||
|     return this.getViewNode("report-broken-site-popup-reason"); | ||||
|   } | ||||
|  | ||||
|   get reasonDropdownPopup() { | ||||
|     return this.win.document.getElementById("ContentSelectDropdown").menupopup; | ||||
|   } | ||||
|  | ||||
|   get reasonRequiredMessage() { | ||||
|     return this.getViewNode("report-broken-site-popup-missing-reason-msg"); | ||||
|   } | ||||
|  | ||||
|   get reasonLabelRequired() { | ||||
|     return this.getViewNode("report-broken-site-popup-reason-label"); | ||||
|   } | ||||
|  | ||||
|   get reasonLabelOptional() { | ||||
|     return this.getViewNode("report-broken-site-popup-reason-optional-label"); | ||||
|   } | ||||
|  | ||||
|   get descriptionTextarea() { | ||||
|     return this.getViewNode("report-broken-site-popup-description"); | ||||
|   } | ||||
|  | ||||
|   get sendMoreInfoLink() { | ||||
|     return this.getViewNode("report-broken-site-popup-send-more-info-link"); | ||||
|   } | ||||
|  | ||||
|   get backButton() { | ||||
|     return this.mainView.querySelector(".subviewbutton-back"); | ||||
|   } | ||||
|  | ||||
|   get sendButton() { | ||||
|     return this.getViewNode("report-broken-site-popup-send-button"); | ||||
|   } | ||||
|  | ||||
|   get cancelButton() { | ||||
|     return this.getViewNode("report-broken-site-popup-cancel-button"); | ||||
|   } | ||||
|  | ||||
|   get okayButton() { | ||||
|     return this.getViewNode("report-broken-site-popup-okay-button"); | ||||
|   } | ||||
|  | ||||
|   // Test helpers | ||||
|  | ||||
|   #setInput(input, value) { | ||||
|     input.value = value; | ||||
|     input.dispatchEvent( | ||||
|       new UIEvent("input", { bubbles: true, view: this.win }) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   setURL(value) { | ||||
|     this.#setInput(this.URLInput, value); | ||||
|   } | ||||
|  | ||||
|   chooseReason(value) { | ||||
|     const item = this.getViewNode(`report-broken-site-popup-reason-${value}`); | ||||
|     this.reasonInput.selectedIndex = item.index; | ||||
|   } | ||||
|  | ||||
|   dismissDropdownPopup() { | ||||
|     const popup = this.reasonDropdownPopup; | ||||
|     const menuPromise = BrowserTestUtils.waitForPopupEvent(popup, "hidden"); | ||||
|     popup.hidePopup(); | ||||
|     return menuPromise; | ||||
|   } | ||||
|  | ||||
|   setDescription(value) { | ||||
|     this.#setInput(this.descriptionTextarea, value); | ||||
|   } | ||||
|  | ||||
|   isURL(expected) { | ||||
|     is(this.URLInput.value, expected); | ||||
|   } | ||||
|  | ||||
|   isURLInvalidMessageShown() { | ||||
|     ok( | ||||
|       BrowserTestUtils.isVisible(this.URLInvalidMessage), | ||||
|       "'Please enter a valid URL' message is shown" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isURLInvalidMessageHidden() { | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.URLInvalidMessage), | ||||
|       "'Please enter a valid URL' message is hidden" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isReasonNeededMessageShown() { | ||||
|     ok( | ||||
|       BrowserTestUtils.isVisible(this.reasonRequiredMessage), | ||||
|       "'Please choose a reason' message is shown" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isReasonNeededMessageHidden() { | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.reasonRequiredMessage), | ||||
|       "'Please choose a reason' message is hidden" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isSendButtonEnabled() { | ||||
|     ok(BrowserTestUtils.isVisible(this.sendButton), "Send button is visible"); | ||||
|     ok(!this.sendButton.disabled, "Send button is enabled"); | ||||
|   } | ||||
|  | ||||
|   isSendButtonDisabled() { | ||||
|     ok(BrowserTestUtils.isVisible(this.sendButton), "Send button is visible"); | ||||
|     ok(this.sendButton.disabled, "Send button is disabled"); | ||||
|   } | ||||
|  | ||||
|   isSendMoreInfoShown() { | ||||
|     ok( | ||||
|       BrowserTestUtils.isVisible(this.sendMoreInfoLink), | ||||
|       "send more info is shown" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isSendMoreInfoHidden() { | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.sendMoreInfoLink), | ||||
|       "send more info is hidden" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isSendMoreInfoShownOrHiddenAppropriately() { | ||||
|     if (Services.prefs.getBoolPref(PREFS.SEND_MORE_INFO)) { | ||||
|       this.isSendMoreInfoShown(); | ||||
|     } else { | ||||
|       this.isSendMoreInfoHidden(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   isReasonHidden() { | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.reasonInput), | ||||
|       "reason drop-down is hidden" | ||||
|     ); | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.reasonLabelOptional), | ||||
|       "optional reason label is hidden" | ||||
|     ); | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.reasonLabelRequired), | ||||
|       "required reason label is hidden" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isReasonRequired() { | ||||
|     ok( | ||||
|       BrowserTestUtils.isVisible(this.reasonInput), | ||||
|       "reason drop-down is shown" | ||||
|     ); | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.reasonLabelOptional), | ||||
|       "optional reason label is hidden" | ||||
|     ); | ||||
|     ok( | ||||
|       BrowserTestUtils.isVisible(this.reasonLabelRequired), | ||||
|       "required reason label is shown" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isReasonOptional() { | ||||
|     ok( | ||||
|       BrowserTestUtils.isVisible(this.reasonInput), | ||||
|       "reason drop-down is shown" | ||||
|     ); | ||||
|     ok( | ||||
|       BrowserTestUtils.isVisible(this.reasonLabelOptional), | ||||
|       "optional reason label is shown" | ||||
|     ); | ||||
|     ok( | ||||
|       !BrowserTestUtils.isVisible(this.reasonLabelRequired), | ||||
|       "required reason label is hidden" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   isReasonShownOrHiddenAppropriately() { | ||||
|     const pref = Services.prefs.getIntPref(PREFS.REASON); | ||||
|     if (pref == 2) { | ||||
|       this.isReasonOptional(); | ||||
|     } else if (pref == 1) { | ||||
|       this.isReasonOptional(); | ||||
|     } else { | ||||
|       this.isReasonHidden(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   isDescription(expected) { | ||||
|     return this.descriptionTextarea.value == expected; | ||||
|   } | ||||
|  | ||||
|   isMainViewResetToCurrentTab() { | ||||
|     this.isURL(this.win.gBrowser.selectedBrowser.currentURI.spec); | ||||
|     this.isDescription(""); | ||||
|     this.isReasonShownOrHiddenAppropriately(); | ||||
|     this.isSendMoreInfoShownOrHiddenAppropriately(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class MenuHelper { | ||||
|   menuDescription = undefined; | ||||
|  | ||||
|   win = undefined; | ||||
|  | ||||
|   constructor(win = window) { | ||||
|     this.win = win; | ||||
|   } | ||||
|  | ||||
|   getViewNode(id) { | ||||
|     return PanelMultiView.getViewNode(this.win.document, id); | ||||
|   } | ||||
|  | ||||
|   get showsBackButton() { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   get reportBrokenSite() { | ||||
|     throw new Error("Should be defined in derived class"); | ||||
|   } | ||||
|  | ||||
|   get popup() { | ||||
|     throw new Error("Should be defined in derived class"); | ||||
|   } | ||||
|  | ||||
|   get opened() { | ||||
|     return this.popup?.hasAttribute("panelopen"); | ||||
|   } | ||||
|  | ||||
|   async open() {} | ||||
|  | ||||
|   async close() {} | ||||
|  | ||||
|   isReportBrokenSiteDisabled() { | ||||
|     return isMenuItemDisabled(this.reportBrokenSite, this.menuDescription); | ||||
|   } | ||||
|  | ||||
|   isReportBrokenSiteEnabled() { | ||||
|     return isMenuItemEnabled(this.reportBrokenSite, this.menuDescription); | ||||
|   } | ||||
|  | ||||
|   isReportBrokenSiteHidden() { | ||||
|     return isMenuItemHidden(this.reportBrokenSite, this.menuDescription); | ||||
|   } | ||||
|  | ||||
|   async clickReportBrokenSiteAndAwaitWebCompatTabData() { | ||||
|     const newTabPromise = waitForWebcompatComTab(this.win.gBrowser); | ||||
|     await this.clickReportBrokenSite(); | ||||
|     const newTab = await newTabPromise; | ||||
|     const receivedData = await SpecialPowers.spawn( | ||||
|       newTab.linkedBrowser, | ||||
|       [], | ||||
|       async function () { | ||||
|         await content.wrappedJSObject.messageArrived; | ||||
|         return content.wrappedJSObject.message; | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     this.win.gBrowser.removeCurrentTab(); | ||||
|     return receivedData; | ||||
|   } | ||||
|  | ||||
|   async clickReportBrokenSite() { | ||||
|     if (!this.opened) { | ||||
|       await this.open(); | ||||
|     } | ||||
|     isMenuItemEnabled(this.reportBrokenSite, this.menuDescription); | ||||
|     const rbs = new ReportBrokenSiteHelper(this); | ||||
|     await rbs.click(this.reportBrokenSite); | ||||
|     return rbs; | ||||
|   } | ||||
|  | ||||
|   async openReportBrokenSite() { | ||||
|     if (!this.opened) { | ||||
|       await this.open(); | ||||
|     } | ||||
|     isMenuItemEnabled(this.reportBrokenSite, this.menuDescription); | ||||
|     const rbs = new ReportBrokenSiteHelper(this); | ||||
|     await rbs.open(this.reportBrokenSite); | ||||
|     return rbs; | ||||
|   } | ||||
|  | ||||
|   async openAndPrefillReportBrokenSite(url = null, description = "") { | ||||
|     let rbs = await this.openReportBrokenSite(); | ||||
|     rbs.isMainViewResetToCurrentTab(); | ||||
|     if (url) { | ||||
|       rbs.setURL(url); | ||||
|     } | ||||
|     if (description) { | ||||
|       rbs.setDescription(description); | ||||
|     } | ||||
|     return rbs; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class AppMenuHelper extends MenuHelper { | ||||
|   menuDescription = "AppMenu"; | ||||
|  | ||||
|   get reportBrokenSite() { | ||||
|     return this.getViewNode("appMenu-report-broken-site-button"); | ||||
|   } | ||||
|  | ||||
|   get popup() { | ||||
|     return this.win.document.getElementById("appMenu-popup"); | ||||
|   } | ||||
|  | ||||
|   async open() { | ||||
|     await new CustomizableUITestUtils(this.win).openMainMenu(); | ||||
|   } | ||||
|  | ||||
|   async close() { | ||||
|     if (this.opened) { | ||||
|       await new CustomizableUITestUtils(this.win).hideMainMenu(); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| class HelpMenuHelper extends MenuHelper { | ||||
|   menuDescription = "Help Menu"; | ||||
|  | ||||
|   get showsBackButton() { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   get reportBrokenSite() { | ||||
|     return this.win.document.getElementById("help_reportBrokenSite"); | ||||
|   } | ||||
|  | ||||
|   get popup() { | ||||
|     return this.getViewNode("PanelUI-helpView"); | ||||
|   } | ||||
|  | ||||
|   get helpMenu() { | ||||
|     return this.win.document.getElementById("menu_HelpPopup"); | ||||
|   } | ||||
|  | ||||
|   async openReportBrokenSite() { | ||||
|     // We can't actually open the Help menu properly in testing, so the best | ||||
|     // we can do to open its Report Broken Site panel is to force its DOM to be | ||||
|     // prepared, and then soft-click the Report Broken Site menuitem to open it. | ||||
|     await this.open(); | ||||
|     const shownPromise = BrowserTestUtils.waitForEvent( | ||||
|       this.win, | ||||
|       "ViewShown", | ||||
|       true, | ||||
|       e => e.target.classList.contains("report-broken-site-view") | ||||
|     ); | ||||
|     this.reportBrokenSite.click(); | ||||
|     await shownPromise; | ||||
|     return new ReportBrokenSiteHelper(this); | ||||
|   } | ||||
|  | ||||
|   async clickReportBrokenSite() { | ||||
|     await this.open(); | ||||
|     this.reportBrokenSite.click(); | ||||
|     return new ReportBrokenSiteHelper(this); | ||||
|   } | ||||
|  | ||||
|   async open() { | ||||
|     const { helpMenu } = this; | ||||
|     const promise = BrowserTestUtils.waitForEvent(helpMenu, "popupshown"); | ||||
|  | ||||
|     // This event-faking method was copied from browser_title_case_menus.js. | ||||
|     // We can't actually open the Help menu in testing, but this lets us | ||||
|     // force its DOM to be properly built. | ||||
|     helpMenu.dispatchEvent(new MouseEvent("popupshowing", { bubbles: true })); | ||||
|     helpMenu.dispatchEvent(new MouseEvent("popupshown", { bubbles: true })); | ||||
|  | ||||
|     await promise; | ||||
|   } | ||||
|  | ||||
|   async close() { | ||||
|     const { helpMenu } = this; | ||||
|     const promise = BrowserTestUtils.waitForPopupEvent(helpMenu, "hidden"); | ||||
|  | ||||
|     // (Also copied from browser_title_case_menus.js) | ||||
|     // Just for good measure, we'll fire the popuphiding/popuphidden events | ||||
|     // after we close the menupopups. | ||||
|     helpMenu.dispatchEvent(new MouseEvent("popuphiding", { bubbles: true })); | ||||
|     helpMenu.dispatchEvent(new MouseEvent("popuphidden", { bubbles: true })); | ||||
|  | ||||
|     await promise; | ||||
|   } | ||||
| } | ||||
|  | ||||
| class ProtectionsPanelHelper extends MenuHelper { | ||||
|   menuDescription = "Protections Panel"; | ||||
|  | ||||
|   get reportBrokenSite() { | ||||
|     this.win.gProtectionsHandler._initializePopup(); | ||||
|     return this.getViewNode("protections-popup-report-broken-site-button"); | ||||
|   } | ||||
|  | ||||
|   get popup() { | ||||
|     this.win.gProtectionsHandler._initializePopup(); | ||||
|     return this.win.document.getElementById("protections-popup"); | ||||
|   } | ||||
|  | ||||
|   async open() { | ||||
|     const promise = BrowserTestUtils.waitForEvent( | ||||
|       this.win, | ||||
|       "popupshown", | ||||
|       true, | ||||
|       e => e.target.id == "protections-popup" | ||||
|     ); | ||||
|     this.win.gProtectionsHandler.showProtectionsPopup(); | ||||
|     await promise; | ||||
|   } | ||||
|  | ||||
|   async close() { | ||||
|     if (this.opened) { | ||||
|       const popup = this.popup; | ||||
|       const promise = BrowserTestUtils.waitForPopupEvent(popup, "hidden"); | ||||
|       PanelMultiView.hidePopup(popup, false); | ||||
|       await promise; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| function AppMenu(win = window) { | ||||
|   return new AppMenuHelper(win); | ||||
| } | ||||
|  | ||||
| function HelpMenu(win = window) { | ||||
|   return new HelpMenuHelper(win); | ||||
| } | ||||
|  | ||||
| function ProtectionsPanel(win = window) { | ||||
|   return new ProtectionsPanelHelper(win); | ||||
| } | ||||
|  | ||||
| function pressKeyAndAwait(event, key, config = {}) { | ||||
|   const win = config.window || window; | ||||
|   if (!event.then) { | ||||
|     event = BrowserTestUtils.waitForEvent(win, event, config.timeout || 200); | ||||
|   } | ||||
|   EventUtils.synthesizeKey(key, config, win); | ||||
|   return event; | ||||
| } | ||||
|  | ||||
| async function pressKeyAndGetFocus(key, config = {}) { | ||||
|   return (await pressKeyAndAwait("focus", key, config)).target; | ||||
| } | ||||
|  | ||||
| async function tabTo(match, win = window) { | ||||
|   const config = { window: win }; | ||||
|   const { activeElement } = win.document; | ||||
|   if (activeElement?.matches(match)) { | ||||
|     return activeElement; | ||||
|   } | ||||
|   let initial = await pressKeyAndGetFocus("VK_TAB", config); | ||||
|   let target = initial; | ||||
|   do { | ||||
|     if (target.matches(match)) { | ||||
|       return target; | ||||
|     } | ||||
|     target = await pressKeyAndGetFocus("VK_TAB", config); | ||||
|   } while (target && target !== initial); | ||||
|   return undefined; | ||||
| } | ||||
|  | ||||
| function filterFrameworkDetectorFails(ping, expected) { | ||||
|   // the framework detector's frame-script may fail to run in low memory or other | ||||
|   // weird corner-cases, so we ignore the results in that case if they don't match. | ||||
|   if (!areObjectsEqual(ping.frameworks, expected.frameworks)) { | ||||
|     const { fastclick, mobify, marfeel } = ping.frameworks; | ||||
|     if (!fastclick && !mobify && !marfeel) { | ||||
|       console.info("Ignoring failure to get framework data"); | ||||
|       expected.frameworks = ping.frameworks; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function setupStrictETP() { | ||||
|   await UrlClassifierTestUtils.addTestTrackers(); | ||||
|   registerCleanupFunction(() => { | ||||
|     UrlClassifierTestUtils.cleanupTestTrackers(); | ||||
|   }); | ||||
|  | ||||
|   await SpecialPowers.pushPrefEnv({ | ||||
|     set: [ | ||||
|       ["security.mixed_content.block_active_content", true], | ||||
|       ["security.mixed_content.block_display_content", true], | ||||
|       ["security.mixed_content.upgrade_display_content", false], | ||||
|       [ | ||||
|         "urlclassifier.trackingTable", | ||||
|         "content-track-digest256,mochitest2-track-simple", | ||||
|       ], | ||||
|       ["browser.contentblocking.category", "strict"], | ||||
|     ], | ||||
|   }); | ||||
| } | ||||
|  | ||||
| // copied from browser/base/content/test/protectionsUI/head.js | ||||
| function waitForContentBlockingEvent(numChanges = 1, win = null) { | ||||
|   if (!win) { | ||||
|     win = window; | ||||
|   } | ||||
|   return new Promise(resolve => { | ||||
|     let n = 0; | ||||
|     let listener = { | ||||
|       onContentBlockingEvent(webProgress, request, event) { | ||||
|         n = n + 1; | ||||
|         info( | ||||
|           `Received onContentBlockingEvent event: ${event} (${n} of ${numChanges})` | ||||
|         ); | ||||
|         if (n >= numChanges) { | ||||
|           win.gBrowser.removeProgressListener(listener); | ||||
|           resolve(n); | ||||
|         } | ||||
|       }, | ||||
|     }; | ||||
|     win.gBrowser.addProgressListener(listener); | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										330
									
								
								src/zen/tests/mochitests/reportbrokensite/send.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								src/zen/tests/mochitests/reportbrokensite/send.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,330 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Helper methods for testing sending reports with | ||||
|  * the Report Broken Site feature. | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from head.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| const { Troubleshoot } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/Troubleshoot.sys.mjs" | ||||
| ); | ||||
|  | ||||
| function getSysinfoProperty(propertyName, defaultValue) { | ||||
|   try { | ||||
|     return Services.sysinfo.getProperty(propertyName); | ||||
|   } catch (e) {} | ||||
|   return defaultValue; | ||||
| } | ||||
|  | ||||
| function securityStringToArray(str) { | ||||
|   return str ? str.split(";") : null; | ||||
| } | ||||
|  | ||||
| function getExpectedGraphicsDevices(snapshot) { | ||||
|   const { graphics } = snapshot; | ||||
|   return [ | ||||
|     graphics.adapterDeviceID, | ||||
|     graphics.adapterVendorID, | ||||
|     graphics.adapterDeviceID2, | ||||
|     graphics.adapterVendorID2, | ||||
|   ] | ||||
|     .filter(i => i) | ||||
|     .sort(); | ||||
| } | ||||
|  | ||||
| function compareGraphicsDevices(expected, rawActual) { | ||||
|   const actual = rawActual | ||||
|     .map(({ deviceID, vendorID }) => [deviceID, vendorID]) | ||||
|     .flat() | ||||
|     .filter(i => i) | ||||
|     .sort(); | ||||
|   return areObjectsEqual(actual, expected); | ||||
| } | ||||
|  | ||||
| function getExpectedGraphicsDrivers(snapshot) { | ||||
|   const { graphics } = snapshot; | ||||
|   const expected = []; | ||||
|   for (let i = 1; i < 3; ++i) { | ||||
|     const version = graphics[`webgl${i}Version`]; | ||||
|     if (version && version != "-") { | ||||
|       expected.push(graphics[`webgl${i}Renderer`]); | ||||
|       expected.push(version); | ||||
|     } | ||||
|   } | ||||
|   return expected.filter(i => i).sort(); | ||||
| } | ||||
|  | ||||
| function compareGraphicsDrivers(expected, rawActual) { | ||||
|   const actual = rawActual | ||||
|     .map(({ renderer, version }) => [renderer, version]) | ||||
|     .flat() | ||||
|     .filter(i => i) | ||||
|     .sort(); | ||||
|   return areObjectsEqual(actual, expected); | ||||
| } | ||||
|  | ||||
| function getExpectedGraphicsFeatures(snapshot) { | ||||
|   const expected = {}; | ||||
|   for (let { name, log, status } of snapshot.graphics.featureLog.features) { | ||||
|     for (const item of log?.reverse() ?? []) { | ||||
|       if (item.failureId && item.status == status) { | ||||
|         status = `${status} (${item.message || item.failureId})`; | ||||
|       } | ||||
|     } | ||||
|     expected[name] = status; | ||||
|   } | ||||
|   return expected; | ||||
| } | ||||
|  | ||||
| async function getExpectedWebCompatInfo(tab, snapshot, fullAppData = false) { | ||||
|   const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); | ||||
|  | ||||
|   const { application, graphics, intl, securitySoftware } = snapshot; | ||||
|  | ||||
|   const { fissionAutoStart, memorySizeBytes, updateChannel, userAgent } = | ||||
|     application; | ||||
|  | ||||
|   const app = { | ||||
|     defaultLocales: intl.localeService.available, | ||||
|     defaultUseragentString: userAgent, | ||||
|     fissionEnabled: fissionAutoStart, | ||||
|   }; | ||||
|   if (fullAppData) { | ||||
|     app.applicationName = application.name; | ||||
|     app.osArchitecture = getSysinfoProperty("arch", null); | ||||
|     app.osName = getSysinfoProperty("name", null); | ||||
|     app.osVersion = getSysinfoProperty("version", null); | ||||
|     app.updateChannel = updateChannel; | ||||
|     app.version = application.version; | ||||
|   } | ||||
|  | ||||
|   const hasTouchScreen = graphics.info.ApzTouchInput == 1; | ||||
|  | ||||
|   const { registeredAntiVirus, registeredAntiSpyware, registeredFirewall } = | ||||
|     securitySoftware; | ||||
|  | ||||
|   const browserInfo = { | ||||
|     addons: [], | ||||
|     app, | ||||
|     experiments: [], | ||||
|     graphics: { | ||||
|       devicesJson(actualStr) { | ||||
|         const expected = getExpectedGraphicsDevices(snapshot); | ||||
|         // If undefined is saved to the Glean value here, we'll get the string "undefined" (invalid JSON). | ||||
|         // We should stop using JSON like this in bug 1875185. | ||||
|         if (!actualStr || actualStr == "undefined") { | ||||
|           return !expected.length; | ||||
|         } | ||||
|         return compareGraphicsDevices(expected, JSON.parse(actualStr)); | ||||
|       }, | ||||
|       driversJson(actualStr) { | ||||
|         const expected = getExpectedGraphicsDrivers(snapshot); | ||||
|         // If undefined is saved to the Glean value here, we'll get the string "undefined" (invalid JSON). | ||||
|         // We should stop using JSON like this in bug 1875185. | ||||
|         if (!actualStr || actualStr == "undefined") { | ||||
|           return !expected.length; | ||||
|         } | ||||
|         return compareGraphicsDrivers(expected, JSON.parse(actualStr)); | ||||
|       }, | ||||
|       featuresJson(actualStr) { | ||||
|         const expected = getExpectedGraphicsFeatures(snapshot); | ||||
|         // If undefined is saved to the Glean value here, we'll get the string "undefined" (invalid JSON). | ||||
|         // We should stop using JSON like this in bug 1875185. | ||||
|         if (!actualStr || actualStr == "undefined") { | ||||
|           return !expected.length; | ||||
|         } | ||||
|         return areObjectsEqual(JSON.parse(actualStr), expected); | ||||
|       }, | ||||
|       hasTouchScreen, | ||||
|       monitorsJson(actualStr) { | ||||
|         const expected = gfxInfo.getMonitors(); | ||||
|         // If undefined is saved to the Glean value here, we'll get the string "undefined" (invalid JSON). | ||||
|         // We should stop using JSON like this in bug 1875185. | ||||
|         if (!actualStr || actualStr == "undefined") { | ||||
|           return !expected.length; | ||||
|         } | ||||
|         return areObjectsEqual(JSON.parse(actualStr), expected); | ||||
|       }, | ||||
|     }, | ||||
|     prefs: { | ||||
|       cookieBehavior: Services.prefs.getIntPref( | ||||
|         "network.cookie.cookieBehavior", | ||||
|         -1 | ||||
|       ), | ||||
|       forcedAcceleratedLayers: Services.prefs.getBoolPref( | ||||
|         "layers.acceleration.force-enabled", | ||||
|         false | ||||
|       ), | ||||
|       globalPrivacyControlEnabled: Services.prefs.getBoolPref( | ||||
|         "privacy.globalprivacycontrol.enabled", | ||||
|         false | ||||
|       ), | ||||
|       installtriggerEnabled: Services.prefs.getBoolPref( | ||||
|         "extensions.InstallTrigger.enabled", | ||||
|         false | ||||
|       ), | ||||
|       opaqueResponseBlocking: Services.prefs.getBoolPref( | ||||
|         "browser.opaqueResponseBlocking", | ||||
|         false | ||||
|       ), | ||||
|       resistFingerprintingEnabled: Services.prefs.getBoolPref( | ||||
|         "privacy.resistFingerprinting", | ||||
|         false | ||||
|       ), | ||||
|       softwareWebrender: Services.prefs.getBoolPref( | ||||
|         "gfx.webrender.software", | ||||
|         false | ||||
|       ), | ||||
|       thirdPartyCookieBlockingEnabled: Services.prefs.getBoolPref( | ||||
|         "network.cookie.cookieBehavior.optInPartitioning", | ||||
|         false | ||||
|       ), | ||||
|       thirdPartyCookieBlockingEnabledInPbm: Services.prefs.getBoolPref( | ||||
|         "network.cookie.cookieBehavior.optInPartitioning.pbmode", | ||||
|         false | ||||
|       ), | ||||
|     }, | ||||
|     security: { | ||||
|       antispyware: securityStringToArray(registeredAntiSpyware), | ||||
|       antivirus: securityStringToArray(registeredAntiVirus), | ||||
|       firewall: securityStringToArray(registeredFirewall), | ||||
|     }, | ||||
|     system: { | ||||
|       isTablet: getSysinfoProperty("tablet", false), | ||||
|       memory: Math.round(memorySizeBytes / 1024 / 1024), | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   const tabInfo = await tab.linkedBrowser.ownerGlobal.SpecialPowers.spawn( | ||||
|     tab.linkedBrowser, | ||||
|     [], | ||||
|     async function () { | ||||
|       return { | ||||
|         devicePixelRatio: `${content.devicePixelRatio}`, | ||||
|         antitracking: { | ||||
|           blockList: "basic", | ||||
|           isPrivateBrowsing: false, | ||||
|           hasTrackingContentBlocked: false, | ||||
|           hasMixedActiveContentBlocked: false, | ||||
|           hasMixedDisplayContentBlocked: false, | ||||
|           btpHasPurgedSite: false, | ||||
|           etpCategory: "standard", | ||||
|         }, | ||||
|         frameworks: { | ||||
|           fastclick: false, | ||||
|           marfeel: false, | ||||
|           mobify: false, | ||||
|         }, | ||||
|         languages: content.navigator.languages, | ||||
|         useragentString: content.navigator.userAgent, | ||||
|       }; | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   browserInfo.graphics.devicePixelRatio = tabInfo.devicePixelRatio; | ||||
|   delete tabInfo.devicePixelRatio; | ||||
|  | ||||
|   return { browserInfo, tabInfo }; | ||||
| } | ||||
|  | ||||
| function extractPingData(branch) { | ||||
|   const data = {}; | ||||
|   for (const [name, value] of Object.entries(branch)) { | ||||
|     data[name] = value.testGetValue(); | ||||
|   } | ||||
|   return data; | ||||
| } | ||||
|  | ||||
| function extractBrokenSiteReportFromGleanPing(Glean) { | ||||
|   const ping = extractPingData(Glean.brokenSiteReport); | ||||
|   ping.tabInfo = extractPingData(Glean.brokenSiteReportTabInfo); | ||||
|   ping.tabInfo.antitracking = extractPingData( | ||||
|     Glean.brokenSiteReportTabInfoAntitracking | ||||
|   ); | ||||
|   ping.tabInfo.frameworks = extractPingData( | ||||
|     Glean.brokenSiteReportTabInfoFrameworks | ||||
|   ); | ||||
|   ping.browserInfo = { | ||||
|     addons: Array.from(Glean.brokenSiteReportBrowserInfo.addons.testGetValue()), | ||||
|     app: extractPingData(Glean.brokenSiteReportBrowserInfoApp), | ||||
|     graphics: extractPingData(Glean.brokenSiteReportBrowserInfoGraphics), | ||||
|     experiments: Array.from( | ||||
|       Glean.brokenSiteReportBrowserInfo.experiments.testGetValue() | ||||
|     ), | ||||
|     prefs: extractPingData(Glean.brokenSiteReportBrowserInfoPrefs), | ||||
|     security: extractPingData(Glean.brokenSiteReportBrowserInfoSecurity), | ||||
|     system: extractPingData(Glean.brokenSiteReportBrowserInfoSystem), | ||||
|   }; | ||||
|   return ping; | ||||
| } | ||||
|  | ||||
| async function testSend(tab, menu, expectedOverrides = {}) { | ||||
|   const url = expectedOverrides.url ?? menu.win.gBrowser.currentURI.spec; | ||||
|   const description = expectedOverrides.description ?? ""; | ||||
|   const breakageCategory = expectedOverrides.breakageCategory ?? null; | ||||
|  | ||||
|   let rbs = await menu.openAndPrefillReportBrokenSite(url, description); | ||||
|  | ||||
|   const snapshot = await Troubleshoot.snapshot(); | ||||
|   const expected = await getExpectedWebCompatInfo(tab, snapshot); | ||||
|  | ||||
|   expected.url = url; | ||||
|   expected.description = description; | ||||
|   expected.breakageCategory = breakageCategory; | ||||
|  | ||||
|   if (expectedOverrides.addons) { | ||||
|     expected.browserInfo.addons = expectedOverrides.addons; | ||||
|   } | ||||
|  | ||||
|   if (expectedOverrides.experiments) { | ||||
|     expected.browserInfo.experiments = expectedOverrides.experiments; | ||||
|   } | ||||
|  | ||||
|   if (expectedOverrides.antitracking) { | ||||
|     expected.tabInfo.antitracking = expectedOverrides.antitracking; | ||||
|   } | ||||
|  | ||||
|   if (expectedOverrides.frameworks) { | ||||
|     expected.tabInfo.frameworks = expectedOverrides.frameworks; | ||||
|   } | ||||
|  | ||||
|   if (breakageCategory) { | ||||
|     rbs.chooseReason(breakageCategory); | ||||
|   } | ||||
|  | ||||
|   Services.fog.testResetFOG(); | ||||
|   await GleanPings.brokenSiteReport.testSubmission( | ||||
|     () => { | ||||
|       const ping = extractBrokenSiteReportFromGleanPing(Glean); | ||||
|  | ||||
|       // sanity checks | ||||
|       const { browserInfo, tabInfo } = ping; | ||||
|       ok(ping.url?.length, "Got a URL"); | ||||
|       ok( | ||||
|         ["basic", "strict"].includes(tabInfo.antitracking.blockList), | ||||
|         "Got a blockList" | ||||
|       ); | ||||
|       ok(tabInfo.useragentString?.length, "Got a final UA string"); | ||||
|       ok( | ||||
|         browserInfo.app.defaultUseragentString?.length, | ||||
|         "Got a default UA string" | ||||
|       ); | ||||
|  | ||||
|       filterFrameworkDetectorFails(ping.tabInfo, expected.tabInfo); | ||||
|  | ||||
|       ok(areObjectsEqual(ping, expected), "ping matches expectations"); | ||||
|     }, | ||||
|     () => rbs.clickSend() | ||||
|   ); | ||||
|  | ||||
|   await rbs.clickOkay(); | ||||
|  | ||||
|   // re-opening the panel, the url and description should be reset | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   rbs.isMainViewResetToCurrentTab(); | ||||
|   rbs.close(); | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| <!DOCTYPE HTML> | ||||
| <!-- 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 http://mozilla.org/MPL/2.0/. --> | ||||
| <html dir="ltr" xml:lang="en-US" lang="en-US"> | ||||
|   <head> | ||||
|     <meta charset="utf8"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <script> | ||||
|       let ready; | ||||
|       window.wrtReady = new Promise(r => ready = r); | ||||
|  | ||||
|       let arrived; | ||||
|       window.messageArrived = new Promise(r => arrived = r); | ||||
|  | ||||
|       window.addEventListener("message", e => { | ||||
|         window.message = e.data; | ||||
|         arrived(); | ||||
|       }); | ||||
|  | ||||
|       window.addEventListener("load", () => { | ||||
|         setTimeout(ready, 100); | ||||
|       }); | ||||
|     </script> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										305
									
								
								src/zen/tests/mochitests/reportbrokensite/send_more_info.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								src/zen/tests/mochitests/reportbrokensite/send_more_info.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,305 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /* Helper methods for testing the "send more info" link | ||||
|  * of the Report Broken Site feature. | ||||
|  */ | ||||
|  | ||||
| /* import-globals-from head.js */ | ||||
| /* import-globals-from send.js */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| Services.scriptloader.loadSubScript( | ||||
|   getRootDirectory(gTestPath) + "send.js", | ||||
|   this | ||||
| ); | ||||
|  | ||||
| async function reformatExpectedWebCompatInfo(tab, overrides) { | ||||
|   const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); | ||||
|   const snapshot = await Troubleshoot.snapshot(); | ||||
|   const expected = await getExpectedWebCompatInfo(tab, snapshot, true); | ||||
|   const { browserInfo, tabInfo } = expected; | ||||
|   const { app, graphics, prefs, security } = browserInfo; | ||||
|   const { | ||||
|     applicationName, | ||||
|     defaultUseragentString, | ||||
|     fissionEnabled, | ||||
|     osArchitecture, | ||||
|     osName, | ||||
|     osVersion, | ||||
|     updateChannel, | ||||
|     version, | ||||
|   } = app; | ||||
|   const { devicePixelRatio, hasTouchScreen } = graphics; | ||||
|   const { antitracking, languages, useragentString } = tabInfo; | ||||
|  | ||||
|   const addons = overrides.addons || []; | ||||
|   const experiments = overrides.experiments || []; | ||||
|   const atOverrides = overrides.antitracking; | ||||
|   const blockList = atOverrides?.blockList ?? antitracking.blockList; | ||||
|   const hasMixedActiveContentBlocked = | ||||
|     atOverrides?.hasMixedActiveContentBlocked ?? | ||||
|     antitracking.hasMixedActiveContentBlocked; | ||||
|   const hasMixedDisplayContentBlocked = | ||||
|     atOverrides?.hasMixedDisplayContentBlocked ?? | ||||
|     antitracking.hasMixedDisplayContentBlocked; | ||||
|   const hasTrackingContentBlocked = | ||||
|     atOverrides?.hasTrackingContentBlocked ?? | ||||
|     antitracking.hasTrackingContentBlocked; | ||||
|   const isPrivateBrowsing = | ||||
|     atOverrides?.isPrivateBrowsing ?? antitracking.isPrivateBrowsing; | ||||
|   const btpHasPurgedSite = | ||||
|     atOverrides?.btpHasPurgedSite ?? antitracking.btpHasPurgedSite; | ||||
|   const etpCategory = atOverrides?.etpCategory ?? antitracking.etpCategory; | ||||
|  | ||||
|   const extra_labels = []; | ||||
|   const frameworks = overrides.frameworks ?? { | ||||
|     fastclick: false, | ||||
|     mobify: false, | ||||
|     marfeel: false, | ||||
|   }; | ||||
|  | ||||
|   // ignore the console log unless explicily testing for it. | ||||
|   const consoleLog = overrides.consoleLog ?? (() => true); | ||||
|  | ||||
|   const finalPrefs = {}; | ||||
|   for (const [key, pref] of Object.entries({ | ||||
|     cookieBehavior: "network.cookie.cookieBehavior", | ||||
|     forcedAcceleratedLayers: "layers.acceleration.force-enabled", | ||||
|     globalPrivacyControlEnabled: "privacy.globalprivacycontrol.enabled", | ||||
|     installtriggerEnabled: "extensions.InstallTrigger.enabled", | ||||
|     opaqueResponseBlocking: "browser.opaqueResponseBlocking", | ||||
|     resistFingerprintingEnabled: "privacy.resistFingerprinting", | ||||
|     softwareWebrender: "gfx.webrender.software", | ||||
|     thirdPartyCookieBlockingEnabled: | ||||
|       "network.cookie.cookieBehavior.optInPartitioning", | ||||
|     thirdPartyCookieBlockingEnabledInPbm: | ||||
|       "network.cookie.cookieBehavior.optInPartitioning.pbmode", | ||||
|   })) { | ||||
|     if (key in prefs) { | ||||
|       finalPrefs[pref] = prefs[key]; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const reformatted = { | ||||
|     blockList, | ||||
|     details: { | ||||
|       additionalData: { | ||||
|         addons, | ||||
|         applicationName, | ||||
|         blockList, | ||||
|         buildId: snapshot.application.buildID, | ||||
|         devicePixelRatio: parseInt(devicePixelRatio), | ||||
|         experiments, | ||||
|         finalUserAgent: useragentString, | ||||
|         fissionEnabled, | ||||
|         gfxData: { | ||||
|           devices(actual) { | ||||
|             const devices = getExpectedGraphicsDevices(snapshot); | ||||
|             return compareGraphicsDevices(devices, actual); | ||||
|           }, | ||||
|           drivers(actual) { | ||||
|             const drvs = getExpectedGraphicsDrivers(snapshot); | ||||
|             return compareGraphicsDrivers(drvs, actual); | ||||
|           }, | ||||
|           features(actual) { | ||||
|             const features = getExpectedGraphicsFeatures(snapshot); | ||||
|             return areObjectsEqual(actual, features); | ||||
|           }, | ||||
|           hasTouchScreen, | ||||
|           monitors(actual) { | ||||
|             return areObjectsEqual(actual, gfxInfo.getMonitors()); | ||||
|           }, | ||||
|         }, | ||||
|         hasMixedActiveContentBlocked, | ||||
|         hasMixedDisplayContentBlocked, | ||||
|         hasTrackingContentBlocked, | ||||
|         btpHasPurgedSite, | ||||
|         isPB: isPrivateBrowsing, | ||||
|         etpCategory, | ||||
|         languages, | ||||
|         locales: snapshot.intl.localeService.available, | ||||
|         memoryMB: browserInfo.system.memory, | ||||
|         osArchitecture, | ||||
|         osName, | ||||
|         osVersion, | ||||
|         prefs: finalPrefs, | ||||
|         version, | ||||
|       }, | ||||
|       blockList, | ||||
|       channel: updateChannel, | ||||
|       consoleLog, | ||||
|       defaultUserAgent: defaultUseragentString, | ||||
|       frameworks, | ||||
|       hasTouchScreen, | ||||
|       "gfx.webrender.software": prefs.softwareWebrender, | ||||
|       "mixed active content blocked": hasMixedActiveContentBlocked, | ||||
|       "mixed passive content blocked": hasMixedDisplayContentBlocked, | ||||
|       "tracking content blocked": hasTrackingContentBlocked | ||||
|         ? `true (${blockList})` | ||||
|         : "false", | ||||
|       "btp has purged site": btpHasPurgedSite, | ||||
|     }, | ||||
|     extra_labels, | ||||
|     src: "desktop-reporter", | ||||
|     utm_campaign: "report-broken-site", | ||||
|     utm_source: "desktop-reporter", | ||||
|   }; | ||||
|  | ||||
|   const { gfxData } = reformatted.details.additionalData; | ||||
|   for (const optional of [ | ||||
|     "direct2DEnabled", | ||||
|     "directWriteEnabled", | ||||
|     "directWriteVersion", | ||||
|     "clearTypeParameters", | ||||
|     "targetFrameRate", | ||||
|   ]) { | ||||
|     if (optional in snapshot.graphics) { | ||||
|       gfxData[optional] = snapshot.graphics[optional]; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // We only care about this pref on Linux right now on webcompat.com. | ||||
|   if (AppConstants.platform != "linux") { | ||||
|     delete finalPrefs["layers.acceleration.force-enabled"]; | ||||
|   } else { | ||||
|     reformatted.details["layers.acceleration.force-enabled"] = | ||||
|       finalPrefs["layers.acceleration.force-enabled"]; | ||||
|   } | ||||
|  | ||||
|   // Only bother adding the security key if it has any data | ||||
|   if (Object.values(security).filter(e => e).length) { | ||||
|     reformatted.details.additionalData.sec = security; | ||||
|   } | ||||
|  | ||||
|   const expectedCodecs = snapshot.media.codecSupportInfo | ||||
|     .replaceAll(" NONE", "") | ||||
|     .split("\n") | ||||
|     .sort() | ||||
|     .join("\n"); | ||||
|   if (expectedCodecs) { | ||||
|     reformatted.details.additionalData.gfxData.codecSupport = rawActual => { | ||||
|       const actual = Object.entries(rawActual) | ||||
|         .map( | ||||
|           ([ | ||||
|             name, | ||||
|             { hardwareDecode, softwareDecode, hardwareEncode, softwareEncode }, | ||||
|           ]) => | ||||
|             ( | ||||
|               `${name} ` + | ||||
|               `${softwareDecode ? "SWDEC " : ""}` + | ||||
|               `${hardwareDecode ? "HWDEC " : ""}` + | ||||
|               `${softwareEncode ? "SWENC " : ""}` + | ||||
|               `${hardwareEncode ? "HWENC " : ""}` | ||||
|             ).trim() | ||||
|         ) | ||||
|         .sort() | ||||
|         .join("\n"); | ||||
|       return areObjectsEqual(actual, expectedCodecs); | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   if (blockList != "basic") { | ||||
|     extra_labels.push(`type-tracking-protection-${blockList}`); | ||||
|   } | ||||
|  | ||||
|   if (overrides.expectNoTabDetails) { | ||||
|     delete reformatted.details.frameworks; | ||||
|     delete reformatted.details.consoleLog; | ||||
|     delete reformatted.details["mixed active content blocked"]; | ||||
|     delete reformatted.details["mixed passive content blocked"]; | ||||
|     delete reformatted.details["tracking content blocked"]; | ||||
|     delete reformatted.details["btp has purged site"]; | ||||
|   } else { | ||||
|     const { fastclick, mobify, marfeel } = frameworks; | ||||
|     if (fastclick) { | ||||
|       extra_labels.push("type-fastclick"); | ||||
|       reformatted.details.fastclick = true; | ||||
|     } | ||||
|     if (mobify) { | ||||
|       extra_labels.push("type-mobify"); | ||||
|       reformatted.details.mobify = true; | ||||
|     } | ||||
|     if (marfeel) { | ||||
|       extra_labels.push("type-marfeel"); | ||||
|       reformatted.details.marfeel = true; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   extra_labels.sort(); | ||||
|  | ||||
|   return reformatted; | ||||
| } | ||||
|  | ||||
| async function testSendMoreInfo(tab, menu, expectedOverrides = {}) { | ||||
|   const url = expectedOverrides.url ?? menu.win.gBrowser.currentURI.spec; | ||||
|   const description = expectedOverrides.description ?? ""; | ||||
|  | ||||
|   let rbs = await menu.openAndPrefillReportBrokenSite(url, description); | ||||
|  | ||||
|   const receivedData = await rbs.clickSendMoreInfo(); | ||||
|   await checkWebcompatComPayload( | ||||
|     tab, | ||||
|     url, | ||||
|     description, | ||||
|     expectedOverrides, | ||||
|     receivedData | ||||
|   ); | ||||
|  | ||||
|   // re-opening the panel, the url and description should be reset | ||||
|   rbs = await menu.openReportBrokenSite(); | ||||
|   rbs.isMainViewResetToCurrentTab(); | ||||
|   rbs.close(); | ||||
| } | ||||
|  | ||||
| async function testWebcompatComFallback(tab, menu) { | ||||
|   const url = menu.win.gBrowser.currentURI.spec; | ||||
|   const receivedData = | ||||
|     await menu.clickReportBrokenSiteAndAwaitWebCompatTabData(); | ||||
|   await checkWebcompatComPayload(tab, url, "", {}, receivedData); | ||||
|   menu.close(); | ||||
| } | ||||
|  | ||||
| async function checkWebcompatComPayload( | ||||
|   tab, | ||||
|   url, | ||||
|   description, | ||||
|   expectedOverrides, | ||||
|   receivedData | ||||
| ) { | ||||
|   const expected = await reformatExpectedWebCompatInfo(tab, expectedOverrides); | ||||
|   expected.url = url; | ||||
|   expected.description = description; | ||||
|  | ||||
|   // sanity checks | ||||
|   const { message } = receivedData; | ||||
|   const { details } = message; | ||||
|   const { additionalData } = details; | ||||
|   ok(message.url?.length, "Got a URL"); | ||||
|   ok(["basic", "strict"].includes(details.blockList), "Got a blockList"); | ||||
|   ok(additionalData.applicationName?.length, "Got an app name"); | ||||
|   ok(additionalData.osArchitecture?.length, "Got an OS arch"); | ||||
|   ok(additionalData.osName?.length, "Got an OS name"); | ||||
|   ok(additionalData.osVersion?.length, "Got an OS version"); | ||||
|   ok(additionalData.version?.length, "Got an app version"); | ||||
|   ok(details.channel?.length, "Got an app channel"); | ||||
|   ok(details.defaultUserAgent?.length, "Got a default UA string"); | ||||
|   ok(additionalData.finalUserAgent?.length, "Got a final UA string"); | ||||
|  | ||||
|   // If we're sending any tab-specific data (which includes console logs), | ||||
|   // check that there is also a valid screenshot. | ||||
|   if ("consoleLog" in details) { | ||||
|     const isScreenshotValid = await new Promise(done => { | ||||
|       var image = new Image(); | ||||
|       image.onload = () => done(image.width > 0); | ||||
|       image.onerror = () => done(false); | ||||
|       image.src = receivedData.screenshot; | ||||
|     }); | ||||
|     ok(isScreenshotValid, "Got a valid screenshot"); | ||||
|   } | ||||
|  | ||||
|   filterFrameworkDetectorFails(message.details, expected.details); | ||||
|  | ||||
|   ok(areObjectsEqual(message, expected), "sent info matches expectations"); | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/zen/tests/mochitests/safebrowsing/browser.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/zen/tests/mochitests/safebrowsing/browser.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| [DEFAULT] | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "empty_file.html", | ||||
| ] | ||||
|  | ||||
| ["browser_bug400731.js"] | ||||
|  | ||||
| ["browser_bug415846.js"] | ||||
| skip-if = ["true"] # Bug 1248632 | ||||
|  | ||||
| ["browser_mixedcontent_aboutblocked.js"] | ||||
|  | ||||
| ["browser_whitelisted.js"] | ||||
							
								
								
									
										65
									
								
								src/zen/tests/mochitests/safebrowsing/browser_bug400731.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/zen/tests/mochitests/safebrowsing/browser_bug400731.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| /* Check presence of the "Ignore this warning" button */ | ||||
|  | ||||
| function checkWarningState() { | ||||
|   return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { | ||||
|     return !!content.document.getElementById("ignore_warning_link"); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| add_task(async function testMalware() { | ||||
|   await new Promise(resolve => waitForDBInit(resolve)); | ||||
|  | ||||
|   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); | ||||
|  | ||||
|   const url = "http://www.itisatrap.org/firefox/its-an-attack.html"; | ||||
|   BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url); | ||||
|   await BrowserTestUtils.browserLoaded( | ||||
|     gBrowser.selectedBrowser, | ||||
|     false, | ||||
|     url, | ||||
|     true | ||||
|   ); | ||||
|  | ||||
|   let buttonPresent = await checkWarningState(); | ||||
|   ok(buttonPresent, "Ignore warning link should be present for malware"); | ||||
| }); | ||||
|  | ||||
| add_task(async function testUnwanted() { | ||||
|   Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", false); | ||||
|  | ||||
|   // Now launch the unwanted software test | ||||
|   const url = "http://www.itisatrap.org/firefox/unwanted.html"; | ||||
|   BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url); | ||||
|   await BrowserTestUtils.browserLoaded( | ||||
|     gBrowser.selectedBrowser, | ||||
|     false, | ||||
|     url, | ||||
|     true | ||||
|   ); | ||||
|  | ||||
|   // Confirm that "Ignore this warning" is visible - bug 422410 | ||||
|   let buttonPresent = await checkWarningState(); | ||||
|   ok( | ||||
|     !buttonPresent, | ||||
|     "Ignore warning link should be missing for unwanted software" | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function testPhishing() { | ||||
|   Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", true); | ||||
|  | ||||
|   // Now launch the phishing test | ||||
|   const url = "http://www.itisatrap.org/firefox/its-a-trap.html"; | ||||
|   BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, url); | ||||
|   await BrowserTestUtils.browserLoaded( | ||||
|     gBrowser.selectedBrowser, | ||||
|     false, | ||||
|     url, | ||||
|     true | ||||
|   ); | ||||
|  | ||||
|   let buttonPresent = await checkWarningState(); | ||||
|   ok(buttonPresent, "Ignore warning link should be present for phishing"); | ||||
|  | ||||
|   gBrowser.removeCurrentTab(); | ||||
| }); | ||||
							
								
								
									
										98
									
								
								src/zen/tests/mochitests/safebrowsing/browser_bug415846.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/zen/tests/mochitests/safebrowsing/browser_bug415846.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| /* Check for the correct behaviour of the report web forgery/not a web forgery | ||||
| menu items. | ||||
|  | ||||
| Mac makes this astonishingly painful to test since their help menu is special magic, | ||||
| but we can at least test it on the other platforms.*/ | ||||
|  | ||||
| const NORMAL_PAGE = "http://example.com"; | ||||
| const PHISH_PAGE = "http://www.itisatrap.org/firefox/its-a-trap.html"; | ||||
|  | ||||
| /** | ||||
|  * Opens a new tab and browses to some URL, tests for the existence | ||||
|  * of the phishing menu items, and then runs a test function to check | ||||
|  * the state of the menu once opened. This function will take care of | ||||
|  * opening and closing the menu. | ||||
|  * | ||||
|  * @param url (string) | ||||
|  *        The URL to browse the tab to. | ||||
|  * @param testFn (function) | ||||
|  *        The function to run once the menu has been opened. This | ||||
|  *        function will be passed the "reportMenu" and "errorMenu" | ||||
|  *        DOM nodes as arguments, in that order. This function | ||||
|  *        should not yield anything. | ||||
|  * @returns Promise | ||||
|  */ | ||||
| function check_menu_at_page(url, testFn) { | ||||
|   return BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "about:blank", | ||||
|     }, | ||||
|     async function (browser) { | ||||
|       // We don't get load events when the DocShell redirects to error | ||||
|       // pages, but we do get DOMContentLoaded, so we'll wait for that. | ||||
|       let dclPromise = SpecialPowers.spawn(browser, [], async function () { | ||||
|         await ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", false); | ||||
|       }); | ||||
|       BrowserTestUtils.startLoadingURIString(browser, url); | ||||
|       await dclPromise; | ||||
|  | ||||
|       let menu = document.getElementById("menu_HelpPopup"); | ||||
|       ok(menu, "Help menu should exist"); | ||||
|  | ||||
|       let reportMenu = document.getElementById( | ||||
|         "menu_HelpPopup_reportPhishingtoolmenu" | ||||
|       ); | ||||
|       ok(reportMenu, "Report phishing menu item should exist"); | ||||
|  | ||||
|       let errorMenu = document.getElementById( | ||||
|         "menu_HelpPopup_reportPhishingErrortoolmenu" | ||||
|       ); | ||||
|       ok(errorMenu, "Report phishing error menu item should exist"); | ||||
|  | ||||
|       let menuOpen = BrowserTestUtils.waitForEvent(menu, "popupshown"); | ||||
|       menu.openPopup(null, "", 0, 0, false, null); | ||||
|       await menuOpen; | ||||
|  | ||||
|       testFn(reportMenu, errorMenu); | ||||
|  | ||||
|       let menuClose = BrowserTestUtils.waitForEvent(menu, "popuphidden"); | ||||
|       menu.hidePopup(); | ||||
|       await menuClose; | ||||
|     } | ||||
|   ); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Tests that we show the "Report this page" menu item at a normal | ||||
|  * page. | ||||
|  */ | ||||
| add_task(async function () { | ||||
|   await check_menu_at_page(NORMAL_PAGE, (reportMenu, errorMenu) => { | ||||
|     ok( | ||||
|       !reportMenu.hidden, | ||||
|       "Report phishing menu should be visible on normal sites" | ||||
|     ); | ||||
|     ok( | ||||
|       errorMenu.hidden, | ||||
|       "Report error menu item should be hidden on normal sites" | ||||
|     ); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Tests that we show the "Report this page is okay" menu item at | ||||
|  * a reported attack site. | ||||
|  */ | ||||
| add_task(async function () { | ||||
|   await check_menu_at_page(PHISH_PAGE, (reportMenu, errorMenu) => { | ||||
|     ok( | ||||
|       reportMenu.hidden, | ||||
|       "Report phishing menu should be hidden on phishing sites" | ||||
|     ); | ||||
|     ok( | ||||
|       !errorMenu.hidden, | ||||
|       "Report error menu item should be visible on phishing sites" | ||||
|     ); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,47 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| const SECURE_CONTAINER_URL = | ||||
|   "https://example.com/browser/browser/components/safebrowsing/content/test/empty_file.html"; | ||||
|  | ||||
| add_task(async function testNormalBrowsing() { | ||||
|   await SpecialPowers.pushPrefEnv({ | ||||
|     set: [["browser.safebrowsing.only_top_level", false]], | ||||
|   }); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     SECURE_CONTAINER_URL, | ||||
|     async function (browser) { | ||||
|       // Before we load the phish url, we have to make sure the hard-coded | ||||
|       // black list has been added to the database. | ||||
|       await new Promise(resolve => waitForDBInit(resolve)); | ||||
|  | ||||
|       let promise = new Promise(resolve => { | ||||
|         // Register listener before loading phish URL. | ||||
|         let removeFunc = BrowserTestUtils.addContentEventListener( | ||||
|           browser, | ||||
|           "AboutBlockedLoaded", | ||||
|           () => { | ||||
|             removeFunc(); | ||||
|             resolve(); | ||||
|           }, | ||||
|           { wantUntrusted: true } | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       await SpecialPowers.spawn( | ||||
|         browser, | ||||
|         [PHISH_URL], | ||||
|         async function (aPhishUrl) { | ||||
|           // Create an iframe which is going to load a phish url. | ||||
|           let iframe = content.document.createElement("iframe"); | ||||
|           iframe.src = aPhishUrl; | ||||
|           content.document.body.appendChild(iframe); | ||||
|         } | ||||
|       ); | ||||
|  | ||||
|       await promise; | ||||
|       ok(true, "about:blocked is successfully loaded!"); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										46
									
								
								src/zen/tests/mochitests/safebrowsing/browser_whitelisted.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/zen/tests/mochitests/safebrowsing/browser_whitelisted.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| /* Ensure that hostnames in the whitelisted pref are not blocked. */ | ||||
|  | ||||
| const PREF_WHITELISTED_HOSTNAMES = "urlclassifier.skipHostnames"; | ||||
| const TEST_PAGE = "http://www.itisatrap.org/firefox/its-an-attack.html"; | ||||
| var tabbrowser = null; | ||||
|  | ||||
| registerCleanupFunction(function () { | ||||
|   tabbrowser = null; | ||||
|   Services.prefs.clearUserPref(PREF_WHITELISTED_HOSTNAMES); | ||||
|   while (gBrowser.tabs.length > 1) { | ||||
|     gBrowser.removeCurrentTab(); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| function testBlockedPage() { | ||||
|   info("Non-whitelisted pages must be blocked"); | ||||
|   ok(true, "about:blocked was shown"); | ||||
| } | ||||
|  | ||||
| function testWhitelistedPage(window) { | ||||
|   info("Whitelisted pages must be skipped"); | ||||
|   var getmeout_button = window.document.getElementById("getMeOutButton"); | ||||
|   var ignorewarning_button = window.document.getElementById( | ||||
|     "ignoreWarningButton" | ||||
|   ); | ||||
|   ok(!getmeout_button, "GetMeOut button not present"); | ||||
|   ok(!ignorewarning_button, "IgnoreWarning button not present"); | ||||
| } | ||||
|  | ||||
| add_task(async function testNormalBrowsing() { | ||||
|   tabbrowser = gBrowser; | ||||
|   let tab = (tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser)); | ||||
|  | ||||
|   info("Load a test page that's whitelisted"); | ||||
|   Services.prefs.setCharPref( | ||||
|     PREF_WHITELISTED_HOSTNAMES, | ||||
|     "example.com,www.ItIsaTrap.org,example.net" | ||||
|   ); | ||||
|   await promiseTabLoadEvent(tab, TEST_PAGE, "load"); | ||||
|   testWhitelistedPage(tab.ownerGlobal); | ||||
|  | ||||
|   info("Load a test page that's no longer whitelisted"); | ||||
|   Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, ""); | ||||
|   await promiseTabLoadEvent(tab, TEST_PAGE, "AboutBlockedLoaded"); | ||||
|   testBlockedPage(tab.ownerGlobal); | ||||
| }); | ||||
							
								
								
									
										1
									
								
								src/zen/tests/mochitests/safebrowsing/empty_file.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/zen/tests/mochitests/safebrowsing/empty_file.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <html><body></body></html> | ||||
							
								
								
									
										103
									
								
								src/zen/tests/mochitests/safebrowsing/head.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/zen/tests/mochitests/safebrowsing/head.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| // This url must sync with the table, url in SafeBrowsing.sys.mjs addMozEntries | ||||
| const PHISH_TABLE = "moztest-phish-simple"; | ||||
| const PHISH_URL = "https://www.itisatrap.org/firefox/its-a-trap.html"; | ||||
|  | ||||
| /** | ||||
|  * Waits for a load (or custom) event to finish in a given tab. If provided | ||||
|  * load an uri into the tab. | ||||
|  * | ||||
|  * @param tab | ||||
|  *        The tab to load into. | ||||
|  * @param [optional] url | ||||
|  *        The url to load, or the current url. | ||||
|  * @param [optional] event | ||||
|  *        The load event type to wait for.  Defaults to "load". | ||||
|  * @return {Promise} resolved when the event is handled. | ||||
|  * @resolves to the received event | ||||
|  * @rejects if a valid load event is not received within a meaningful interval | ||||
|  */ | ||||
| function promiseTabLoadEvent(tab, url, eventType = "load") { | ||||
|   info(`Wait tab event: ${eventType}`); | ||||
|  | ||||
|   function handle(loadedUrl) { | ||||
|     if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { | ||||
|       info(`Skipping spurious load event for ${loadedUrl}`); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     info("Tab event received: load"); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   let loaded; | ||||
|   if (eventType === "load") { | ||||
|     loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); | ||||
|   } else { | ||||
|     // No need to use handle. | ||||
|     loaded = BrowserTestUtils.waitForContentEvent( | ||||
|       tab.linkedBrowser, | ||||
|       eventType, | ||||
|       true, | ||||
|       undefined, | ||||
|       true | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   if (url) { | ||||
|     BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url); | ||||
|   } | ||||
|  | ||||
|   return loaded; | ||||
| } | ||||
|  | ||||
| // This function is mostly ported from classifierCommon.js | ||||
| // under toolkit/components/url-classifier/tests/mochitest. | ||||
| function waitForDBInit(callback) { | ||||
|   // Since there are two cases that may trigger the callback, | ||||
|   // we have to carefully avoid multiple callbacks and observer | ||||
|   // leaking. | ||||
|   let didCallback = false; | ||||
|   function callbackOnce() { | ||||
|     if (!didCallback) { | ||||
|       Services.obs.removeObserver(obsFunc, "mozentries-update-finished"); | ||||
|       callback(); | ||||
|     } | ||||
|     didCallback = true; | ||||
|   } | ||||
|  | ||||
|   // The first part: listen to internal event. | ||||
|   function obsFunc() { | ||||
|     ok(true, "Received internal event!"); | ||||
|     callbackOnce(); | ||||
|   } | ||||
|   Services.obs.addObserver(obsFunc, "mozentries-update-finished"); | ||||
|  | ||||
|   // The second part: we might have missed the event. Just do | ||||
|   // an internal database lookup to confirm if the url has been | ||||
|   // added. | ||||
|   let principal = Services.scriptSecurityManager.createContentPrincipal( | ||||
|     Services.io.newURI(PHISH_URL), | ||||
|     {} | ||||
|   ); | ||||
|  | ||||
|   let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"].getService( | ||||
|     Ci.nsIUrlClassifierDBService | ||||
|   ); | ||||
|   dbService.lookup(principal, PHISH_TABLE, value => { | ||||
|     if (value === PHISH_TABLE) { | ||||
|       ok(true, "DB lookup success!"); | ||||
|       callbackOnce(); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  | ||||
| Services.prefs.setCharPref( | ||||
|   "urlclassifier.malwareTable", | ||||
|   "moztest-malware-simple,moztest-unwanted-simple,moztest-harmful-simple" | ||||
| ); | ||||
| Services.prefs.setCharPref("urlclassifier.phishTable", "moztest-phish-simple"); | ||||
| Services.prefs.setCharPref( | ||||
|   "urlclassifier.blockedTable", | ||||
|   "moztest-block-simple" | ||||
| ); | ||||
| SafeBrowsing.init(); | ||||
							
								
								
									
										101
									
								
								src/zen/tests/mochitests/shell/browser.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/zen/tests/mochitests/shell/browser.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| [DEFAULT] | ||||
|  | ||||
| ["browser_1119088.js"] | ||||
| support-files = ["mac_desktop_image.py"] | ||||
| run-if = ["os == 'mac'"] | ||||
| tags = "os_integration" | ||||
| skip-if = ["os == 'mac' && os_version == '14.70' && processor == 'x86_64'"] # Bug 1869703 | ||||
|  | ||||
| ["browser_420786.js"] | ||||
| run-if = ["os == 'linux'"] | ||||
|  | ||||
| ["browser_633221.js"] | ||||
| run-if = ["os == 'linux'"] | ||||
|  | ||||
| ["browser_createWindowsShortcut.js"] | ||||
| run-if = ["os == 'win'"] | ||||
|  | ||||
| ["browser_doesAppNeedPin.js"] | ||||
|  | ||||
| ["browser_headless_screenshot_1.js"] | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "headless.html", | ||||
| ] | ||||
| skip-if = [ | ||||
|   "os == 'win'", | ||||
|   "ccov", | ||||
|   "tsan", # Bug 1429950, Bug 1583315, Bug 1696109, Bug 1701449 | ||||
| ] | ||||
| tags = "os_integration" | ||||
|  | ||||
| ["browser_headless_screenshot_2.js"] | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "headless.html", | ||||
| ] | ||||
| skip-if = [ | ||||
|   "os == 'win'", | ||||
|   "ccov", | ||||
|   "tsan", # Bug 1429950, Bug 1583315, Bug 1696109, Bug 1701449 | ||||
| ] | ||||
|  | ||||
| ["browser_headless_screenshot_3.js"] | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "headless.html", | ||||
| ] | ||||
| skip-if = [ | ||||
|   "os == 'win'", | ||||
|   "ccov", | ||||
|   "tsan", # Bug 1429950, Bug 1583315, Bug 1696109, Bug 1701449 | ||||
| ] | ||||
|  | ||||
| ["browser_headless_screenshot_4.js"] | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "headless.html", | ||||
| ] | ||||
| skip-if = [ | ||||
|   "os == 'win'", | ||||
|   "ccov", | ||||
|   "tsan", # Bug 1429950, Bug 1583315, Bug 1696109, Bug 1701449 | ||||
| ] | ||||
|  | ||||
| ["browser_headless_screenshot_cross_origin.js"] | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "headless_cross_origin.html", | ||||
|   "headless_iframe.html", | ||||
| ] | ||||
| skip-if = [ | ||||
|   "os == 'win'", | ||||
|   "ccov", | ||||
|   "tsan", # Bug 1429950, Bug 1583315, Bug 1696109, Bug 1701449 | ||||
| ] | ||||
|  | ||||
| ["browser_headless_screenshot_redirect.js"] | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "headless.html", | ||||
|   "headless_redirect.html", | ||||
|   "headless_redirect.html^headers^", | ||||
| ] | ||||
| skip-if = [ | ||||
|   "os == 'win'", | ||||
|   "ccov", | ||||
|   "tsan", # Bug 1429950, Bug 1583315, Bug 1696109, Bug 1701449 | ||||
| ] | ||||
|  | ||||
| ["browser_processAUMID.js"] | ||||
| run-if = ["os == 'win'"] | ||||
|  | ||||
| ["browser_setDefaultBrowser.js"] | ||||
| tags = "os_integration" | ||||
|  | ||||
| ["browser_setDefaultPDFHandler.js"] | ||||
| run-if = ["os == 'win'"] | ||||
| tags = "os_integration" | ||||
|  | ||||
| ["browser_setDesktopBackgroundPreview.js"] | ||||
| tags = "os_integration" | ||||
							
								
								
									
										179
									
								
								src/zen/tests/mochitests/shell/browser_1119088.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/zen/tests/mochitests/shell/browser_1119088.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | ||||
| // Where we save the desktop background to (~/Pictures). | ||||
| const NS_OSX_PICTURE_DOCUMENTS_DIR = "Pct"; | ||||
|  | ||||
| // Paths used to run the CLI command (python script) that is used to | ||||
| // 1) check the desktop background image matches what we set it to via | ||||
| //    nsIShellService::setDesktopBackground() and | ||||
| // 2) revert the desktop background image to the OS default | ||||
|  | ||||
| let kPythonPath = "/usr/bin/python"; | ||||
| if (AppConstants.isPlatformAndVersionAtLeast("macosx", 23.0)) { | ||||
|   kPythonPath = "/usr/local/bin/python3"; | ||||
| } | ||||
|  | ||||
| const kDesktopCheckerScriptPath = | ||||
|   "browser/browser/components/shell/test/mac_desktop_image.py"; | ||||
| const kDefaultBackgroundImage_10_14 = | ||||
|   "/Library/Desktop Pictures/Solid Colors/Teal.png"; | ||||
| const kDefaultBackgroundImage_10_15 = | ||||
|   "/System/Library/Desktop Pictures/Solid Colors/Teal.png"; | ||||
|  | ||||
| ChromeUtils.defineESModuleGetters(this, { | ||||
|   FileUtils: "resource://gre/modules/FileUtils.sys.mjs", | ||||
| }); | ||||
|  | ||||
| function getPythonExecutableFile() { | ||||
|   let python = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); | ||||
|   info(`Using python at location ${kPythonPath}`); | ||||
|   python.initWithPath(kPythonPath); | ||||
|   return python; | ||||
| } | ||||
|  | ||||
| function createProcess() { | ||||
|   return Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); | ||||
| } | ||||
|  | ||||
| // Use a CLI command to set the desktop background to |imagePath|. Returns the | ||||
| // exit code of the CLI command which reflects whether or not the background | ||||
| // image was successfully set. Returns 0 on success. | ||||
| function setDesktopBackgroundCLI(imagePath) { | ||||
|   let setBackgroundProcess = createProcess(); | ||||
|   setBackgroundProcess.init(getPythonExecutableFile()); | ||||
|   let args = [ | ||||
|     kDesktopCheckerScriptPath, | ||||
|     "--verbose", | ||||
|     "--set-background-image", | ||||
|     imagePath, | ||||
|   ]; | ||||
|   setBackgroundProcess.run(true, args, args.length); | ||||
|   return setBackgroundProcess.exitValue; | ||||
| } | ||||
|  | ||||
| // Check the desktop background is |imagePath| using a CLI command. | ||||
| // Returns the exit code of the CLI command which reflects whether or not | ||||
| // the provided image path matches the path of the current desktop background | ||||
| // image. A return value of 0 indicates success/match. | ||||
| function checkDesktopBackgroundCLI(imagePath) { | ||||
|   let checkBackgroundProcess = createProcess(); | ||||
|   checkBackgroundProcess.init(getPythonExecutableFile()); | ||||
|   let args = [ | ||||
|     kDesktopCheckerScriptPath, | ||||
|     "--verbose", | ||||
|     "--check-background-image", | ||||
|     imagePath, | ||||
|   ]; | ||||
|   checkBackgroundProcess.run(true, args, args.length); | ||||
|   return checkBackgroundProcess.exitValue; | ||||
| } | ||||
|  | ||||
| // Use the python script to set/check the desktop background is |imagePath| | ||||
| function setAndCheckDesktopBackgroundCLI(imagePath) { | ||||
|   Assert.ok(FileUtils.File(imagePath).exists(), `${imagePath} exists`); | ||||
|  | ||||
|   let setExitCode = setDesktopBackgroundCLI(imagePath); | ||||
|   Assert.equal(setExitCode, 0, `Setting background via CLI to ${imagePath}`); | ||||
|  | ||||
|   let checkExitCode = checkDesktopBackgroundCLI(imagePath); | ||||
|   Assert.equal(checkExitCode, 0, `Checking background via CLI is ${imagePath}`); | ||||
| } | ||||
|  | ||||
| // Restore the automation default background image. i.e., the default used | ||||
| // in the automated test environment, not the OS default. | ||||
| function restoreDefaultBackground() { | ||||
|   let defaultBackgroundPath; | ||||
|   if (AppConstants.isPlatformAndVersionAtLeast("macosx", 19)) { | ||||
|     defaultBackgroundPath = kDefaultBackgroundImage_10_15; | ||||
|   } else { | ||||
|     defaultBackgroundPath = kDefaultBackgroundImage_10_14; | ||||
|   } | ||||
|   setAndCheckDesktopBackgroundCLI(defaultBackgroundPath); | ||||
| } | ||||
|  | ||||
| add_setup(async function () { | ||||
|   await SpecialPowers.pushPrefEnv({ | ||||
|     set: [["test.wait300msAfterTabSwitch", true]], | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Tests "Set As Desktop Background" platform implementation on macOS. | ||||
|  * | ||||
|  * Sets the desktop background image to the browser logo from the about:logo | ||||
|  * page and verifies it was set successfully. Setting the desktop background | ||||
|  * (which uses the nsIShellService::setDesktopBackground() interface method) | ||||
|  * downloads the image to ~/Pictures using a unique file name and sets the | ||||
|  * desktop background to the downloaded file leaving the download in place. | ||||
|  * After setDesktopBackground() is called, the test uses a python script to | ||||
|  * validate that the current desktop background is in fact set to the | ||||
|  * downloaded logo. | ||||
|  */ | ||||
| add_task(async function () { | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "about:logo", | ||||
|     }, | ||||
|     async () => { | ||||
|       let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService( | ||||
|         Ci.nsIDirectoryServiceProvider | ||||
|       ); | ||||
|       let uuidGenerator = Services.uuid; | ||||
|       let shellSvc = Cc["@mozilla.org/browser/shell-service;1"].getService( | ||||
|         Ci.nsIShellService | ||||
|       ); | ||||
|  | ||||
|       // Ensure we are starting with the default background. Log a | ||||
|       // failure if we can not set the background to the default, but | ||||
|       // ignore the case where the background is not already set as that | ||||
|       // that may be due to a previous test failure. | ||||
|       restoreDefaultBackground(); | ||||
|  | ||||
|       // Generate a UUID (with non-alphanumberic characters removed) to build | ||||
|       // up a filename for the desktop background. Use a UUID to distinguish | ||||
|       // between runs so we won't be confused by images that were not properly | ||||
|       // cleaned up after previous runs. | ||||
|       let uuid = uuidGenerator.generateUUID().toString().replace(/\W/g, ""); | ||||
|  | ||||
|       // Set the background image path to be $HOME/Pictures/<UUID>.png. | ||||
|       // nsIShellService.setDesktopBackground() downloads the image to this | ||||
|       // path and then sets it as the desktop background image, leaving the | ||||
|       // image in place. | ||||
|       let backgroundImage = dirSvc.getFile(NS_OSX_PICTURE_DOCUMENTS_DIR, {}); | ||||
|       backgroundImage.append(uuid + ".png"); | ||||
|       if (backgroundImage.exists()) { | ||||
|         backgroundImage.remove(false); | ||||
|       } | ||||
|  | ||||
|       // For simplicity, we're going to reach in and access the image on the | ||||
|       // page directly, which means the page shouldn't be running in a remote | ||||
|       // browser. Thankfully, about:logo runs in the parent process for now. | ||||
|       Assert.ok( | ||||
|         !gBrowser.selectedBrowser.isRemoteBrowser, | ||||
|         "image can be accessed synchronously from the parent process" | ||||
|       ); | ||||
|       let image = gBrowser.selectedBrowser.contentDocument.images[0]; | ||||
|  | ||||
|       info(`Setting/saving desktop background to ${backgroundImage.path}`); | ||||
|  | ||||
|       // Saves the file in ~/Pictures | ||||
|       shellSvc.setDesktopBackground(image, 0, backgroundImage.leafName); | ||||
|  | ||||
|       await BrowserTestUtils.waitForCondition(() => backgroundImage.exists()); | ||||
|       info(`${backgroundImage.path} downloaded`); | ||||
|       Assert.ok( | ||||
|         FileUtils.File(backgroundImage.path).exists(), | ||||
|         `${backgroundImage.path} exists` | ||||
|       ); | ||||
|  | ||||
|       // Check that the desktop background image is the image we set above. | ||||
|       let exitCode = checkDesktopBackgroundCLI(backgroundImage.path); | ||||
|       Assert.equal(exitCode, 0, `background should be ${backgroundImage.path}`); | ||||
|  | ||||
|       // Restore the background image to the Mac default. | ||||
|       restoreDefaultBackground(); | ||||
|  | ||||
|       // We no longer need the downloaded image. | ||||
|       backgroundImage.remove(false); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										101
									
								
								src/zen/tests/mochitests/shell/browser_420786.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/zen/tests/mochitests/shell/browser_420786.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| const DG_BACKGROUND = "/desktop/gnome/background"; | ||||
| const DG_IMAGE_KEY = DG_BACKGROUND + "/picture_filename"; | ||||
| const DG_OPTION_KEY = DG_BACKGROUND + "/picture_options"; | ||||
| const DG_DRAW_BG_KEY = DG_BACKGROUND + "/draw_background"; | ||||
|  | ||||
| const GS_BG_SCHEMA = "org.gnome.desktop.background"; | ||||
| const GS_IMAGE_KEY = "picture-uri"; | ||||
| const GS_OPTION_KEY = "picture-options"; | ||||
| const GS_DRAW_BG_KEY = "draw-background"; | ||||
|  | ||||
| add_task(async function () { | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "about:logo", | ||||
|     }, | ||||
|     () => { | ||||
|       var brandName = Services.strings | ||||
|         .createBundle("chrome://branding/locale/brand.properties") | ||||
|         .GetStringFromName("brandShortName"); | ||||
|  | ||||
|       var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService( | ||||
|         Ci.nsIDirectoryServiceProvider | ||||
|       ); | ||||
|       var homeDir = dirSvc.getFile("Home", {}); | ||||
|  | ||||
|       var wpFile = homeDir.clone(); | ||||
|       wpFile.append(brandName + "_wallpaper.png"); | ||||
|  | ||||
|       // Backup the existing wallpaper so that this test doesn't change the user's | ||||
|       // settings. | ||||
|       var wpFileBackup = homeDir.clone(); | ||||
|       wpFileBackup.append(brandName + "_wallpaper.png.backup"); | ||||
|  | ||||
|       if (wpFileBackup.exists()) { | ||||
|         wpFileBackup.remove(false); | ||||
|       } | ||||
|  | ||||
|       if (wpFile.exists()) { | ||||
|         wpFile.copyTo(null, wpFileBackup.leafName); | ||||
|       } | ||||
|  | ||||
|       var shell = Cc["@mozilla.org/browser/shell-service;1"] | ||||
|         .getService(Ci.nsIShellService) | ||||
|         .QueryInterface(Ci.nsIGNOMEShellService); | ||||
|  | ||||
|       // For simplicity, we're going to reach in and access the image on the | ||||
|       // page directly, which means the page shouldn't be running in a remote | ||||
|       // browser. Thankfully, about:logo runs in the parent process for now. | ||||
|       Assert.ok( | ||||
|         !gBrowser.selectedBrowser.isRemoteBrowser, | ||||
|         "image can be accessed synchronously from the parent process" | ||||
|       ); | ||||
|  | ||||
|       var image = content.document.images[0]; | ||||
|  | ||||
|       let checkWallpaper, restoreSettings; | ||||
|       try { | ||||
|         const prevImage = shell.getGSettingsString(GS_BG_SCHEMA, GS_IMAGE_KEY); | ||||
|         const prevOption = shell.getGSettingsString( | ||||
|           GS_BG_SCHEMA, | ||||
|           GS_OPTION_KEY | ||||
|         ); | ||||
|  | ||||
|         checkWallpaper = function (position, expectedGSettingsPosition) { | ||||
|           shell.setDesktopBackground(image, position, ""); | ||||
|           ok(wpFile.exists(), "Wallpaper was written to disk"); | ||||
|           is( | ||||
|             shell.getGSettingsString(GS_BG_SCHEMA, GS_IMAGE_KEY), | ||||
|             encodeURI("file://" + wpFile.path), | ||||
|             "Wallpaper file GSettings key is correct" | ||||
|           ); | ||||
|           is( | ||||
|             shell.getGSettingsString(GS_BG_SCHEMA, GS_OPTION_KEY), | ||||
|             expectedGSettingsPosition, | ||||
|             "Wallpaper position GSettings key is correct" | ||||
|           ); | ||||
|         }; | ||||
|  | ||||
|         restoreSettings = function () { | ||||
|           shell.setGSettingsString(GS_BG_SCHEMA, GS_IMAGE_KEY, prevImage); | ||||
|           shell.setGSettingsString(GS_BG_SCHEMA, GS_OPTION_KEY, prevOption); | ||||
|         }; | ||||
|       } catch (e) {} | ||||
|  | ||||
|       checkWallpaper(Ci.nsIShellService.BACKGROUND_TILE, "wallpaper"); | ||||
|       checkWallpaper(Ci.nsIShellService.BACKGROUND_STRETCH, "stretched"); | ||||
|       checkWallpaper(Ci.nsIShellService.BACKGROUND_CENTER, "centered"); | ||||
|       checkWallpaper(Ci.nsIShellService.BACKGROUND_FILL, "zoom"); | ||||
|       checkWallpaper(Ci.nsIShellService.BACKGROUND_FIT, "scaled"); | ||||
|       checkWallpaper(Ci.nsIShellService.BACKGROUND_SPAN, "spanned"); | ||||
|  | ||||
|       restoreSettings(); | ||||
|  | ||||
|       // Restore files | ||||
|       if (wpFileBackup.exists()) { | ||||
|         wpFileBackup.moveTo(null, wpFile.leafName); | ||||
|       } | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										11
									
								
								src/zen/tests/mochitests/shell/browser_633221.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/zen/tests/mochitests/shell/browser_633221.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| function test() { | ||||
|   ShellService.setDefaultBrowser(false); | ||||
|   ok( | ||||
|     ShellService.isDefaultBrowser(true, false), | ||||
|     "we got here and are the default browser" | ||||
|   ); | ||||
|   ok( | ||||
|     ShellService.isDefaultBrowser(true, true), | ||||
|     "we got here and are the default browser" | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										218
									
								
								src/zen/tests/mochitests/shell/browser_createWindowsShortcut.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								src/zen/tests/mochitests/shell/browser_createWindowsShortcut.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| ChromeUtils.defineESModuleGetters(this, { | ||||
|   FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs", | ||||
|   MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs", | ||||
| }); | ||||
|  | ||||
| const gBase = Services.dirsvc.get("ProfD", Ci.nsIFile); | ||||
| gBase.append("CreateWindowsShortcut"); | ||||
| createDirectory(gBase); | ||||
|  | ||||
| const gTmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile); | ||||
|  | ||||
| const gDirectoryServiceProvider = { | ||||
|   getFile(prop, persistent) { | ||||
|     persistent.value = false; | ||||
|  | ||||
|     // We only expect a narrow range of calls. | ||||
|     let folder = gBase.clone(); | ||||
|     switch (prop) { | ||||
|       case "Progs": | ||||
|         folder.append("Programs"); | ||||
|         break; | ||||
|       case "Desk": | ||||
|         folder.append("Desktop"); | ||||
|         break; | ||||
|       case "UpdRootD": | ||||
|         // We really want DataRoot, but UpdateSubdir is what we usually get. | ||||
|         folder.append("DataRoot"); | ||||
|         folder.append("UpdateDir"); | ||||
|         folder.append("UpdateSubdir"); | ||||
|         break; | ||||
|       case "ProfD": | ||||
|         // Used by test infrastructure. | ||||
|         folder = folder.parent; | ||||
|         break; | ||||
|       case "TmpD": | ||||
|         // Used by FileTestUtils. | ||||
|         folder = gTmpDir; | ||||
|         break; | ||||
|       default: | ||||
|         console.error(`Access to unexpected directory '${prop}'`); | ||||
|         return Cr.NS_ERROR_FAILURE; | ||||
|     } | ||||
|  | ||||
|     createDirectory(folder); | ||||
|     return folder; | ||||
|   }, | ||||
|   QueryInterface: ChromeUtils.generateQI([Ci.nsIDirectoryServiceProvider]), | ||||
| }; | ||||
|  | ||||
| add_setup(() => { | ||||
|   Services.dirsvc | ||||
|     .QueryInterface(Ci.nsIDirectoryService) | ||||
|     .registerProvider(gDirectoryServiceProvider); | ||||
| }); | ||||
|  | ||||
| registerCleanupFunction(() => { | ||||
|   gBase.remove(true); | ||||
|   Services.dirsvc | ||||
|     .QueryInterface(Ci.nsIDirectoryService) | ||||
|     .unregisterProvider(gDirectoryServiceProvider); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_CreateWindowsShortcut() { | ||||
|   const DEST = "browser_createWindowsShortcut_TestFile.lnk"; | ||||
|  | ||||
|   const file = FileTestUtils.getTempFile("program.exe"); | ||||
|   const iconPath = FileTestUtils.getTempFile("program.ico"); | ||||
|  | ||||
|   let shortcut; | ||||
|  | ||||
|   const defaults = { | ||||
|     shellService: Cc["@mozilla.org/toolkit/shell-service;1"].getService(), | ||||
|     targetFile: file, | ||||
|     iconFile: iconPath, | ||||
|     description: "made by browser_createWindowsShortcut.js", | ||||
|     aumid: "TESTTEST", | ||||
|   }; | ||||
|  | ||||
|   shortcut = Services.dirsvc.get("Progs", Ci.nsIFile); | ||||
|   shortcut.append(DEST); | ||||
|   await testShortcut({ | ||||
|     shortcutFile: shortcut, | ||||
|     relativePath: DEST, | ||||
|     specialFolder: "Programs", | ||||
|     logHeader: "STARTMENU", | ||||
|     ...defaults, | ||||
|   }); | ||||
|  | ||||
|   let subdir = Services.dirsvc.get("Progs", Ci.nsIFile); | ||||
|   subdir.append("Shortcut Test"); | ||||
|   tryRemove(subdir); | ||||
|  | ||||
|   shortcut = subdir.clone(); | ||||
|   shortcut.append(DEST); | ||||
|   await testShortcut({ | ||||
|     shortcutFile: shortcut, | ||||
|     relativePath: "Shortcut Test\\" + DEST, | ||||
|     specialFolder: "Programs", | ||||
|     logHeader: "STARTMENU", | ||||
|     ...defaults, | ||||
|   }); | ||||
|   tryRemove(subdir); | ||||
|  | ||||
|   shortcut = Services.dirsvc.get("Desk", Ci.nsIFile); | ||||
|   shortcut.append(DEST); | ||||
|   await testShortcut({ | ||||
|     shortcutFile: shortcut, | ||||
|     relativePath: DEST, | ||||
|     specialFolder: "Desktop", | ||||
|     logHeader: "DESKTOP", | ||||
|     ...defaults, | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| async function testShortcut({ | ||||
|   shortcutFile, | ||||
|   relativePath, | ||||
|   specialFolder, | ||||
|   logHeader, | ||||
|  | ||||
|   // Generally provided by the defaults. | ||||
|   shellService, | ||||
|   targetFile, | ||||
|   iconFile, | ||||
|   description, | ||||
|   aumid, | ||||
| }) { | ||||
|   // If it already exists, remove it. | ||||
|   tryRemove(shortcutFile); | ||||
|  | ||||
|   await shellService.createShortcut( | ||||
|     targetFile, | ||||
|     [], | ||||
|     description, | ||||
|     iconFile, | ||||
|     0, | ||||
|     aumid, | ||||
|     specialFolder, | ||||
|     relativePath | ||||
|   ); | ||||
|   ok( | ||||
|     shortcutFile.exists(), | ||||
|     `${specialFolder}\\${relativePath}: Shortcut should exist` | ||||
|   ); | ||||
|   ok( | ||||
|     queryShortcutLog(relativePath, logHeader), | ||||
|     `${specialFolder}\\${relativePath}: Shortcut log entry was added` | ||||
|   ); | ||||
|   await shellService.deleteShortcut(specialFolder, relativePath); | ||||
|   ok( | ||||
|     !shortcutFile.exists(), | ||||
|     `${specialFolder}\\${relativePath}: Shortcut does not exist after deleting` | ||||
|   ); | ||||
|   ok( | ||||
|     !queryShortcutLog(relativePath, logHeader), | ||||
|     `${specialFolder}\\${relativePath}: Shortcut log entry was removed` | ||||
|   ); | ||||
| } | ||||
|  | ||||
| function queryShortcutLog(aShortcutName, aSection) { | ||||
|   const parserFactory = Cc[ | ||||
|     "@mozilla.org/xpcom/ini-parser-factory;1" | ||||
|   ].createInstance(Ci.nsIINIParserFactory); | ||||
|  | ||||
|   const dir = Services.dirsvc.get("UpdRootD", Ci.nsIFile).parent.parent; | ||||
|   const enumerator = dir.directoryEntries; | ||||
|  | ||||
|   for (const file of enumerator) { | ||||
|     // We don't know the user's SID from JS-land, so just look at all of them. | ||||
|     if (!file.path.match(/[^_]+_S[^_]*_shortcuts.ini/)) { | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     const parser = parserFactory.createINIParser(file); | ||||
|     parser.QueryInterface(Ci.nsIINIParser); | ||||
|     parser.QueryInterface(Ci.nsIINIParserWriter); | ||||
|  | ||||
|     for (let i = 0; ; i++) { | ||||
|       try { | ||||
|         let string = parser.getString(aSection, `Shortcut${i}`); | ||||
|         if (string == aShortcutName) { | ||||
|           enumerator.close(); | ||||
|           return true; | ||||
|         } | ||||
|       } catch (e) { | ||||
|         // The key didn't exist, stop here. | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   enumerator.close(); | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| function createDirectory(aFolder) { | ||||
|   try { | ||||
|     aFolder.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); | ||||
|   } catch (e) { | ||||
|     if (e.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) { | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| function tryRemove(file) { | ||||
|   try { | ||||
|     file.remove(false); | ||||
|     return true; | ||||
|   } catch (e) { | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/zen/tests/mochitests/shell/browser_doesAppNeedPin.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/zen/tests/mochitests/shell/browser_doesAppNeedPin.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| ChromeUtils.defineESModuleGetters(this, { | ||||
|   NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", | ||||
|   NimbusTestUtils: "resource://testing-common/NimbusTestUtils.sys.mjs", | ||||
| }); | ||||
|  | ||||
| let defaultValue; | ||||
| add_task(async function default_need() { | ||||
|   defaultValue = await ShellService.doesAppNeedPin(); | ||||
|  | ||||
|   Assert.notStrictEqual( | ||||
|     defaultValue, | ||||
|     undefined, | ||||
|     "Got a default app need pin value" | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function remote_disable() { | ||||
|   if (defaultValue === false) { | ||||
|     info("Default pin already false, so nothing to test"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let doCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { disablePin: true, enabled: true }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   Assert.equal( | ||||
|     await ShellService.doesAppNeedPin(), | ||||
|     false, | ||||
|     "Pinning disabled via nimbus" | ||||
|   ); | ||||
|  | ||||
|   await doCleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function restore_default() { | ||||
|   if (defaultValue === undefined) { | ||||
|     info("No default pin value set, so nothing to test"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   Assert.equal( | ||||
|     await ShellService.doesAppNeedPin(), | ||||
|     defaultValue, | ||||
|     "Pinning restored to original" | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,74 @@ | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function () { | ||||
|   // Test all four basic variations of the "screenshot" argument | ||||
|   // when a file path is specified. | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       screenshotPath, | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       `-screenshot=${screenshotPath}`, | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "--screenshot", | ||||
|       screenshotPath, | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       `--screenshot=${screenshotPath}`, | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
|  | ||||
|   // Test when the requested URL redirects | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless_redirect.html", | ||||
|       "-screenshot", | ||||
|       screenshotPath, | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
|  | ||||
|   // Test with additional command options | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       screenshotPath, | ||||
|       "-attach-console", | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-attach-console", | ||||
|       "-screenshot", | ||||
|       screenshotPath, | ||||
|       "-headless", | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,48 @@ | ||||
| "use strict"; | ||||
| add_task(async function () { | ||||
|   const cwdScreenshotPath = PathUtils.join( | ||||
|     Services.dirsvc.get("CurWorkD", Ci.nsIFile).path, | ||||
|     "screenshot.png" | ||||
|   ); | ||||
|  | ||||
|   // Test variations of the "screenshot" argument when a file path | ||||
|   // isn't specified. | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-screenshot", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "--screenshot", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "--screenshot", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|  | ||||
|   // Test with additional command options | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "--screenshot", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-attach-console", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,59 @@ | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function () { | ||||
|   const cwdScreenshotPath = PathUtils.join( | ||||
|     Services.dirsvc.get("CurWorkD", Ci.nsIFile).path, | ||||
|     "screenshot.png" | ||||
|   ); | ||||
|  | ||||
|   // Test invalid URL arguments (either no argument or too many arguments). | ||||
|   await testFileCreationNegative(["-screenshot"], cwdScreenshotPath); | ||||
|   await testFileCreationNegative( | ||||
|     [ | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "http://mochi.test:8888/headless.html", | ||||
|       "-screenshot", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|  | ||||
|   // Test all four basic variations of the "window-size" argument. | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       "-window-size", | ||||
|       "800", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       "-window-size=800", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       "--window-size", | ||||
|       "800", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       "--window-size=800", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,31 @@ | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function () { | ||||
|   const cwdScreenshotPath = PathUtils.join( | ||||
|     Services.dirsvc.get("CurWorkD", Ci.nsIFile).path, | ||||
|     "screenshot.png" | ||||
|   ); | ||||
|   // Test other variations of the "window-size" argument. | ||||
|   await testWindowSizePositive(800, 600); | ||||
|   await testWindowSizePositive(1234); | ||||
|   await testFileCreationNegative( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       "-window-size", | ||||
|       "hello", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
|   await testFileCreationNegative( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|       "-screenshot", | ||||
|       "-window-size", | ||||
|       "800,", | ||||
|     ], | ||||
|     cwdScreenshotPath | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,9 @@ | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function () { | ||||
|   // Test cross origin iframes work. | ||||
|   await testGreen( | ||||
|     "http://mochi.test:8888/browser/browser/components/shell/test/headless_cross_origin.html", | ||||
|     screenshotPath | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,14 @@ | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function () { | ||||
|   // Test when the requested URL redirects | ||||
|   await testFileCreationPositive( | ||||
|     [ | ||||
|       "-url", | ||||
|       "http://mochi.test:8888/browser/browser/components/shell/test/headless_redirect.html", | ||||
|       "-screenshot", | ||||
|       screenshotPath, | ||||
|     ], | ||||
|     screenshotPath | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										27
									
								
								src/zen/tests/mochitests/shell/browser_processAUMID.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/zen/tests/mochitests/shell/browser_processAUMID.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * https://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /** | ||||
|  * Bug 1950734 tracks how calling PinCurrentAppToTaskbarWin11 | ||||
|  * on MSIX may cause the process AUMID to be unnecessarily changed. | ||||
|  * This test verifies that the behaviour will no longer happen | ||||
|  */ | ||||
|  | ||||
| ChromeUtils.defineESModuleGetters(this, { | ||||
|   ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs", | ||||
| }); | ||||
|  | ||||
| add_task(async function test_processAUMID() { | ||||
|   let processAUMID = ShellService.checkCurrentProcessAUMIDForTesting(); | ||||
|  | ||||
|   // This function will trigger the relevant code paths that | ||||
|   // incorrectly changes the process AUMID on MSIX, prior to | ||||
|   // Bug 1950734 being fixed | ||||
|   await ShellService.checkPinCurrentAppToTaskbarAsync(false); | ||||
|  | ||||
|   is( | ||||
|     processAUMID, | ||||
|     ShellService.checkCurrentProcessAUMIDForTesting(), | ||||
|     "The process AUMID should not be changed" | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										239
									
								
								src/zen/tests/mochitests/shell/browser_setDefaultBrowser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								src/zen/tests/mochitests/shell/browser_setDefaultBrowser.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| ChromeUtils.defineESModuleGetters(this, { | ||||
|   ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs", | ||||
|   ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs", | ||||
|   NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", | ||||
|   NimbusTestUtils: "resource://testing-common/NimbusTestUtils.sys.mjs", | ||||
|   sinon: "resource://testing-common/Sinon.sys.mjs", | ||||
| }); | ||||
|  | ||||
| const setDefaultBrowserUserChoiceStub = async () => { | ||||
|   throw Components.Exception("", Cr.NS_ERROR_WDBA_NO_PROGID); | ||||
| }; | ||||
|  | ||||
| const defaultAgentStub = sinon | ||||
|   .stub(ShellService, "defaultAgent") | ||||
|   .value({ setDefaultBrowserUserChoiceAsync: setDefaultBrowserUserChoiceStub }); | ||||
|  | ||||
| const _userChoiceImpossibleTelemetryResultStub = sinon | ||||
|   .stub(ShellService, "_userChoiceImpossibleTelemetryResult") | ||||
|   .callsFake(() => null); | ||||
|  | ||||
| const userChoiceStub = sinon | ||||
|   .stub(ShellService, "setAsDefaultUserChoice") | ||||
|   .resolves(); | ||||
| const setDefaultStub = sinon.stub(); | ||||
| const shellStub = sinon | ||||
|   .stub(ShellService, "shellService") | ||||
|   .value({ setDefaultBrowser: setDefaultStub }); | ||||
|  | ||||
| const sendTriggerStub = sinon.stub(ASRouter, "sendTriggerMessage"); | ||||
|  | ||||
| registerCleanupFunction(() => { | ||||
|   sinon.restore(); | ||||
| }); | ||||
|  | ||||
| let defaultUserChoice; | ||||
| add_task(async function need_user_choice() { | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|   defaultUserChoice = userChoiceStub.called; | ||||
|  | ||||
|   Assert.notStrictEqual( | ||||
|     defaultUserChoice, | ||||
|     undefined, | ||||
|     "Decided which default browser method to use" | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     setDefaultStub.notCalled, | ||||
|     defaultUserChoice, | ||||
|     "Only one default behavior was used" | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function remote_disable() { | ||||
|   if (defaultUserChoice === false) { | ||||
|     info("Default behavior already not user choice, so nothing to test"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   userChoiceStub.resetHistory(); | ||||
|   setDefaultStub.resetHistory(); | ||||
|   let doCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { | ||||
|         setDefaultBrowserUserChoice: false, | ||||
|         enabled: true, | ||||
|       }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|   Assert.ok( | ||||
|     userChoiceStub.notCalled, | ||||
|     "Set default with user choice disabled via nimbus" | ||||
|   ); | ||||
|   Assert.ok(setDefaultStub.called, "Used plain set default instead"); | ||||
|  | ||||
|   await doCleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function restore_default() { | ||||
|   if (defaultUserChoice === undefined) { | ||||
|     info("No default user choice behavior set, so nothing to test"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   userChoiceStub.resetHistory(); | ||||
|   setDefaultStub.resetHistory(); | ||||
|  | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|   Assert.equal( | ||||
|     userChoiceStub.called, | ||||
|     defaultUserChoice, | ||||
|     "Set default with user choice restored to original" | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     setDefaultStub.notCalled, | ||||
|     defaultUserChoice, | ||||
|     "Plain set default behavior restored to original" | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| add_task(async function ensure_fallback() { | ||||
|   if (AppConstants.platform != "win") { | ||||
|     info("Nothing to test on non-Windows"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let userChoicePromise = Promise.resolve(); | ||||
|   userChoiceStub.callsFake(function (...args) { | ||||
|     return (userChoicePromise = userChoiceStub.wrappedMethod.apply(this, args)); | ||||
|   }); | ||||
|   userChoiceStub.resetHistory(); | ||||
|   setDefaultStub.resetHistory(); | ||||
|   let doCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { | ||||
|         setDefaultBrowserUserChoice: true, | ||||
|         setDefaultPDFHandler: false, | ||||
|         enabled: true, | ||||
|       }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|   Assert.ok(userChoiceStub.called, "Set default with user choice called"); | ||||
|  | ||||
|   let message = ""; | ||||
|   await userChoicePromise.catch(err => (message = err.message || "")); | ||||
|  | ||||
|   Assert.ok( | ||||
|     message.includes("ErrExeProgID"), | ||||
|     "Set default with user choice threw an expected error" | ||||
|   ); | ||||
|   Assert.ok(setDefaultStub.called, "Fallbacked to plain set default"); | ||||
|  | ||||
|   await doCleanup(); | ||||
| }); | ||||
|  | ||||
| async function setUpNotificationTests(guidanceEnabled, oneClick) { | ||||
|   sinon.reset(); | ||||
|   const experimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { | ||||
|         setDefaultGuidanceNotifications: guidanceEnabled, | ||||
|         setDefaultBrowserUserChoice: oneClick, | ||||
|         setDefaultBrowserUserChoiceRegRename: oneClick, | ||||
|         enabled: true, | ||||
|       }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   const doCleanup = async () => { | ||||
|     await experimentCleanup(); | ||||
|     sinon.reset(); | ||||
|   }; | ||||
|  | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|   return doCleanup; | ||||
| } | ||||
|  | ||||
| add_task( | ||||
|   async function show_notification_when_set_to_default_guidance_enabled_and_one_click_disabled() { | ||||
|     if (!AppConstants.isPlatformAndVersionAtLeast("win", 10)) { | ||||
|       info("Nothing to test on non-Windows or older Windows versions"); | ||||
|       return; | ||||
|     } | ||||
|     const doCleanup = await setUpNotificationTests( | ||||
|       true, // guidance enabled | ||||
|       false // one-click disabled | ||||
|     ); | ||||
|  | ||||
|     Assert.ok(setDefaultStub.called, "Fallback method used to set default"); | ||||
|  | ||||
|     Assert.equal( | ||||
|       sendTriggerStub.firstCall.args[0].id, | ||||
|       "deeplinkedToWindowsSettingsUI", | ||||
|       `Set to default guidance message trigger was sent` | ||||
|     ); | ||||
|  | ||||
|     await doCleanup(); | ||||
|   } | ||||
| ); | ||||
|  | ||||
| add_task( | ||||
|   async function do_not_show_notification_when_set_to_default_guidance_disabled_and_one_click_enabled() { | ||||
|     if (!AppConstants.isPlatformAndVersionAtLeast("win", 10)) { | ||||
|       info("Nothing to test on non-Windows or older Windows versions"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const doCleanup = await setUpNotificationTests( | ||||
|       false, // guidance disabled | ||||
|       true // one-click enabled | ||||
|     ); | ||||
|  | ||||
|     Assert.ok(setDefaultStub.notCalled, "Fallback method not called"); | ||||
|  | ||||
|     Assert.equal( | ||||
|       sendTriggerStub.callCount, | ||||
|       0, | ||||
|       `Set to default guidance message trigger was not sent` | ||||
|     ); | ||||
|  | ||||
|     await doCleanup(); | ||||
|   } | ||||
| ); | ||||
|  | ||||
| add_task( | ||||
|   async function do_not_show_notification_when_set_to_default_guidance_enabled_and_one_click_enabled() { | ||||
|     if (!AppConstants.isPlatformAndVersionAtLeast("win", 10)) { | ||||
|       info("Nothing to test on non-Windows or older Windows versions"); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const doCleanup = await setUpNotificationTests( | ||||
|       true, // guidance enabled | ||||
|       true // one-click enabled | ||||
|     ); | ||||
|  | ||||
|     Assert.ok(setDefaultStub.notCalled, "Fallback method not called"); | ||||
|     Assert.equal( | ||||
|       sendTriggerStub.callCount, | ||||
|       0, | ||||
|       `Set to default guidance message trigger was not sent` | ||||
|     ); | ||||
|     await doCleanup(); | ||||
|   } | ||||
| ); | ||||
							
								
								
									
										279
									
								
								src/zen/tests/mochitests/shell/browser_setDefaultPDFHandler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								src/zen/tests/mochitests/shell/browser_setDefaultPDFHandler.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| ChromeUtils.defineESModuleGetters(this, { | ||||
|   ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs", | ||||
|   NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", | ||||
|   NimbusTestUtils: "resource://testing-common/NimbusTestUtils.sys.mjs", | ||||
|   sinon: "resource://testing-common/Sinon.sys.mjs", | ||||
| }); | ||||
|  | ||||
| const setDefaultBrowserUserChoiceStub = sinon.stub(); | ||||
| const setDefaultExtensionHandlersUserChoiceStub = sinon | ||||
|   .stub() | ||||
|   .callsFake(() => Promise.resolve()); | ||||
|  | ||||
| const defaultAgentStub = sinon.stub(ShellService, "defaultAgent").value({ | ||||
|   setDefaultBrowserUserChoiceAsync: setDefaultBrowserUserChoiceStub, | ||||
|   setDefaultExtensionHandlersUserChoice: | ||||
|     setDefaultExtensionHandlersUserChoiceStub, | ||||
| }); | ||||
|  | ||||
| XPCOMUtils.defineLazyServiceGetter( | ||||
|   this, | ||||
|   "XreDirProvider", | ||||
|   "@mozilla.org/xre/directory-provider;1", | ||||
|   "nsIXREDirProvider" | ||||
| ); | ||||
|  | ||||
| const _userChoiceImpossibleTelemetryResultStub = sinon | ||||
|   .stub(ShellService, "_userChoiceImpossibleTelemetryResult") | ||||
|   .callsFake(() => null); | ||||
|  | ||||
| // Ensure we don't fall back to a real implementation. | ||||
| const setDefaultStub = sinon.stub(); | ||||
| // We'll dynamically update this as needed during the tests. | ||||
| const queryCurrentDefaultHandlerForStub = sinon.stub(); | ||||
| const shellStub = sinon.stub(ShellService, "shellService").value({ | ||||
|   setDefaultBrowser: setDefaultStub, | ||||
|   queryCurrentDefaultHandlerFor: queryCurrentDefaultHandlerForStub, | ||||
| }); | ||||
|  | ||||
| registerCleanupFunction(() => { | ||||
|   defaultAgentStub.restore(); | ||||
|   _userChoiceImpossibleTelemetryResultStub.restore(); | ||||
|   shellStub.restore(); | ||||
| }); | ||||
|  | ||||
| add_task(async function ready() { | ||||
|   await ExperimentAPI.ready(); | ||||
| }); | ||||
|  | ||||
| // Everything here is Windows. | ||||
| Assert.equal(AppConstants.platform, "win", "Platform is Windows"); | ||||
|  | ||||
| add_task(async function remoteEnableWithPDF() { | ||||
|   let doCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { | ||||
|         setDefaultBrowserUserChoice: true, | ||||
|         setDefaultPDFHandlerOnlyReplaceBrowsers: false, | ||||
|         setDefaultPDFHandler: true, | ||||
|         enabled: true, | ||||
|       }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), | ||||
|     true | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), | ||||
|     true | ||||
|   ); | ||||
|  | ||||
|   setDefaultBrowserUserChoiceStub.resetHistory(); | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|   const aumi = XreDirProvider.getInstallHash(); | ||||
|   Assert.ok(setDefaultBrowserUserChoiceStub.called); | ||||
|   Assert.deepEqual(setDefaultBrowserUserChoiceStub.firstCall.args, [ | ||||
|     aumi, | ||||
|     [".pdf", "FirefoxPDF"], | ||||
|   ]); | ||||
|  | ||||
|   await doCleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function remoteEnableWithPDF_testOnlyReplaceBrowsers() { | ||||
|   let doCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { | ||||
|         setDefaultBrowserUserChoice: true, | ||||
|         setDefaultPDFHandlerOnlyReplaceBrowsers: true, | ||||
|         setDefaultPDFHandler: true, | ||||
|         enabled: true, | ||||
|       }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), | ||||
|     true | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), | ||||
|     true | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable( | ||||
|       "setDefaultPDFHandlerOnlyReplaceBrowsers" | ||||
|     ), | ||||
|     true | ||||
|   ); | ||||
|  | ||||
|   const aumi = XreDirProvider.getInstallHash(); | ||||
|  | ||||
|   // We'll take the default from a missing association or a known browser. | ||||
|   for (let progId of ["", "MSEdgePDF"]) { | ||||
|     queryCurrentDefaultHandlerForStub.callsFake(() => progId); | ||||
|  | ||||
|     setDefaultBrowserUserChoiceStub.resetHistory(); | ||||
|     await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|     Assert.ok(setDefaultBrowserUserChoiceStub.called); | ||||
|     Assert.deepEqual( | ||||
|       setDefaultBrowserUserChoiceStub.firstCall.args, | ||||
|       [aumi, [".pdf", "FirefoxPDF"]], | ||||
|       `Will take default from missing association or known browser with ProgID '${progId}'` | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // But not from a non-browser. | ||||
|   queryCurrentDefaultHandlerForStub.callsFake(() => "Acrobat.Document.DC"); | ||||
|  | ||||
|   setDefaultBrowserUserChoiceStub.resetHistory(); | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|   Assert.ok(setDefaultBrowserUserChoiceStub.called); | ||||
|   Assert.deepEqual( | ||||
|     setDefaultBrowserUserChoiceStub.firstCall.args, | ||||
|     [aumi, []], | ||||
|     `Will not take default from non-browser` | ||||
|   ); | ||||
|  | ||||
|   await doCleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function remoteEnableWithoutPDF() { | ||||
|   let doCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { | ||||
|         setDefaultBrowserUserChoice: true, | ||||
|         setDefaultPDFHandler: false, | ||||
|         enabled: true, | ||||
|       }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), | ||||
|     true | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), | ||||
|     false | ||||
|   ); | ||||
|  | ||||
|   setDefaultBrowserUserChoiceStub.resetHistory(); | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|   const aumi = XreDirProvider.getInstallHash(); | ||||
|   Assert.ok(setDefaultBrowserUserChoiceStub.called); | ||||
|   Assert.deepEqual(setDefaultBrowserUserChoiceStub.firstCall.args, [aumi, []]); | ||||
|  | ||||
|   await doCleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function remoteDisable() { | ||||
|   let doCleanup = await NimbusTestUtils.enrollWithFeatureConfig( | ||||
|     { | ||||
|       featureId: NimbusFeatures.shellService.featureId, | ||||
|       value: { | ||||
|         setDefaultBrowserUserChoice: false, | ||||
|         setDefaultPDFHandler: true, | ||||
|         enabled: false, | ||||
|       }, | ||||
|     }, | ||||
|     { isRollout: true } | ||||
|   ); | ||||
|  | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultBrowserUserChoice"), | ||||
|     false | ||||
|   ); | ||||
|   Assert.equal( | ||||
|     NimbusFeatures.shellService.getVariable("setDefaultPDFHandler"), | ||||
|     true | ||||
|   ); | ||||
|  | ||||
|   setDefaultBrowserUserChoiceStub.resetHistory(); | ||||
|   await ShellService.setDefaultBrowser(); | ||||
|  | ||||
|   Assert.ok(setDefaultBrowserUserChoiceStub.notCalled); | ||||
|   Assert.ok(setDefaultStub.called); | ||||
|  | ||||
|   await doCleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_setAsDefaultPDFHandler_knownBrowser() { | ||||
|   const sandbox = sinon.createSandbox(); | ||||
|  | ||||
|   const aumi = XreDirProvider.getInstallHash(); | ||||
|   const expectedArguments = [aumi, [".pdf", "FirefoxPDF"]]; | ||||
|  | ||||
|   try { | ||||
|     const pdfHandlerResult = { registered: true, knownBrowser: true }; | ||||
|     sandbox | ||||
|       .stub(ShellService, "getDefaultPDFHandler") | ||||
|       .returns(pdfHandlerResult); | ||||
|  | ||||
|     info("Testing setAsDefaultPDFHandler(true) when knownBrowser = true"); | ||||
|     ShellService.setAsDefaultPDFHandler(true); | ||||
|     Assert.ok( | ||||
|       setDefaultExtensionHandlersUserChoiceStub.called, | ||||
|       "Called default browser agent" | ||||
|     ); | ||||
|     Assert.deepEqual( | ||||
|       setDefaultExtensionHandlersUserChoiceStub.firstCall.args, | ||||
|       expectedArguments, | ||||
|       "Called default browser agent with expected arguments" | ||||
|     ); | ||||
|     setDefaultExtensionHandlersUserChoiceStub.resetHistory(); | ||||
|  | ||||
|     info("Testing setAsDefaultPDFHandler(false) when knownBrowser = true"); | ||||
|     ShellService.setAsDefaultPDFHandler(false); | ||||
|     Assert.ok( | ||||
|       setDefaultExtensionHandlersUserChoiceStub.called, | ||||
|       "Called default browser agent" | ||||
|     ); | ||||
|     Assert.deepEqual( | ||||
|       setDefaultExtensionHandlersUserChoiceStub.firstCall.args, | ||||
|       expectedArguments, | ||||
|       "Called default browser agent with expected arguments" | ||||
|     ); | ||||
|     setDefaultExtensionHandlersUserChoiceStub.resetHistory(); | ||||
|  | ||||
|     pdfHandlerResult.knownBrowser = false; | ||||
|  | ||||
|     info("Testing setAsDefaultPDFHandler(true) when knownBrowser = false"); | ||||
|     ShellService.setAsDefaultPDFHandler(true); | ||||
|     Assert.ok( | ||||
|       setDefaultExtensionHandlersUserChoiceStub.notCalled, | ||||
|       "Did not call default browser agent" | ||||
|     ); | ||||
|     setDefaultExtensionHandlersUserChoiceStub.resetHistory(); | ||||
|  | ||||
|     info("Testing setAsDefaultPDFHandler(false) when knownBrowser = false"); | ||||
|     ShellService.setAsDefaultPDFHandler(false); | ||||
|     Assert.ok( | ||||
|       setDefaultExtensionHandlersUserChoiceStub.called, | ||||
|       "Called default browser agent" | ||||
|     ); | ||||
|     Assert.deepEqual( | ||||
|       setDefaultExtensionHandlersUserChoiceStub.firstCall.args, | ||||
|       expectedArguments, | ||||
|       "Called default browser agent with expected arguments" | ||||
|     ); | ||||
|     setDefaultExtensionHandlersUserChoiceStub.resetHistory(); | ||||
|   } finally { | ||||
|     sandbox.restore(); | ||||
|   } | ||||
| }); | ||||
| @@ -0,0 +1,93 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /** | ||||
|  * Check whether the preview image for setDesktopBackground is rendered | ||||
|  * correctly, without stretching | ||||
|  */ | ||||
|  | ||||
| add_setup(async function () { | ||||
|   await SpecialPowers.pushPrefEnv({ | ||||
|     set: [["test.wait300msAfterTabSwitch", true]], | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| add_task(async function () { | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "about:logo", | ||||
|     }, | ||||
|     async () => { | ||||
|       const dialogLoad = BrowserTestUtils.domWindowOpened(null, async win => { | ||||
|         await BrowserTestUtils.waitForEvent(win, "load"); | ||||
|         Assert.equal( | ||||
|           win.document.documentElement.getAttribute("windowtype"), | ||||
|           "Shell:SetDesktopBackground", | ||||
|           "Opened correct window" | ||||
|         ); | ||||
|         return true; | ||||
|       }); | ||||
|  | ||||
|       const image = content.document.images[0]; | ||||
|       EventUtils.synthesizeMouseAtCenter(image, { type: "contextmenu" }); | ||||
|  | ||||
|       const menu = document.getElementById("contentAreaContextMenu"); | ||||
|       await BrowserTestUtils.waitForPopupEvent(menu, "shown"); | ||||
|       const menuClosed = BrowserTestUtils.waitForPopupEvent(menu, "hidden"); | ||||
|  | ||||
|       const menuItem = document.getElementById("context-setDesktopBackground"); | ||||
|       try { | ||||
|         menu.activateItem(menuItem); | ||||
|       } catch (ex) { | ||||
|         ok( | ||||
|           menuItem.hidden, | ||||
|           "should only fail to activate when menu item is hidden" | ||||
|         ); | ||||
|         ok( | ||||
|           !ShellService.canSetDesktopBackground, | ||||
|           "Should only hide when not able to set the desktop background" | ||||
|         ); | ||||
|         is( | ||||
|           AppConstants.platform, | ||||
|           "linux", | ||||
|           "Should always be able to set desktop background on non-linux platforms" | ||||
|         ); | ||||
|         todo(false, "Skipping test on this configuration"); | ||||
|  | ||||
|         menu.hidePopup(); | ||||
|         await menuClosed; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       await menuClosed; | ||||
|  | ||||
|       const win = await dialogLoad; | ||||
|  | ||||
|       /* setDesktopBackground.js does a setTimeout to wait for correct | ||||
|        dimensions. If we don't wait here we could read the preview dimensions | ||||
|        before they're changed to match the screen */ | ||||
|       await TestUtils.waitForTick(); | ||||
|  | ||||
|       const canvas = win.document.getElementById("screen"); | ||||
|       const screenRatio = screen.width / screen.height; | ||||
|       const previewRatio = canvas.clientWidth / canvas.clientHeight; | ||||
|  | ||||
|       info(`Screen dimensions are ${screen.width}x${screen.height}`); | ||||
|       info(`Screen's raw ratio is ${screenRatio}`); | ||||
|       info( | ||||
|         `Preview dimensions are ${canvas.clientWidth}x${canvas.clientHeight}` | ||||
|       ); | ||||
|       info(`Preview's raw ratio is ${previewRatio}`); | ||||
|  | ||||
|       Assert.ok( | ||||
|         previewRatio < screenRatio + 0.01 && previewRatio > screenRatio - 0.01, | ||||
|         "Preview's aspect ratio is within ±.01 of screen's" | ||||
|       ); | ||||
|  | ||||
|       win.close(); | ||||
|  | ||||
|       await menuClosed; | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,40 @@ | ||||
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ | ||||
| /* 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 http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| #include "gtest/gtest.h" | ||||
|  | ||||
| #include "Windows11LimitedAccessFeatures.h" | ||||
| #include "WinUtils.h" | ||||
|  | ||||
| TEST(LimitedAccessFeature, VerifyGeneratedInfo) | ||||
| { | ||||
|   // If running on MSIX we have no guarantee that the | ||||
|   // generated LAF info will match the known values. | ||||
|   if (mozilla::widget::WinUtils::HasPackageIdentity()) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   LimitedAccessFeatureInfo knownLafInfo = { | ||||
|       // Win11LimitedAccessFeatureType::Taskbar | ||||
|       "Win11LimitedAccessFeatureType::Taskbar"_ns,      // debugName | ||||
|       u"com.microsoft.windows.taskbar.pin"_ns,          // feature | ||||
|       u"kRFiWpEK5uS6PMJZKmR7MQ=="_ns,                   // token | ||||
|       u"pcsmm0jrprpb2 has registered their use of "_ns  // attestation | ||||
|       u"com.microsoft.windows.taskbar.pin with Microsoft and agrees to the "_ns | ||||
|       u"terms "_ns | ||||
|       u"of use."_ns}; | ||||
|  | ||||
|   auto generatedLafInfoResult = GenerateLimitedAccessFeatureInfo( | ||||
|       "Win11LimitedAccessFeatureType::Taskbar"_ns, | ||||
|       u"com.microsoft.windows.taskbar.pin"_ns); | ||||
|   ASSERT_TRUE(generatedLafInfoResult.isOk()); | ||||
|   LimitedAccessFeatureInfo generatedLafInfo = generatedLafInfoResult.unwrap(); | ||||
|  | ||||
|   // Check for equality between generated values and known good values | ||||
|   ASSERT_TRUE(knownLafInfo.debugName.Equals(generatedLafInfo.debugName)); | ||||
|   ASSERT_TRUE(knownLafInfo.feature.Equals(generatedLafInfo.feature)); | ||||
|   ASSERT_TRUE(knownLafInfo.token.Equals(generatedLafInfo.token)); | ||||
|   ASSERT_TRUE(knownLafInfo.attestation.Equals(generatedLafInfo.attestation)); | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/zen/tests/mochitests/shell/gtest/ShellLinkTests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/zen/tests/mochitests/shell/gtest/ShellLinkTests.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ | ||||
| /* 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 http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| #include "gtest/gtest.h" | ||||
|  | ||||
| #include "nsDirectoryServiceDefs.h" | ||||
| #include "nsDirectoryServiceUtils.h" | ||||
| #include "nsWindowsShellServiceInternal.h" | ||||
| #include "nsXULAppAPI.h" | ||||
|  | ||||
| TEST(ShellLink, NarrowCharacterArguments) | ||||
| { | ||||
|   nsCOMPtr<nsIFile> exe; | ||||
|   nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(exe)); | ||||
|   ASSERT_TRUE(NS_SUCCEEDED(rv)); | ||||
|  | ||||
|   RefPtr<IShellLinkW> link; | ||||
|   rv = CreateShellLinkObject(exe, {u"test"_ns}, u"test"_ns, exe, 0, u"aumid"_ns, | ||||
|                              getter_AddRefs(link)); | ||||
|   ASSERT_TRUE(NS_SUCCEEDED(rv)); | ||||
|   ASSERT_TRUE(link != nullptr); | ||||
|  | ||||
|   std::wstring testArgs = L"\"test\" "; | ||||
|  | ||||
|   wchar_t resultArgs[sizeof(testArgs)]; | ||||
|   HRESULT hr = link->GetArguments(resultArgs, sizeof(resultArgs)); | ||||
|   ASSERT_TRUE(SUCCEEDED(hr)); | ||||
|  | ||||
|   ASSERT_TRUE(testArgs == resultArgs); | ||||
| } | ||||
|  | ||||
| TEST(ShellLink, WideCharacterArguments) | ||||
| { | ||||
|   nsCOMPtr<nsIFile> exe; | ||||
|   nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(exe)); | ||||
|   ASSERT_TRUE(NS_SUCCEEDED(rv)); | ||||
|  | ||||
|   RefPtr<IShellLinkW> link; | ||||
|   rv = CreateShellLinkObject(exe, {u"Test\\テスト用アカウント\\Test"_ns}, | ||||
|                              u"test"_ns, exe, 0, u"aumid"_ns, | ||||
|                              getter_AddRefs(link)); | ||||
|   ASSERT_TRUE(NS_SUCCEEDED(rv)); | ||||
|   ASSERT_TRUE(link != nullptr); | ||||
|  | ||||
|   std::wstring testArgs = L"\"Test\\テスト用アカウント\\Test\" "; | ||||
|  | ||||
|   wchar_t resultArgs[sizeof(testArgs)]; | ||||
|   HRESULT hr = link->GetArguments(resultArgs, sizeof(resultArgs)); | ||||
|   ASSERT_TRUE(SUCCEEDED(hr)); | ||||
|  | ||||
|   ASSERT_TRUE(testArgs == resultArgs); | ||||
| } | ||||
							
								
								
									
										15
									
								
								src/zen/tests/mochitests/shell/gtest/moz.build
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/zen/tests/mochitests/shell/gtest/moz.build
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- | ||||
| # vim: set filetype=python: | ||||
| # 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 http://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": | ||||
|     LOCAL_INCLUDES += ["/browser/components/shell"] | ||||
|  | ||||
|     UNIFIED_SOURCES += [ | ||||
|         "LimitedAccessFeatureTests.cpp", | ||||
|         "ShellLinkTests.cpp", | ||||
|     ] | ||||
|  | ||||
| FINAL_LIBRARY = "xul-gtest" | ||||
							
								
								
									
										159
									
								
								src/zen/tests/mochitests/shell/head.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/zen/tests/mochitests/shell/head.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const { Subprocess } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/Subprocess.sys.mjs" | ||||
| ); | ||||
|  | ||||
| const TEMP_DIR = Services.dirsvc.get("TmpD", Ci.nsIFile).path; | ||||
|  | ||||
| const screenshotPath = PathUtils.join(TEMP_DIR, "headless_test_screenshot.png"); | ||||
|  | ||||
| async function runFirefox(args) { | ||||
|   const XRE_EXECUTABLE_FILE = "XREExeF"; | ||||
|   const firefoxExe = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).path; | ||||
|   const NS_APP_PREFS_50_FILE = "PrefF"; | ||||
|   const mochiPrefsFile = Services.dirsvc.get(NS_APP_PREFS_50_FILE, Ci.nsIFile); | ||||
|   const mochiPrefsPath = mochiPrefsFile.path; | ||||
|   const mochiPrefsName = mochiPrefsFile.leafName; | ||||
|   const profilePath = PathUtils.join( | ||||
|     TEMP_DIR, | ||||
|     "headless_test_screenshot_profile" | ||||
|   ); | ||||
|   const prefsPath = PathUtils.join(profilePath, mochiPrefsName); | ||||
|   const firefoxArgs = ["-profile", profilePath]; | ||||
|  | ||||
|   await IOUtils.makeDirectory(profilePath); | ||||
|   await IOUtils.copy(mochiPrefsPath, prefsPath); | ||||
|   let proc = await Subprocess.call({ | ||||
|     command: firefoxExe, | ||||
|     arguments: firefoxArgs.concat(args), | ||||
|     // Disable leak detection to avoid intermittent failure bug 1331152. | ||||
|     environmentAppend: true, | ||||
|     environment: { | ||||
|       ASAN_OPTIONS: | ||||
|         "detect_leaks=0:quarantine_size=50331648:malloc_context_size=5", | ||||
|       // Don't enable Marionette. | ||||
|       MOZ_MARIONETTE: null, | ||||
|     }, | ||||
|   }); | ||||
|   let stdout; | ||||
|   while ((stdout = await proc.stdout.readString())) { | ||||
|     dump(`>>> ${stdout}\n`); | ||||
|   } | ||||
|   let { exitCode } = await proc.wait(); | ||||
|   is(exitCode, 0, "Firefox process should exit with code 0"); | ||||
|   await IOUtils.remove(profilePath, { recursive: true }); | ||||
| } | ||||
|  | ||||
| async function testFileCreationPositive(args, path) { | ||||
|   await runFirefox(args); | ||||
|  | ||||
|   let saved = IOUtils.exists(path); | ||||
|   ok(saved, "A screenshot should be saved as " + path); | ||||
|   if (!saved) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let info = await IOUtils.stat(path); | ||||
|   Assert.greater(info.size, 0, "Screenshot should not be an empty file"); | ||||
|   await IOUtils.remove(path); | ||||
| } | ||||
|  | ||||
| async function testFileCreationNegative(args, path) { | ||||
|   await runFirefox(args); | ||||
|  | ||||
|   let saved = await IOUtils.exists(path); | ||||
|   ok(!saved, "A screenshot should not be saved"); | ||||
|   await IOUtils.remove(path); | ||||
| } | ||||
|  | ||||
| async function testWindowSizePositive(width, height) { | ||||
|   let size = String(width); | ||||
|   if (height) { | ||||
|     size += "," + height; | ||||
|   } | ||||
|  | ||||
|   await runFirefox([ | ||||
|     "-url", | ||||
|     "http://mochi.test:8888/browser/browser/components/shell/test/headless.html", | ||||
|     "-screenshot", | ||||
|     screenshotPath, | ||||
|     "-window-size", | ||||
|     size, | ||||
|   ]); | ||||
|  | ||||
|   let saved = await IOUtils.exists(screenshotPath); | ||||
|   ok(saved, "A screenshot should be saved in the tmp directory"); | ||||
|   if (!saved) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let data = await IOUtils.read(screenshotPath); | ||||
|   await new Promise(resolve => { | ||||
|     let blob = new Blob([data], { type: "image/png" }); | ||||
|     let reader = new FileReader(); | ||||
|     reader.onloadend = function () { | ||||
|       let screenshot = new Image(); | ||||
|       screenshot.onload = function () { | ||||
|         is( | ||||
|           screenshot.width, | ||||
|           width, | ||||
|           "Screenshot should be " + width + " pixels wide" | ||||
|         ); | ||||
|         if (height) { | ||||
|           is( | ||||
|             screenshot.height, | ||||
|             height, | ||||
|             "Screenshot should be " + height + " pixels tall" | ||||
|           ); | ||||
|         } | ||||
|         resolve(); | ||||
|       }; | ||||
|       screenshot.src = reader.result; | ||||
|     }; | ||||
|     reader.readAsDataURL(blob); | ||||
|   }); | ||||
|   await IOUtils.remove(screenshotPath); | ||||
| } | ||||
|  | ||||
| async function testGreen(url, path) { | ||||
|   await runFirefox(["-url", url, `--screenshot=${path}`]); | ||||
|  | ||||
|   let saved = await IOUtils.exists(path); | ||||
|   ok(saved, "A screenshot should be saved in the tmp directory"); | ||||
|   if (!saved) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let data = await IOUtils.read(path); | ||||
|   let image = await new Promise(resolve => { | ||||
|     let blob = new Blob([data], { type: "image/png" }); | ||||
|     let reader = new FileReader(); | ||||
|     reader.onloadend = function () { | ||||
|       let screenshot = new Image(); | ||||
|       screenshot.onload = function () { | ||||
|         resolve(screenshot); | ||||
|       }; | ||||
|       screenshot.src = reader.result; | ||||
|     }; | ||||
|     reader.readAsDataURL(blob); | ||||
|   }); | ||||
|   let canvas = document.createElement("canvas"); | ||||
|   canvas.width = image.naturalWidth; | ||||
|   canvas.height = image.naturalHeight; | ||||
|   let ctx = canvas.getContext("2d"); | ||||
|   ctx.drawImage(image, 0, 0); | ||||
|   let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); | ||||
|   let rgba = imageData.data; | ||||
|  | ||||
|   let found = false; | ||||
|   for (let i = 0; i < rgba.length; i += 4) { | ||||
|     if (rgba[i] === 0 && rgba[i + 1] === 255 && rgba[i + 2] === 0) { | ||||
|       found = true; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   ok(found, "There should be a green pixel in the screenshot."); | ||||
|  | ||||
|   await IOUtils.remove(path); | ||||
| } | ||||
							
								
								
									
										6
									
								
								src/zen/tests/mochitests/shell/headless.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/zen/tests/mochitests/shell/headless.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <html> | ||||
| <head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head> | ||||
| <body style="background-color: rgb(0, 255, 0); color: rgb(0, 0, 255)"> | ||||
| Hi | ||||
| </body> | ||||
| </html> | ||||
| @@ -0,0 +1,7 @@ | ||||
| <html> | ||||
| <head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head> | ||||
| <body> | ||||
| <iframe width="300" height="200" src="http://example.com/browser/browser/components/shell/test/headless_iframe.html"></iframe> | ||||
| Hi | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										6
									
								
								src/zen/tests/mochitests/shell/headless_iframe.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/zen/tests/mochitests/shell/headless_iframe.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <html> | ||||
| <head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head> | ||||
| <body style="background-color: rgb(0, 255, 0);"> | ||||
| Hi | ||||
| </body> | ||||
| </html> | ||||
| @@ -0,0 +1,2 @@ | ||||
| HTTP 302 Moved Temporarily | ||||
| Location: headless.html | ||||
							
								
								
									
										168
									
								
								src/zen/tests/mochitests/shell/mac_desktop_image.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										168
									
								
								src/zen/tests/mochitests/shell/mac_desktop_image.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| #!/usr/bin/python | ||||
| # 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 http://mozilla.org/MPL/2.0/. */ | ||||
|  | ||||
| """ | ||||
| mac_desktop_image.py | ||||
|  | ||||
| Mac-specific utility to get/set the desktop background image or check that | ||||
| the current background image path matches a provided path. | ||||
|  | ||||
| Depends on Objective-C python binding imports which are in the python import | ||||
| paths by default when using macOS's /usr/bin/python. | ||||
|  | ||||
| Includes generous amount of logging to aid debugging for use in automated tests. | ||||
| """ | ||||
|  | ||||
| import argparse | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
|  | ||||
| # | ||||
| # These Objective-C bindings imports are included in the import path by default | ||||
| # for the Mac-bundled python installed in /usr/bin/python. They're needed to | ||||
| # call the Objective-C API's to set and retrieve the current desktop background | ||||
| # image. | ||||
| # | ||||
| from AppKit import NSScreen, NSWorkspace | ||||
| from Cocoa import NSURL | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     parser = argparse.ArgumentParser( | ||||
|         description="Utility to print, set, or " | ||||
|         + "check the path to image being used as " | ||||
|         + "the desktop background image. By " | ||||
|         + "default, prints the path to the " | ||||
|         + "current desktop background image." | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "-v", | ||||
|         "--verbose", | ||||
|         action="store_true", | ||||
|         help="print verbose debugging information", | ||||
|         default=False, | ||||
|     ) | ||||
|     group = parser.add_mutually_exclusive_group() | ||||
|     group.add_argument( | ||||
|         "-s", | ||||
|         "--set-background-image", | ||||
|         dest="newBackgroundImagePath", | ||||
|         required=False, | ||||
|         help="path to the new background image to set. A zero " | ||||
|         + "exit code indicates no errors occurred.", | ||||
|         default=None, | ||||
|     ) | ||||
|     group.add_argument( | ||||
|         "-c", | ||||
|         "--check-background-image", | ||||
|         dest="checkBackgroundImagePath", | ||||
|         required=False, | ||||
|         help="check if the provided background image path " | ||||
|         + "matches the provided path. A zero exit code " | ||||
|         + "indicates the paths match.", | ||||
|         default=None, | ||||
|     ) | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Using logging for verbose output | ||||
|     if args.verbose: | ||||
|         logging.basicConfig(level=logging.DEBUG) | ||||
|     else: | ||||
|         logging.basicConfig(level=logging.CRITICAL) | ||||
|     logger = logging.getLogger("desktopImage") | ||||
|  | ||||
|     # Print what we're going to do | ||||
|     if args.checkBackgroundImagePath is not None: | ||||
|         logger.debug( | ||||
|             "checking provided desktop image %s matches current " | ||||
|             "image" % args.checkBackgroundImagePath | ||||
|         ) | ||||
|     elif args.newBackgroundImagePath is not None: | ||||
|         logger.debug("setting image to %s " % args.newBackgroundImagePath) | ||||
|     else: | ||||
|         logger.debug("retrieving desktop image path") | ||||
|  | ||||
|     focussedScreen = NSScreen.mainScreen() | ||||
|     if not focussedScreen: | ||||
|         raise RuntimeError("mainScreen error") | ||||
|  | ||||
|     ws = NSWorkspace.sharedWorkspace() | ||||
|     if not ws: | ||||
|         raise RuntimeError("sharedWorkspace error") | ||||
|  | ||||
|     # If we're just checking the image path, check it and then return. | ||||
|     # A successful exit code (0) indicates the paths match. | ||||
|     if args.checkBackgroundImagePath is not None: | ||||
|         # Get existing desktop image path and resolve it | ||||
|         existingImageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) | ||||
|         existingImagePath = existingImageURL.path() | ||||
|         existingImagePathReal = os.path.realpath(existingImagePath) | ||||
|         logger.debug("existing desktop image: %s" % existingImagePath) | ||||
|         logger.debug("existing desktop image realpath: %s" % existingImagePath) | ||||
|  | ||||
|         # Resolve the path we're going to check | ||||
|         checkImagePathReal = os.path.realpath(args.checkBackgroundImagePath) | ||||
|         logger.debug("check desktop image: %s" % args.checkBackgroundImagePath) | ||||
|         logger.debug("check desktop image realpath: %s" % checkImagePathReal) | ||||
|  | ||||
|         if existingImagePathReal == checkImagePathReal: | ||||
|             print("desktop image path matches provided path") | ||||
|             return True | ||||
|  | ||||
|         print("desktop image path does NOT match provided path") | ||||
|         return False | ||||
|  | ||||
|     # Log the current desktop image | ||||
|     if args.verbose: | ||||
|         existingImageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) | ||||
|         logger.debug("existing desktop image: %s" % existingImageURL.path()) | ||||
|  | ||||
|     # Set the desktop image | ||||
|     if args.newBackgroundImagePath is not None: | ||||
|         newImagePath = args.newBackgroundImagePath | ||||
|         if not os.path.exists(newImagePath): | ||||
|             logger.critical("%s does not exist" % newImagePath) | ||||
|             return False | ||||
|         if not os.access(newImagePath, os.R_OK): | ||||
|             logger.critical("%s is not readable" % newImagePath) | ||||
|             return False | ||||
|  | ||||
|         logger.debug("new desktop image to set: %s" % newImagePath) | ||||
|         newImageURL = NSURL.fileURLWithPath_(newImagePath) | ||||
|         logger.debug("new desktop image URL to set: %s" % newImageURL) | ||||
|  | ||||
|         status = False | ||||
|         (status, error) = ws.setDesktopImageURL_forScreen_options_error_( | ||||
|             newImageURL, focussedScreen, None, None | ||||
|         ) | ||||
|         if not status: | ||||
|             raise RuntimeError("setDesktopImageURL error") | ||||
|  | ||||
|     # Print the current desktop image | ||||
|     imageURL = getCurrentDesktopImageURL(focussedScreen, ws, logger) | ||||
|     imagePath = imageURL.path() | ||||
|     imagePathReal = os.path.realpath(imagePath) | ||||
|     logger.debug("updated desktop image URL: %s" % imageURL) | ||||
|     logger.debug("updated desktop image path: %s" % imagePath) | ||||
|     logger.debug("updated desktop image path (resolved): %s" % imagePathReal) | ||||
|     print(imagePathReal) | ||||
|     return True | ||||
|  | ||||
|  | ||||
| def getCurrentDesktopImageURL(focussedScreen, workspace, logger): | ||||
|     imageURL = workspace.desktopImageURLForScreen_(focussedScreen) | ||||
|     if not imageURL: | ||||
|         raise RuntimeError("desktopImageURLForScreen returned invalid URL") | ||||
|     if not imageURL.isFileURL(): | ||||
|         logger.warning("desktop image URL is not a file URL") | ||||
|     return imageURL | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     if not main(): | ||||
|         sys.exit(1) | ||||
|     else: | ||||
|         sys.exit(0) | ||||
| @@ -0,0 +1,40 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * https://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| /** | ||||
|  * Test the macOS ShowSecurityPreferences shell service method. | ||||
|  */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| // eslint-disable-next-line mozilla/no-redeclare-with-import-autofix | ||||
| const { AppConstants } = ChromeUtils.importESModule( | ||||
|   "resource://gre/modules/AppConstants.sys.mjs" | ||||
| ); | ||||
|  | ||||
| function killSystemPreferences() { | ||||
|   let killallFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); | ||||
|   killallFile.initWithPath("/usr/bin/killall"); | ||||
|   let sysPrefsArg = ["System Preferences"]; | ||||
|   if (AppConstants.isPlatformAndVersionAtLeast("macosx", 22)) { | ||||
|     sysPrefsArg = ["System Settings"]; | ||||
|   } | ||||
|   let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); | ||||
|   process.init(killallFile); | ||||
|   process.run(true, sysPrefsArg, 1); | ||||
|   return process.exitValue; | ||||
| } | ||||
|  | ||||
| add_setup(async function () { | ||||
|   info("Ensure System Preferences isn't already running"); | ||||
|   killSystemPreferences(); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_prefsOpen() { | ||||
|   let shellSvc = Cc["@mozilla.org/browser/shell-service;1"].getService( | ||||
|     Ci.nsIMacShellService | ||||
|   ); | ||||
|   shellSvc.showSecurityPreferences("Privacy_AllFiles"); | ||||
|  | ||||
|   equal(killSystemPreferences(), 0, "Ensure System Preferences was started"); | ||||
| }); | ||||
							
								
								
									
										6
									
								
								src/zen/tests/mochitests/shell/unit/xpcshell.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/zen/tests/mochitests/shell/unit/xpcshell.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| [DEFAULT] | ||||
| firefox-appdir = "browser" | ||||
| tags = "os_integration" | ||||
|  | ||||
| ["test_macOS_showSecurityPreferences.js"] | ||||
| run-if = ["os == 'mac'"] | ||||
							
								
								
									
										17
									
								
								src/zen/tests/mochitests/tooltiptext/browser.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/zen/tests/mochitests/tooltiptext/browser.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| [DEFAULT] | ||||
|  | ||||
| ["browser_bug329212.js"] | ||||
| support-files = ["title_test.svg"] | ||||
|  | ||||
| ["browser_bug331772_xul_tooltiptext_in_html.js"] | ||||
| support-files = ["xul_tooltiptext.xhtml"] | ||||
|  | ||||
| ["browser_bug561623.js"] | ||||
|  | ||||
| ["browser_bug581947.js"] | ||||
|  | ||||
| ["browser_input_file_tooltips.js"] | ||||
|  | ||||
| ["browser_nac_tooltip.js"] | ||||
|  | ||||
| ["browser_shadow_dom_tooltip.js"] | ||||
							
								
								
									
										48
									
								
								src/zen/tests/mochitests/tooltiptext/browser_bug329212.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/zen/tests/mochitests/tooltiptext/browser_bug329212.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function () { | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "http://mochi.test:8888/browser/toolkit/components/tooltiptext/tests/title_test.svg", | ||||
|     }, | ||||
|     async function (browser) { | ||||
|       await SpecialPowers.spawn(browser, [""], function () { | ||||
|         let tttp = Cc[ | ||||
|           "@mozilla.org/embedcomp/default-tooltiptextprovider;1" | ||||
|         ].getService(Ci.nsITooltipTextProvider); | ||||
|         function checkElement(id, expectedTooltipText) { | ||||
|           let el = content.document.getElementById(id); | ||||
|           let textObj = {}; | ||||
|           let shouldHaveTooltip = expectedTooltipText !== null; | ||||
|           is( | ||||
|             tttp.getNodeText(el, textObj, {}), | ||||
|             shouldHaveTooltip, | ||||
|             "element " + | ||||
|               id + | ||||
|               " should " + | ||||
|               (shouldHaveTooltip ? "" : "not ") + | ||||
|               "have a tooltip" | ||||
|           ); | ||||
|           if (shouldHaveTooltip) { | ||||
|             is( | ||||
|               textObj.value, | ||||
|               expectedTooltipText, | ||||
|               "element " + id + " should have the right tooltip text" | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|         checkElement("svg1", "This is a non-root SVG element title"); | ||||
|         checkElement("text1", "\n\n\n    This            is a title\n\n    "); | ||||
|         checkElement("text2", null); | ||||
|         checkElement("text3", null); | ||||
|         checkElement("link1", "\n      This is a title\n    "); | ||||
|         checkElement("text4", "\n      This is a title\n    "); | ||||
|         checkElement("link2", null); | ||||
|         checkElement("link3", "This is an xlink:title attribute"); | ||||
|         checkElement("link4", "This is an xlink:title attribute"); | ||||
|         checkElement("text5", null); | ||||
|       }); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,30 @@ | ||||
| /** | ||||
|  * Tests that the tooltiptext attribute is used for XUL elements in an HTML doc. | ||||
|  */ | ||||
| add_task(async function () { | ||||
|   await SpecialPowers.pushPermissions([ | ||||
|     { type: "allowXULXBL", allow: true, context: "http://mochi.test:8888" }, | ||||
|   ]); | ||||
|  | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "http://mochi.test:8888/browser/toolkit/components/tooltiptext/tests/xul_tooltiptext.xhtml", | ||||
|     }, | ||||
|     async function (browser) { | ||||
|       await SpecialPowers.spawn(browser, [""], function () { | ||||
|         let textObj = {}; | ||||
|         let tttp = Cc[ | ||||
|           "@mozilla.org/embedcomp/default-tooltiptextprovider;1" | ||||
|         ].getService(Ci.nsITooltipTextProvider); | ||||
|         let xulToolbarButton = | ||||
|           content.document.getElementById("xulToolbarButton"); | ||||
|         ok( | ||||
|           tttp.getNodeText(xulToolbarButton, textObj, {}), | ||||
|           "should get tooltiptext" | ||||
|         ); | ||||
|         is(textObj.value, "XUL tooltiptext"); | ||||
|       }); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										33
									
								
								src/zen/tests/mochitests/tooltiptext/browser_bug561623.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/zen/tests/mochitests/tooltiptext/browser_bug561623.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| add_task(async function () { | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "data:text/html,<!DOCTYPE html><html><body><input id='i'></body></html>", | ||||
|     }, | ||||
|     async function (browser) { | ||||
|       await SpecialPowers.spawn(browser, [""], function () { | ||||
|         let tttp = Cc[ | ||||
|           "@mozilla.org/embedcomp/default-tooltiptextprovider;1" | ||||
|         ].getService(Ci.nsITooltipTextProvider); | ||||
|         let i = content.document.getElementById("i"); | ||||
|  | ||||
|         ok( | ||||
|           !tttp.getNodeText(i, {}, {}), | ||||
|           "No tooltip should be shown when @title is null" | ||||
|         ); | ||||
|  | ||||
|         i.title = "foo"; | ||||
|         ok( | ||||
|           tttp.getNodeText(i, {}, {}), | ||||
|           "A tooltip should be shown when @title is not the empty string" | ||||
|         ); | ||||
|  | ||||
|         i.pattern = "bar"; | ||||
|         ok( | ||||
|           tttp.getNodeText(i, {}, {}), | ||||
|           "A tooltip should be shown when @title is not the empty string" | ||||
|         ); | ||||
|       }); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
							
								
								
									
										107
									
								
								src/zen/tests/mochitests/tooltiptext/browser_bug581947.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/zen/tests/mochitests/tooltiptext/browser_bug581947.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| function check(aBrowser, aElementName, aBarred, aType) { | ||||
|   return SpecialPowers.spawn( | ||||
|     aBrowser, | ||||
|     [[aElementName, aBarred, aType]], | ||||
|     async function ([aElementName, aBarred, aType]) { | ||||
|       let e = content.document.createElement(aElementName); | ||||
|       let contentElement = content.document.getElementById("content"); | ||||
|       contentElement.appendChild(e); | ||||
|  | ||||
|       if (aType) { | ||||
|         e.type = aType; | ||||
|       } | ||||
|  | ||||
|       let tttp = Cc[ | ||||
|         "@mozilla.org/embedcomp/default-tooltiptextprovider;1" | ||||
|       ].getService(Ci.nsITooltipTextProvider); | ||||
|       ok( | ||||
|         !tttp.getNodeText(e, {}, {}), | ||||
|         "No tooltip should be shown when the element is valid" | ||||
|       ); | ||||
|  | ||||
|       e.setCustomValidity("foo"); | ||||
|       if (aBarred) { | ||||
|         ok( | ||||
|           !tttp.getNodeText(e, {}, {}), | ||||
|           "No tooltip should be shown when the element is barred from constraint validation" | ||||
|         ); | ||||
|       } else { | ||||
|         ok( | ||||
|           tttp.getNodeText(e, {}, {}), | ||||
|           e.tagName + " A tooltip should be shown when the element isn't valid" | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       e.setAttribute("title", ""); | ||||
|       ok( | ||||
|         !tttp.getNodeText(e, {}, {}), | ||||
|         "No tooltip should be shown if the title attribute is set" | ||||
|       ); | ||||
|  | ||||
|       e.removeAttribute("title"); | ||||
|       contentElement.setAttribute("novalidate", ""); | ||||
|       ok( | ||||
|         !tttp.getNodeText(e, {}, {}), | ||||
|         "No tooltip should be shown if the novalidate attribute is set on the form owner" | ||||
|       ); | ||||
|       contentElement.removeAttribute("novalidate"); | ||||
|  | ||||
|       e.remove(); | ||||
|     } | ||||
|   ); | ||||
| } | ||||
|  | ||||
| function todo_check(aBrowser, aElementName, aBarred) { | ||||
|   return SpecialPowers.spawn( | ||||
|     aBrowser, | ||||
|     [[aElementName, aBarred]], | ||||
|     async function ([aElementName]) { | ||||
|       let e = content.document.createElement(aElementName); | ||||
|       let contentElement = content.document.getElementById("content"); | ||||
|       contentElement.appendChild(e); | ||||
|  | ||||
|       let caught = false; | ||||
|       try { | ||||
|         e.setCustomValidity("foo"); | ||||
|       } catch (e) { | ||||
|         caught = true; | ||||
|       } | ||||
|  | ||||
|       todo(!caught, "setCustomValidity should exist for " + aElementName); | ||||
|  | ||||
|       e.remove(); | ||||
|     } | ||||
|   ); | ||||
| } | ||||
|  | ||||
| add_task(async function () { | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "data:text/html,<!DOCTYPE html><html><body><form id='content'></form></body></html>", | ||||
|     }, | ||||
|     async function (browser) { | ||||
|       let testData = [ | ||||
|         /* element name, barred */ | ||||
|         ["input", false, null], | ||||
|         ["textarea", false, null], | ||||
|         ["button", true, "button"], | ||||
|         ["button", false, "submit"], | ||||
|         ["select", false, null], | ||||
|         ["output", true, null], | ||||
|         ["fieldset", true, null], | ||||
|         ["object", true, null], | ||||
|       ]; | ||||
|  | ||||
|       for (let data of testData) { | ||||
|         await check(browser, data[0], data[1], data[2]); | ||||
|       } | ||||
|  | ||||
|       let todo_testData = [["keygen", "false"]]; | ||||
|  | ||||
|       for (let data of todo_testData) { | ||||
|         await todo_check(browser, data[0], data[1]); | ||||
|       } | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,131 @@ | ||||
| /* eslint-disable mozilla/no-arbitrary-setTimeout */ | ||||
|  | ||||
| let tempFile; | ||||
| add_setup(async function () { | ||||
|   await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] }); | ||||
|   tempFile = createTempFile(); | ||||
|   registerCleanupFunction(function () { | ||||
|     tempFile.remove(true); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_singlefile_selected() { | ||||
|   await do_test({ value: true, result: "testfile_bug1251809" }); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_title_set() { | ||||
|   await do_test({ title: "foo", result: "foo" }); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_nofile_selected() { | ||||
|   await do_test({ result: "No file selected." }); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_multipleset_nofile_selected() { | ||||
|   await do_test({ multiple: true, result: "No files selected." }); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_requiredset() { | ||||
|   await do_test({ required: true, result: "Please select a file." }); | ||||
| }); | ||||
|  | ||||
| async function do_test(test) { | ||||
|   info(`starting test ${JSON.stringify(test)}`); | ||||
|  | ||||
|   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); | ||||
|  | ||||
|   info("Moving mouse out of the way."); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove( | ||||
|     tab.linkedBrowser, | ||||
|     300, | ||||
|     300 | ||||
|   ); | ||||
|  | ||||
|   info("creating input field"); | ||||
|   await SpecialPowers.spawn(tab.linkedBrowser, [test], async function (test) { | ||||
|     let doc = content.document; | ||||
|     let input = doc.createElement("input"); | ||||
|     doc.body.appendChild(input); | ||||
|     input.id = "test_input"; | ||||
|     input.setAttribute("style", "position: absolute; top: 0; left: 0;"); | ||||
|     input.type = "file"; | ||||
|     if (test.title) { | ||||
|       input.setAttribute("title", test.title); | ||||
|     } | ||||
|     if (test.multiple) { | ||||
|       input.multiple = true; | ||||
|     } | ||||
|     if (test.required) { | ||||
|       input.required = true; | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   if (test.value) { | ||||
|     info("Creating mock filepicker to select files"); | ||||
|     let MockFilePicker = SpecialPowers.MockFilePicker; | ||||
|     MockFilePicker.init(window.browsingContext); | ||||
|     MockFilePicker.returnValue = MockFilePicker.returnOK; | ||||
|     MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", []); | ||||
|     MockFilePicker.setFiles([tempFile]); | ||||
|     MockFilePicker.afterOpenCallback = MockFilePicker.cleanup; | ||||
|  | ||||
|     try { | ||||
|       // Open the File Picker dialog (MockFilePicker) to select | ||||
|       // the files for the test. | ||||
|       await BrowserTestUtils.synthesizeMouseAtCenter( | ||||
|         "#test_input", | ||||
|         {}, | ||||
|         tab.linkedBrowser | ||||
|       ); | ||||
|       info("Waiting for the input to have the requisite files"); | ||||
|       await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { | ||||
|         let input = content.document.querySelector("#test_input"); | ||||
|         await ContentTaskUtils.waitForCondition( | ||||
|           () => input.files.length, | ||||
|           "The input should have at least one file selected" | ||||
|         ); | ||||
|         info(`The input has ${input.files.length} file(s) selected.`); | ||||
|       }); | ||||
|     } catch (e) {} | ||||
|   } else { | ||||
|     info("No real file selection required."); | ||||
|   } | ||||
|  | ||||
|   let awaitTooltipOpen = new Promise(resolve => { | ||||
|     let tooltipId = Services.appinfo.browserTabsRemoteAutostart | ||||
|       ? "remoteBrowserTooltip" | ||||
|       : "aHTMLTooltip"; | ||||
|     let tooltip = document.getElementById(tooltipId); | ||||
|     tooltip.addEventListener( | ||||
|       "popupshown", | ||||
|       function (event) { | ||||
|         resolve(event.target); | ||||
|       }, | ||||
|       { once: true } | ||||
|     ); | ||||
|   }); | ||||
|   info("Initial mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5); | ||||
|   info("Waiting"); | ||||
|   await new Promise(resolve => setTimeout(resolve, 400)); | ||||
|   info("Second mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5); | ||||
|   info("Waiting for tooltip to open"); | ||||
|   let tooltip = await awaitTooltipOpen; | ||||
|  | ||||
|   is( | ||||
|     tooltip.getAttribute("label"), | ||||
|     test.result, | ||||
|     "tooltip label should match expectation" | ||||
|   ); | ||||
|  | ||||
|   info("Closing tab"); | ||||
|   BrowserTestUtils.removeTab(tab); | ||||
| } | ||||
|  | ||||
| function createTempFile() { | ||||
|   let file = FileUtils.getDir("TmpD", []); | ||||
|   file.append("testfile_bug1251809"); | ||||
|   file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); | ||||
|   return file; | ||||
| } | ||||
							
								
								
									
										66
									
								
								src/zen/tests/mochitests/tooltiptext/browser_nac_tooltip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/zen/tests/mochitests/tooltiptext/browser_nac_tooltip.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|  * http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
| /* eslint-disable mozilla/no-arbitrary-setTimeout */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_setup(async function () { | ||||
|   await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] }); | ||||
| }); | ||||
|  | ||||
| add_task(async function () { | ||||
|   await BrowserTestUtils.withNewTab( | ||||
|     { | ||||
|       gBrowser, | ||||
|       url: "data:text/html,<!DOCTYPE html>", | ||||
|     }, | ||||
|     async function (browser) { | ||||
|       info("Moving mouse out of the way."); | ||||
|       await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 300, 300); | ||||
|  | ||||
|       await SpecialPowers.spawn(browser, [], function () { | ||||
|         let widget = content.document.insertAnonymousContent(); | ||||
|         widget.root.innerHTML = `<button style="pointer-events: auto; position: absolute; width: 200px; height: 200px;" title="foo">bar</button>`; | ||||
|         let tttp = Cc[ | ||||
|           "@mozilla.org/embedcomp/default-tooltiptextprovider;1" | ||||
|         ].getService(Ci.nsITooltipTextProvider); | ||||
|  | ||||
|         let text = {}; | ||||
|         let dir = {}; | ||||
|         ok( | ||||
|           tttp.getNodeText(widget.root.querySelector("button"), text, dir), | ||||
|           "A tooltip should be shown for NAC" | ||||
|         ); | ||||
|         is(text.value, "foo", "Tooltip text should be correct"); | ||||
|       }); | ||||
|  | ||||
|       let awaitTooltipOpen = new Promise(resolve => { | ||||
|         let tooltipId = Services.appinfo.browserTabsRemoteAutostart | ||||
|           ? "remoteBrowserTooltip" | ||||
|           : "aHTMLTooltip"; | ||||
|         let tooltip = document.getElementById(tooltipId); | ||||
|         tooltip.addEventListener( | ||||
|           "popupshown", | ||||
|           function (event) { | ||||
|             resolve(event.target); | ||||
|           }, | ||||
|           { once: true } | ||||
|         ); | ||||
|       }); | ||||
|  | ||||
|       info("Initial mouse move"); | ||||
|       await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 50, 5); | ||||
|       info("Waiting"); | ||||
|       await new Promise(resolve => setTimeout(resolve, 400)); | ||||
|       info("Second mouse move"); | ||||
|       await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 70, 5); | ||||
|       info("Waiting for tooltip to open"); | ||||
|       let tooltip = await awaitTooltipOpen; | ||||
|       is( | ||||
|         tooltip.getAttribute("label"), | ||||
|         "foo", | ||||
|         "tooltip label should match expectation" | ||||
|       ); | ||||
|     } | ||||
|   ); | ||||
| }); | ||||
| @@ -0,0 +1,166 @@ | ||||
| /* eslint-disable mozilla/no-arbitrary-setTimeout */ | ||||
|  | ||||
| add_setup(async function () { | ||||
|   await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] }); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_title_in_shadow_dom() { | ||||
|   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); | ||||
|  | ||||
|   info("Moving mouse out of the way."); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove( | ||||
|     tab.linkedBrowser, | ||||
|     300, | ||||
|     300 | ||||
|   ); | ||||
|  | ||||
|   info("creating host"); | ||||
|   await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { | ||||
|     let doc = content.document; | ||||
|     let host = doc.createElement("div"); | ||||
|     doc.body.appendChild(host); | ||||
|     host.setAttribute("style", "position: absolute; top: 0; left: 0;"); | ||||
|     var sr = host.attachShadow({ mode: "closed" }); | ||||
|     sr.innerHTML = | ||||
|       "<div title='shadow' style='width: 200px; height: 200px;'>shadow</div>"; | ||||
|   }); | ||||
|  | ||||
|   let awaitTooltipOpen = new Promise(resolve => { | ||||
|     let tooltipId = Services.appinfo.browserTabsRemoteAutostart | ||||
|       ? "remoteBrowserTooltip" | ||||
|       : "aHTMLTooltip"; | ||||
|     let tooltip = document.getElementById(tooltipId); | ||||
|     tooltip.addEventListener( | ||||
|       "popupshown", | ||||
|       function (event) { | ||||
|         resolve(event.target); | ||||
|       }, | ||||
|       { once: true } | ||||
|     ); | ||||
|   }); | ||||
|   info("Initial mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5); | ||||
|   info("Waiting"); | ||||
|   await new Promise(resolve => setTimeout(resolve, 400)); | ||||
|   info("Second mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5); | ||||
|   info("Waiting for tooltip to open"); | ||||
|   let tooltip = await awaitTooltipOpen; | ||||
|  | ||||
|   is( | ||||
|     tooltip.getAttribute("label"), | ||||
|     "shadow", | ||||
|     "tooltip label should match expectation" | ||||
|   ); | ||||
|  | ||||
|   info("Closing tab"); | ||||
|   BrowserTestUtils.removeTab(tab); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_title_in_light_dom() { | ||||
|   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); | ||||
|  | ||||
|   info("Moving mouse out of the way."); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove( | ||||
|     tab.linkedBrowser, | ||||
|     300, | ||||
|     300 | ||||
|   ); | ||||
|  | ||||
|   info("creating host"); | ||||
|   await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { | ||||
|     let doc = content.document; | ||||
|     let host = doc.createElement("div"); | ||||
|     host.title = "light"; | ||||
|     doc.body.appendChild(host); | ||||
|     host.setAttribute("style", "position: absolute; top: 0; left: 0;"); | ||||
|     var sr = host.attachShadow({ mode: "closed" }); | ||||
|     sr.innerHTML = "<div style='width: 200px; height: 200px;'>shadow</div>"; | ||||
|   }); | ||||
|  | ||||
|   let awaitTooltipOpen = new Promise(resolve => { | ||||
|     let tooltipId = Services.appinfo.browserTabsRemoteAutostart | ||||
|       ? "remoteBrowserTooltip" | ||||
|       : "aHTMLTooltip"; | ||||
|     let tooltip = document.getElementById(tooltipId); | ||||
|     tooltip.addEventListener( | ||||
|       "popupshown", | ||||
|       function (event) { | ||||
|         resolve(event.target); | ||||
|       }, | ||||
|       { once: true } | ||||
|     ); | ||||
|   }); | ||||
|   info("Initial mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5); | ||||
|   info("Waiting"); | ||||
|   await new Promise(resolve => setTimeout(resolve, 400)); | ||||
|   info("Second mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5); | ||||
|   info("Waiting for tooltip to open"); | ||||
|   let tooltip = await awaitTooltipOpen; | ||||
|  | ||||
|   is( | ||||
|     tooltip.getAttribute("label"), | ||||
|     "light", | ||||
|     "tooltip label should match expectation" | ||||
|   ); | ||||
|  | ||||
|   info("Closing tab"); | ||||
|   BrowserTestUtils.removeTab(tab); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_title_through_slot() { | ||||
|   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); | ||||
|  | ||||
|   info("Moving mouse out of the way."); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove( | ||||
|     tab.linkedBrowser, | ||||
|     300, | ||||
|     300 | ||||
|   ); | ||||
|  | ||||
|   info("creating host"); | ||||
|   await SpecialPowers.spawn(tab.linkedBrowser, [], async function () { | ||||
|     let doc = content.document; | ||||
|     let host = doc.createElement("div"); | ||||
|     host.title = "light"; | ||||
|     host.innerHTML = "<div style='width: 200px; height: 200px;'>light</div>"; | ||||
|     doc.body.appendChild(host); | ||||
|     host.setAttribute("style", "position: absolute; top: 0; left: 0;"); | ||||
|     var sr = host.attachShadow({ mode: "closed" }); | ||||
|     sr.innerHTML = | ||||
|       "<div title='shadow' style='width: 200px; height: 200px;'><slot></slot></div>"; | ||||
|   }); | ||||
|  | ||||
|   let awaitTooltipOpen = new Promise(resolve => { | ||||
|     let tooltipId = Services.appinfo.browserTabsRemoteAutostart | ||||
|       ? "remoteBrowserTooltip" | ||||
|       : "aHTMLTooltip"; | ||||
|     let tooltip = document.getElementById(tooltipId); | ||||
|     tooltip.addEventListener( | ||||
|       "popupshown", | ||||
|       function (event) { | ||||
|         resolve(event.target); | ||||
|       }, | ||||
|       { once: true } | ||||
|     ); | ||||
|   }); | ||||
|   info("Initial mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5); | ||||
|   info("Waiting"); | ||||
|   await new Promise(resolve => setTimeout(resolve, 400)); | ||||
|   info("Second mouse move"); | ||||
|   await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5); | ||||
|   info("Waiting for tooltip to open"); | ||||
|   let tooltip = await awaitTooltipOpen; | ||||
|  | ||||
|   is( | ||||
|     tooltip.getAttribute("label"), | ||||
|     "shadow", | ||||
|     "tooltip label should match expectation" | ||||
|   ); | ||||
|  | ||||
|   info("Closing tab"); | ||||
|   BrowserTestUtils.removeTab(tab); | ||||
| }); | ||||
							
								
								
									
										59
									
								
								src/zen/tests/mochitests/tooltiptext/title_test.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/zen/tests/mochitests/tooltiptext/title_test.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||||
|   <title>This is a root SVG element's title</title> | ||||
|   <foreignObject> | ||||
|     <html xmlns="http://www.w3.org/1999/xhtml"> | ||||
|       <body> | ||||
|         <svg xmlns="http://www.w3.org/2000/svg" id="svg1"> | ||||
|           <title>This is a non-root SVG element title</title> | ||||
|         </svg> | ||||
|       </body> | ||||
|     </html> | ||||
|   </foreignObject> | ||||
|   <text id="text1" x="10px" y="32px" font-size="24px"> | ||||
|     This contains only <title> | ||||
|     <title> | ||||
|  | ||||
|  | ||||
|     This            is a title | ||||
|  | ||||
|     </title> | ||||
|   </text> | ||||
|   <text id="text2" x="10px" y="96px" font-size="24px"> | ||||
|     This contains only <desc> | ||||
|     <desc>This is a desc</desc> | ||||
|   </text> | ||||
|   <text id="text3" x="10px" y="128px" font-size="24px" title="ignored for SVG"> | ||||
|     This contains nothing. | ||||
|   </text> | ||||
|   <a id="link1" href="#"> | ||||
|     This link contains <title> | ||||
|     <title> | ||||
|       This is a title | ||||
|     </title> | ||||
|     <text id="text4" x="10px" y="192px" font-size="24px"> | ||||
|     </text> | ||||
|   </a> | ||||
|   <a id="link2" href="#"> | ||||
|     <text x="10px" y="192px" font-size="24px"> | ||||
|       This text contains <title> | ||||
|       <title> | ||||
|       This is a title | ||||
|       </title> | ||||
|     </text> | ||||
|   </a> | ||||
|   <a id="link3" href="#" xlink:title="This is an xlink:title attribute"> | ||||
|     <text x="10px" y="224px" font-size="24px"> | ||||
|       This link contains <title> & xlink:title attr. | ||||
|       <title>This is a title</title> | ||||
|     </text> | ||||
|   </a> | ||||
|   <a id="link4" href="#" xlink:title="This is an xlink:title attribute"> | ||||
|     <text x="10px" y="256px" font-size="24px"> | ||||
|       This link contains xlink:title attr. | ||||
|     </text> | ||||
|   </a> | ||||
|   <text id="text5" x="10px" y="160px" font-size="24px" | ||||
|         xlink:title="This is an xlink:title attribute but it isn't on a link" > | ||||
|     This contains nothing. | ||||
|   </text> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.8 KiB | 
							
								
								
									
										12
									
								
								src/zen/tests/mochitests/tooltiptext/xul_tooltiptext.xhtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/zen/tests/mochitests/tooltiptext/xul_tooltiptext.xhtml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <?xml version="1.0"?> | ||||
| <html xmlns="http://www.w3.org/1999/xhtml" | ||||
|       xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> | ||||
|   <xul:toolbox xmlns:html="http://www.w3.org/1999/xhtml" | ||||
|                xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> | ||||
|       <toolbar> | ||||
|           <toolbarbutton id="xulToolbarButton" | ||||
|                          tooltiptext="XUL tooltiptext" | ||||
|                          title="XUL title"/> | ||||
|       </toolbar> | ||||
|   </xul:toolbox> | ||||
| </html> | ||||
							
								
								
									
										285
									
								
								src/zen/tests/mochitests/translations/browser.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										285
									
								
								src/zen/tests/mochitests/translations/browser.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,285 @@ | ||||
| [DEFAULT] | ||||
| subsuite = "translations" | ||||
| support-files = [ | ||||
|   "head.js", | ||||
|   "!/toolkit/components/translations/tests/browser/shared-head.js", | ||||
|   "!/toolkit/components/translations/tests/browser/translations-test.mjs", | ||||
| ] | ||||
|  | ||||
| ["browser_translations_about_preferences_manage_downloaded_languages.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_always_translate_languages.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_download_languages_all_ui.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_download_languages_error_ui.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_download_languages_ui.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_never_translate_languages.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_never_translate_sites.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_ui.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_ui_keyboard_a11y.js"] | ||||
|  | ||||
| ["browser_translations_about_preferences_settings_ui_tab.js"] | ||||
|  | ||||
| ["browser_translations_e2e_full_page_translate_with_lexical_shortlist.js"] | ||||
|  | ||||
| ["browser_translations_e2e_full_page_translate_without_lexical_shortlist.js"] | ||||
|  | ||||
| ["browser_translations_full_page_intersection_content_eager.js"] | ||||
|  | ||||
| ["browser_translations_full_page_intersection_find_bar.js"] | ||||
|  | ||||
| ["browser_translations_full_page_intersection_find_bar_move_tab_to_new_window.js"] | ||||
|  | ||||
| ["browser_translations_full_page_intersection_find_bar_multi_tab.js"] | ||||
|  | ||||
| ["browser_translations_full_page_intersection_lazy.js"] | ||||
|  | ||||
| ["browser_translations_full_page_intersection_mutations_content_eager.js"] | ||||
|  | ||||
| ["browser_translations_full_page_intersection_mutations_lazy.js"] | ||||
|  | ||||
| ["browser_translations_full_page_language_id_behavior.js"] | ||||
|  | ||||
| ["browser_translations_full_page_move_tab_to_new_window.js"] | ||||
|  | ||||
| ["browser_translations_full_page_moz_extension.js"] | ||||
|  | ||||
| ["browser_translations_full_page_multiple_windows.js"] | ||||
| skip-if = ["os == 'linux' && os_version == '24.04' && processor == 'x86_64' && display == 'x11' && ccov"] # Bug 1893021 | ||||
|  | ||||
| ["browser_translations_full_page_panel_a11y_focus.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_always_translate_language_bad_data.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_always_translate_language_basic.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_always_translate_language_manual.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_always_translate_language_restore.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_app_menu_never_translate_language.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_app_menu_never_translate_site.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_auto_translate_error_view.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_auto_translate_revisit_view.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_basics.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_button.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_cancel.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_change_app_locale.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_close_panel_never_translate_language_with_translations_active.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_close_panel_never_translate_language_with_translations_inactive.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_close_panel_never_translate_site.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_engine_destroy.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_engine_destroy_pending.js"] | ||||
| skip-if = ["os == 'linux' && os_version == '24.04' && processor == 'x86_64' && display == 'x11' && ccov"] # Bug 1893021 | ||||
|  | ||||
| ["browser_translations_full_page_panel_engine_unsupported.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_firstrun.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_firstrun_revisit.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_flip_lexical_shortlist.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_fuzzing.js"] | ||||
| skip-if = ["true"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_gear.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_init_failure.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_modify_available_language_models.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_never_translate_language.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_never_translate_site_auto.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_never_translate_site_basic.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_never_translate_site_manual.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_retry.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_script_tags.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_settings_unsupported_lang.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_switch_languages.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_switch_tabs_before_engine_ready.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_target_language_persists_on_reopen.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_unsupported_lang.js"] | ||||
|  | ||||
| ["browser_translations_full_page_panel_weblanguage_differs_from_app.js"] | ||||
|  | ||||
| ["browser_translations_full_page_reader_mode.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_auto_translate.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_basics.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_open_panel.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_panel_auto_offer.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_panel_auto_offer_settings.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_retranslate.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_switch_languages.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_translation_failure.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_translation_request.js"] | ||||
|  | ||||
| ["browser_translations_full_page_telemetry_unsupported_lang.js"] | ||||
|  | ||||
| ["browser_translations_recent_language_memory_auto_translate.js"] | ||||
|  | ||||
| ["browser_translations_recent_language_memory_full_page_and_select.js"] | ||||
|  | ||||
| ["browser_translations_recent_language_memory_full_page_and_select_multi_window.js"] | ||||
| skip-if = ["os == 'linux' && os_version == '24.04' && processor == 'x86_64' && display == 'x11' && ccov"] # Bug 1972458 | ||||
|  | ||||
| ["browser_translations_recent_language_memory_full_page_multi_window.js"] | ||||
|  | ||||
| ["browser_translations_recent_language_memory_full_page_multi_window_multi_tab.js"] | ||||
|  | ||||
| ["browser_translations_recent_language_memory_full_page_navigate.js"] | ||||
|  | ||||
| ["browser_translations_recent_language_memory_full_page_retranslate.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_engine_unsupported.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_feature_disabled.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_preferred_app_locales.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_preferred_language_edge_cases.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_preferred_web_languages.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_with_full_page_translations_active.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_with_hyperlink.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_with_no_text_selected.js"] | ||||
|  | ||||
| ["browser_translations_select_context_menu_with_text_selected.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_a11y_utils.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_change_app_locale.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_close_on_new_tab.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_copy_button.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_engine_cache.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_fallback_to_doc_language.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_flip_lexical_shortlist.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_init_failure.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_modify_available_language_models.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_pdf.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_reader_mode.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_retranslate_on_change_language_directly.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_retranslate_on_change_language_from_dropdown_menu.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_script_tags.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_select_current_language_directly.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_select_current_language_from_dropdown_menu.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_select_same_from_and_to_languages_directly.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_settings_menu.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translate_full_page_button.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translate_on_change_language_directly.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translate_on_open.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translation_failure_after_unsupported_language.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translation_failure_on_open.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_translation_failure_on_retranslate.js"] | ||||
|  | ||||
| ["browser_translations_select_panel_unsupported_language.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_change_both_languages_together.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_change_languages_multiple_times.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_init_failure_ui_then_cancel.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_init_failure_ui_then_succeed.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_cancel_button.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_copy_button.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_done_button.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_settings_button.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_translate_button.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_translate_full_page_button.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_try_again_button_init_failure.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_keypresses_try_again_button_translation_failure.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_multi_page.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_primary_ui.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_settings_menu.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_translation_failure_ui_then_cancel.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_translation_failure_ui_then_succeed.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_translation_failure_with_full_page_translations_active.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_translation_success_with_full_page_translations_active.js"] | ||||
|  | ||||
| ["browser_translations_select_telemetry_unsupported_language_ui.js"] | ||||
| @@ -0,0 +1,225 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| function getFrenchModels() { | ||||
|   return languageModelNames([ | ||||
|     { fromLang: "fr", toLang: "en" }, | ||||
|     { fromLang: "en", toLang: "fr" }, | ||||
|   ]); | ||||
| } | ||||
|  | ||||
| add_task(async function test_about_preferences_manage_languages() { | ||||
|   await testWithAndWithoutLexicalShortlist(async lexicalShortlistPrefs => { | ||||
|     const { | ||||
|       cleanup, | ||||
|       remoteClients, | ||||
|       elements: { | ||||
|         downloadAllLabel, | ||||
|         downloadAll, | ||||
|         deleteAll, | ||||
|         frenchLabel, | ||||
|         frenchDownload, | ||||
|         frenchDelete, | ||||
|         spanishLabel, | ||||
|         spanishDownload, | ||||
|         spanishDelete, | ||||
|         ukrainianLabel, | ||||
|         ukrainianDownload, | ||||
|         ukrainianDelete, | ||||
|       }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [ | ||||
|         ["browser.translations.newSettingsUI.enable", false], | ||||
|         ...lexicalShortlistPrefs, | ||||
|       ], | ||||
|     }); | ||||
|  | ||||
|     is( | ||||
|       downloadAllLabel.getAttribute("data-l10n-id"), | ||||
|       "translations-manage-download-description", | ||||
|       "The first row is all of the languages." | ||||
|     ); | ||||
|     is(frenchLabel.textContent, "French", "There is a French row."); | ||||
|     is(spanishLabel.textContent, "Spanish", "There is a Spanish row."); | ||||
|     is(ukrainianLabel.textContent, "Ukrainian", "There is a Ukrainian row."); | ||||
|  | ||||
|     await ensureVisibility({ | ||||
|       message: "Everything starts out as available to download", | ||||
|       visible: { | ||||
|         downloadAll, | ||||
|         frenchDownload, | ||||
|         spanishDownload, | ||||
|         ukrainianDownload, | ||||
|       }, | ||||
|       hidden: { deleteAll, frenchDelete, spanishDelete, ukrainianDelete }, | ||||
|     }); | ||||
|  | ||||
|     click(frenchDownload, "Downloading French"); | ||||
|  | ||||
|     const frenchModels = getFrenchModels(); | ||||
|  | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationModels.resolvePendingDownloads( | ||||
|         frenchModels.length | ||||
|       ), | ||||
|       frenchModels, | ||||
|       "French models were downloaded." | ||||
|     ); | ||||
|  | ||||
|     await ensureVisibility({ | ||||
|       message: "French can now be deleted, and delete all is available.", | ||||
|       visible: { | ||||
|         downloadAll, | ||||
|         deleteAll, | ||||
|         frenchDelete, | ||||
|         spanishDownload, | ||||
|         ukrainianDownload, | ||||
|       }, | ||||
|       hidden: { frenchDownload, spanishDelete, ukrainianDelete }, | ||||
|     }); | ||||
|  | ||||
|     click(frenchDelete, "Deleting French"); | ||||
|  | ||||
|     await ensureVisibility({ | ||||
|       message: "Everything can be downloaded.", | ||||
|       visible: { | ||||
|         downloadAll, | ||||
|         frenchDownload, | ||||
|         spanishDownload, | ||||
|         ukrainianDownload, | ||||
|       }, | ||||
|       hidden: { deleteAll, frenchDelete, spanishDelete, ukrainianDelete }, | ||||
|     }); | ||||
|  | ||||
|     click(downloadAll, "Downloading all languages."); | ||||
|  | ||||
|     const allModels = languageModelNames(LANGUAGE_PAIRS); | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationModels.resolvePendingDownloads( | ||||
|         allModels.length | ||||
|       ), | ||||
|       allModels, | ||||
|       "All models were downloaded." | ||||
|     ); | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationsWasm.resolvePendingDownloads(1), | ||||
|       ["bergamot-translator"], | ||||
|       "Wasm was downloaded." | ||||
|     ); | ||||
|  | ||||
|     await ensureVisibility({ | ||||
|       message: "Everything can be deleted.", | ||||
|       visible: { deleteAll, frenchDelete, spanishDelete, ukrainianDelete }, | ||||
|       hidden: { | ||||
|         downloadAll, | ||||
|         frenchDownload, | ||||
|         spanishDownload, | ||||
|         ukrainianDownload, | ||||
|       }, | ||||
|     }); | ||||
|  | ||||
|     click(deleteAll, "Deleting all languages."); | ||||
|  | ||||
|     await ensureVisibility({ | ||||
|       message: "Everything can be downloaded again", | ||||
|       visible: { | ||||
|         downloadAll, | ||||
|         frenchDownload, | ||||
|         spanishDownload, | ||||
|         ukrainianDownload, | ||||
|       }, | ||||
|       hidden: { deleteAll, frenchDelete, spanishDelete, ukrainianDelete }, | ||||
|     }); | ||||
|  | ||||
|     click(frenchDownload, "Downloading French."); | ||||
|     click(spanishDownload, "Downloading Spanish."); | ||||
|     click(ukrainianDownload, "Downloading Ukrainian."); | ||||
|  | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationModels.resolvePendingDownloads( | ||||
|         allModels.length | ||||
|       ), | ||||
|       allModels, | ||||
|       "All models were downloaded again." | ||||
|     ); | ||||
|  | ||||
|     remoteClients.translationsWasm.assertNoNewDownloads(); | ||||
|  | ||||
|     await ensureVisibility({ | ||||
|       message: "Everything is downloaded again.", | ||||
|       visible: { deleteAll, frenchDelete, spanishDelete, ukrainianDelete }, | ||||
|       hidden: { | ||||
|         downloadAll, | ||||
|         frenchDownload, | ||||
|         spanishDownload, | ||||
|         ukrainianDownload, | ||||
|       }, | ||||
|     }); | ||||
|  | ||||
|     await cleanup(); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_about_preferences_download_reject() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     remoteClients, | ||||
|     elements: { document, frenchDownload }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", false]], | ||||
|   }); | ||||
|  | ||||
|   click(frenchDownload, "Downloading French"); | ||||
|  | ||||
|   is( | ||||
|     maybeGetByL10nId("translations-manage-error-download", document), | ||||
|     null, | ||||
|     "No error messages are present." | ||||
|   ); | ||||
|  | ||||
|   const failureErrors = await captureTranslationsError(() => | ||||
|     remoteClients.translationModels.rejectPendingDownloads( | ||||
|       getFrenchModels().length | ||||
|     ) | ||||
|   ); | ||||
|  | ||||
|   ok( | ||||
|     !!failureErrors.length, | ||||
|     `The errors for download should have been reported, found ${failureErrors.length} errors` | ||||
|   ); | ||||
|   for (const { error } of failureErrors) { | ||||
|     is( | ||||
|       error?.message, | ||||
|       "Failed to download file.", | ||||
|       "The error reported was a download error." | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   await waitForCondition( | ||||
|     () => maybeGetByL10nId("translations-manage-error-download", document), | ||||
|     "The error message is now visible." | ||||
|   ); | ||||
|  | ||||
|   click(frenchDownload, "Attempting to download French again", document); | ||||
|   is( | ||||
|     maybeGetByL10nId("translations-manage-error-download", document), | ||||
|     null, | ||||
|     "The error message is hidden again." | ||||
|   ); | ||||
|  | ||||
|   const successErrors = await captureTranslationsError(() => | ||||
|     remoteClients.translationModels.resolvePendingDownloads( | ||||
|       getFrenchModels().length | ||||
|     ) | ||||
|   ); | ||||
|  | ||||
|   is( | ||||
|     successErrors.length, | ||||
|     0, | ||||
|     "Expected no errors downloading French the second time" | ||||
|   ); | ||||
|  | ||||
|   await cleanup(); | ||||
| }); | ||||
| @@ -0,0 +1,97 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_task( | ||||
|   async function test_about_preferences_always_translate_language_settings() { | ||||
|     const { | ||||
|       cleanup, | ||||
|       elements: { settingsButton }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [["browser.translations.newSettingsUI.enable", false]], | ||||
|     }); | ||||
|  | ||||
|     info("Ensuring the list of always-translate languages is empty"); | ||||
|     is( | ||||
|       getAlwaysTranslateLanguagesFromPref().length, | ||||
|       0, | ||||
|       "The list of always-translate languages is empty" | ||||
|     ); | ||||
|  | ||||
|     info("Adding two languages to the alwaysTranslateLanguages pref"); | ||||
|     Services.prefs.setCharPref(ALWAYS_TRANSLATE_LANGS_PREF, "fr,de"); | ||||
|  | ||||
|     const dialogWindow = await waitForOpenDialogWindow( | ||||
|       "chrome://browser/content/preferences/dialogs/translations.xhtml", | ||||
|       () => { | ||||
|         click( | ||||
|           settingsButton, | ||||
|           "Opening the about:preferences Translations Settings" | ||||
|         ); | ||||
|       } | ||||
|     ); | ||||
|     let tree = dialogWindow.document.getElementById( | ||||
|       "alwaysTranslateLanguagesTree" | ||||
|     ); | ||||
|     let remove = dialogWindow.document.getElementById( | ||||
|       "removeAlwaysTranslateLanguage" | ||||
|     ); | ||||
|     let removeAll = dialogWindow.document.getElementById( | ||||
|       "removeAllAlwaysTranslateLanguages" | ||||
|     ); | ||||
|  | ||||
|     is( | ||||
|       tree.view.rowCount, | ||||
|       2, | ||||
|       "The always-translate languages list has 2 items" | ||||
|     ); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled"); | ||||
|  | ||||
|     info("Selecting the first always-translate language."); | ||||
|     tree.view.selection.select(0); | ||||
|     ok(!remove.disabled, "The 'Remove Language' button is enabled"); | ||||
|  | ||||
|     click(remove, "Clicking the remove-language button"); | ||||
|     is( | ||||
|       tree.view.rowCount, | ||||
|       1, | ||||
|       "The always-translate languages list now contains 1 item" | ||||
|     ); | ||||
|     is( | ||||
|       getAlwaysTranslateLanguagesFromPref().length, | ||||
|       1, | ||||
|       "One language tag in the pref" | ||||
|     ); | ||||
|  | ||||
|     info("Removing all languages from the alwaysTranslateLanguages pref"); | ||||
|     Services.prefs.setCharPref(ALWAYS_TRANSLATE_LANGS_PREF, ""); | ||||
|     is(tree.view.rowCount, 0, "The always-translate languages list is empty"); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(removeAll.disabled, "The 'Remove All Languages' button is disabled"); | ||||
|  | ||||
|     info("Adding more languages to the alwaysTranslateLanguages pref"); | ||||
|     Services.prefs.setCharPref(ALWAYS_TRANSLATE_LANGS_PREF, "fr,en,es"); | ||||
|     is( | ||||
|       tree.view.rowCount, | ||||
|       3, | ||||
|       "The always-translate languages list has 3 items" | ||||
|     ); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled"); | ||||
|  | ||||
|     click(removeAll, "Clicking the remove-all languages button"); | ||||
|     is(tree.view.rowCount, 0, "The always-translate languages list is empty"); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(removeAll.disabled, "The 'Remove All Languages' button is disabled"); | ||||
|     is( | ||||
|       getAlwaysTranslateLanguagesFromPref().length, | ||||
|       0, | ||||
|       "There are no languages in the alwaysTranslateLanguages pref" | ||||
|     ); | ||||
|  | ||||
|     await waitForCloseDialogWindow(dialogWindow); | ||||
|     await cleanup(); | ||||
|   } | ||||
| ); | ||||
| @@ -0,0 +1,159 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function test_translations_settings_download_languages_all() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     remoteClients, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|   }); | ||||
|  | ||||
|   const frenchModels = languageModelNames([ | ||||
|     { fromLang: "fr", toLang: "en" }, | ||||
|     { fromLang: "en", toLang: "fr" }, | ||||
|   ]); | ||||
|  | ||||
|   const spanishModels = languageModelNames([ | ||||
|     { fromLang: "es", toLang: "en" }, | ||||
|     { fromLang: "en", toLang: "es" }, | ||||
|   ]); | ||||
|  | ||||
|   const ukrainianModels = languageModelNames([ | ||||
|     { fromLang: "uk", toLang: "en" }, | ||||
|     { fromLang: "en", toLang: "uk" }, | ||||
|   ]); | ||||
|  | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneGeneral elements to be visible.", | ||||
|     visible: { settingsButton }, | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "Open translations settings page by clicking on translations settings button." | ||||
|   ); | ||||
|   const { downloadLanguageList } = | ||||
|     await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|       settingsButton | ||||
|     ); | ||||
|  | ||||
|   info( | ||||
|     "Install each language French, Spanish and Ukrainian and check if All language state changes to 'all language downloaded' by changing the all language button icon to 'remove icon'" | ||||
|   ); | ||||
|  | ||||
|   info("Download French language model."); | ||||
|   let langFr = Array.from(downloadLanguageList.querySelectorAll("label")).find( | ||||
|     el => el.getAttribute("value") === "fr" | ||||
|   ); | ||||
|  | ||||
|   let clickButton = BrowserTestUtils.waitForEvent( | ||||
|     langFr.parentNode.querySelector("moz-button"), | ||||
|     "click" | ||||
|   ); | ||||
|   langFr.parentNode.querySelector("moz-button").click(); | ||||
|   await clickButton; | ||||
|  | ||||
|   Assert.deepEqual( | ||||
|     await remoteClients.translationModels.resolvePendingDownloads( | ||||
|       frenchModels.length | ||||
|     ), | ||||
|     frenchModels, | ||||
|     "French models were downloaded." | ||||
|   ); | ||||
|  | ||||
|   await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|     langFr, | ||||
|     "translations-settings-remove-icon", | ||||
|     "Delete icon is visible for French language hence downloaded" | ||||
|   ); | ||||
|  | ||||
|   info("Download Spanish language model."); | ||||
|  | ||||
|   let langEs = Array.from(downloadLanguageList.querySelectorAll("label")).find( | ||||
|     el => el.getAttribute("value") === "es" | ||||
|   ); | ||||
|  | ||||
|   clickButton = BrowserTestUtils.waitForEvent( | ||||
|     langEs.parentNode.querySelector("moz-button"), | ||||
|     "click" | ||||
|   ); | ||||
|   langEs.parentNode.querySelector("moz-button").click(); | ||||
|   await clickButton; | ||||
|  | ||||
|   Assert.deepEqual( | ||||
|     await remoteClients.translationModels.resolvePendingDownloads( | ||||
|       spanishModels.length | ||||
|     ), | ||||
|     spanishModels, | ||||
|     "Spanish models were downloaded." | ||||
|   ); | ||||
|  | ||||
|   await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|     langEs, | ||||
|     "translations-settings-remove-icon", | ||||
|     "Delete icon is visible for Spanish language hence downloaded" | ||||
|   ); | ||||
|  | ||||
|   info("Download Ukrainian language model."); | ||||
|  | ||||
|   let langUk = Array.from(downloadLanguageList.querySelectorAll("label")).find( | ||||
|     el => el.getAttribute("value") === "uk" | ||||
|   ); | ||||
|  | ||||
|   clickButton = BrowserTestUtils.waitForEvent( | ||||
|     langUk.parentNode.querySelector("moz-button"), | ||||
|     "click" | ||||
|   ); | ||||
|   langUk.parentNode.querySelector("moz-button").click(); | ||||
|   await clickButton; | ||||
|  | ||||
|   Assert.deepEqual( | ||||
|     await remoteClients.translationModels.resolvePendingDownloads( | ||||
|       ukrainianModels.length | ||||
|     ), | ||||
|     ukrainianModels, | ||||
|     "Ukrainian models were downloaded." | ||||
|   ); | ||||
|  | ||||
|   await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|     langUk, | ||||
|     "translations-settings-remove-icon", | ||||
|     "Delete icon is visible for Ukranian language hence downloaded." | ||||
|   ); | ||||
|  | ||||
|   // Download "All languages" is the first child | ||||
|   let langAll = downloadLanguageList.children[0]; | ||||
|  | ||||
|   ok( | ||||
|     langAll | ||||
|       .querySelector("moz-button") | ||||
|       .classList.contains("translations-settings-remove-icon"), | ||||
|     "Delete icon is visible for All Languages after all individual language models were downloaded." | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Remove one language ensure that All Languages change state changes to 'removed' to indicate that all languages are not downloaded." | ||||
|   ); | ||||
|  | ||||
|   info("Remove Spanish language model."); | ||||
|   langEs.parentNode.querySelector("moz-button").click(); | ||||
|   await clickButton; | ||||
|  | ||||
|   await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|     langEs, | ||||
|     "translations-settings-download-icon", | ||||
|     "Download icon is visible for Spanish language hence removed" | ||||
|   ); | ||||
|  | ||||
|   ok( | ||||
|     langAll | ||||
|       .querySelector("moz-button") | ||||
|       .classList.contains("translations-settings-download-icon"), | ||||
|     "Download icon is visible for all languages i.e. all languages are not downloaded since one language, Spanish was removed." | ||||
|   ); | ||||
|  | ||||
|   await cleanup(); | ||||
| }); | ||||
| @@ -0,0 +1,173 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_task( | ||||
|   async function test_translations_settings_download_languages_error_handling() { | ||||
|     const { | ||||
|       cleanup, | ||||
|       remoteClients, | ||||
|       elements: { settingsButton }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|     }); | ||||
|  | ||||
|     const frenchModels = languageModelNames([ | ||||
|       { fromLang: "fr", toLang: "en" }, | ||||
|       { fromLang: "en", toLang: "fr" }, | ||||
|     ]); | ||||
|  | ||||
|     const spanishModels = languageModelNames([ | ||||
|       { fromLang: "es", toLang: "en" }, | ||||
|       { fromLang: "en", toLang: "es" }, | ||||
|     ]); | ||||
|  | ||||
|     const allModels = languageModelNames(LANGUAGE_PAIRS); | ||||
|  | ||||
|     assertVisibility({ | ||||
|       message: "Expect paneGeneral elements to be visible.", | ||||
|       visible: { settingsButton }, | ||||
|     }); | ||||
|  | ||||
|     info( | ||||
|       "Open translations settings page by clicking on translations settings button." | ||||
|     ); | ||||
|     const { downloadLanguageList } = | ||||
|       await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|         settingsButton | ||||
|       ); | ||||
|  | ||||
|     info("Test French language model for download error"); | ||||
|  | ||||
|     let langFr = Array.from( | ||||
|       downloadLanguageList.querySelectorAll("label") | ||||
|     ).find(el => el.getAttribute("value") === "fr"); | ||||
|  | ||||
|     let clickButton = BrowserTestUtils.waitForEvent( | ||||
|       langFr.parentNode.querySelector("moz-button"), | ||||
|       "click" | ||||
|     ); | ||||
|     langFr.parentNode.querySelector("moz-button").click(); | ||||
|     await clickButton; | ||||
|  | ||||
|     await captureTranslationsError(() => | ||||
|       remoteClients.translationModels.rejectPendingDownloads( | ||||
|         frenchModels.length | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     const errorElement = gBrowser.selectedBrowser.contentDocument.querySelector( | ||||
|       ".translations-settings-language-error" | ||||
|     ); | ||||
|  | ||||
|     assertVisibility({ | ||||
|       message: "Moz-message-bar with error message is visible", | ||||
|       visible: { errorElement }, | ||||
|     }); | ||||
|     is( | ||||
|       document.l10n.getAttributes(errorElement).id, | ||||
|       "translations-settings-language-download-error", | ||||
|       "Error message correctly shows download error" | ||||
|     ); | ||||
|     is( | ||||
|       document.l10n.getAttributes(errorElement).args.name, | ||||
|       "French", | ||||
|       "Error message correctly shows download error for French language" | ||||
|     ); | ||||
|  | ||||
|     await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|       langFr, | ||||
|       "translations-settings-download-icon", | ||||
|       "Download icon is visible on French button" | ||||
|     ); | ||||
|  | ||||
|     remoteClients.translationsWasm.assertNoNewDownloads(); | ||||
|  | ||||
|     info("Download Spanish language model successfully."); | ||||
|  | ||||
|     let langEs = Array.from( | ||||
|       downloadLanguageList.querySelectorAll("label") | ||||
|     ).find(el => el.getAttribute("value") === "es"); | ||||
|  | ||||
|     clickButton = BrowserTestUtils.waitForEvent( | ||||
|       langEs.parentNode.querySelector("moz-button"), | ||||
|       "click" | ||||
|     ); | ||||
|     langEs.parentNode.querySelector("moz-button").click(); | ||||
|     await clickButton; | ||||
|  | ||||
|     const errorElementEs = | ||||
|       gBrowser.selectedBrowser.contentDocument.querySelector( | ||||
|         ".translations-settings-language-error" | ||||
|       ); | ||||
|  | ||||
|     ok( | ||||
|       !errorElementEs, | ||||
|       "Previous error is remove when new action occured, i.e. click download Spanish button" | ||||
|     ); | ||||
|  | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationModels.resolvePendingDownloads( | ||||
|         spanishModels.length | ||||
|       ), | ||||
|       spanishModels, | ||||
|       "Spanish models were downloaded." | ||||
|     ); | ||||
|  | ||||
|     await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|       langEs, | ||||
|       "translations-settings-remove-icon", | ||||
|       "Delete icon is visible for Spanish language hence downloaded" | ||||
|     ); | ||||
|  | ||||
|     info("Test All language models download error"); | ||||
|     // Download "All languages" is the first child | ||||
|     let langAll = downloadLanguageList.children[0]; | ||||
|  | ||||
|     let clickButtonAll = BrowserTestUtils.waitForEvent( | ||||
|       langAll.querySelector("moz-button"), | ||||
|       "click" | ||||
|     ); | ||||
|     langAll.querySelector("moz-button").click(); | ||||
|     await clickButtonAll; | ||||
|  | ||||
|     await captureTranslationsError(() => | ||||
|       remoteClients.translationModels.rejectPendingDownloads(allModels.length) | ||||
|     ); | ||||
|  | ||||
|     await captureTranslationsError(() => | ||||
|       remoteClients.translationsWasm.rejectPendingDownloads(allModels.length) | ||||
|     ); | ||||
|  | ||||
|     remoteClients.translationsWasm.assertNoNewDownloads(); | ||||
|  | ||||
|     await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|       langAll, | ||||
|       "translations-settings-download-icon", | ||||
|       "Download icon is visible for 'all languages'" | ||||
|     ); | ||||
|  | ||||
|     const errorElementAll = | ||||
|       gBrowser.selectedBrowser.contentDocument.querySelector( | ||||
|         ".translations-settings-language-error" | ||||
|       ); | ||||
|  | ||||
|     assertVisibility({ | ||||
|       message: "Moz-message-bar with error message is visible", | ||||
|       visible: { errorElementAll }, | ||||
|     }); | ||||
|     is( | ||||
|       document.l10n.getAttributes(errorElementAll).id, | ||||
|       "translations-settings-language-download-error", | ||||
|       "Error message correctly shows download error" | ||||
|     ); | ||||
|     is( | ||||
|       document.l10n.getAttributes(errorElementAll).args.name, | ||||
|       "all", | ||||
|       "Error message correctly shows download error for all language" | ||||
|     ); | ||||
|  | ||||
|     await cleanup(); | ||||
|   } | ||||
| ); | ||||
| @@ -0,0 +1,117 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function test_translations_settings_download_languages() { | ||||
|   await testWithAndWithoutLexicalShortlist(async lexicalShortlistPrefs => { | ||||
|     const { | ||||
|       cleanup, | ||||
|       remoteClients, | ||||
|       elements: { settingsButton }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [ | ||||
|         ["browser.translations.newSettingsUI.enable", true], | ||||
|         ...lexicalShortlistPrefs, | ||||
|       ], | ||||
|     }); | ||||
|  | ||||
|     assertVisibility({ | ||||
|       message: "Expect paneGeneral elements to be visible.", | ||||
|       visible: { settingsButton }, | ||||
|     }); | ||||
|  | ||||
|     info( | ||||
|       "Open translations settings page by clicking on translations settings button." | ||||
|     ); | ||||
|     const { downloadLanguageList } = | ||||
|       await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|         settingsButton | ||||
|       ); | ||||
|  | ||||
|     info("Test French language model install and uninstall function."); | ||||
|  | ||||
|     let langFr = Array.from( | ||||
|       downloadLanguageList.querySelectorAll("label") | ||||
|     ).find(el => el.getAttribute("value") === "fr"); | ||||
|  | ||||
|     let clickButton = BrowserTestUtils.waitForEvent( | ||||
|       langFr.parentNode.querySelector("moz-button"), | ||||
|       "click" | ||||
|     ); | ||||
|     langFr.parentNode.querySelector("moz-button").click(); | ||||
|     await clickButton; | ||||
|  | ||||
|     const frenchModels = languageModelNames([ | ||||
|       { fromLang: "fr", toLang: "en" }, | ||||
|       { fromLang: "en", toLang: "fr" }, | ||||
|     ]); | ||||
|  | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationModels.resolvePendingDownloads( | ||||
|         frenchModels.length | ||||
|       ), | ||||
|       frenchModels, | ||||
|       "French models were downloaded." | ||||
|     ); | ||||
|  | ||||
|     await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|       langFr, | ||||
|       "translations-settings-remove-icon", | ||||
|       "Delete icon is visible on French button." | ||||
|     ); | ||||
|  | ||||
|     langFr.parentNode.querySelector("moz-button").click(); | ||||
|     await clickButton; | ||||
|  | ||||
|     await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|       langFr, | ||||
|       "translations-settings-download-icon", | ||||
|       "Download icon is visible on French Button." | ||||
|     ); | ||||
|  | ||||
|     info("Test 'All language' models install and uninstall function"); | ||||
|  | ||||
|     // Download "All languages" is the first child | ||||
|     let langAll = downloadLanguageList.children[0]; | ||||
|  | ||||
|     let clickButtonAll = BrowserTestUtils.waitForEvent( | ||||
|       langAll.querySelector("moz-button"), | ||||
|       "click" | ||||
|     ); | ||||
|     langAll.querySelector("moz-button").click(); | ||||
|     await clickButtonAll; | ||||
|  | ||||
|     const allModels = languageModelNames(LANGUAGE_PAIRS); | ||||
|  | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationModels.resolvePendingDownloads( | ||||
|         allModels.length | ||||
|       ), | ||||
|       allModels, | ||||
|       "All models were downloaded." | ||||
|     ); | ||||
|     Assert.deepEqual( | ||||
|       await remoteClients.translationsWasm.resolvePendingDownloads(1), | ||||
|       ["bergamot-translator"], | ||||
|       "Wasm was downloaded." | ||||
|     ); | ||||
|  | ||||
|     await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|       langAll, | ||||
|       "translations-settings-remove-icon", | ||||
|       "Delete icon is visible on 'All languages' button" | ||||
|     ); | ||||
|  | ||||
|     langAll.querySelector("moz-button").click(); | ||||
|     await clickButton; | ||||
|  | ||||
|     await TranslationsSettingsTestUtils.downaloadButtonClick( | ||||
|       langAll, | ||||
|       "translations-settings-download-icon", | ||||
|       "Download icon is visible on 'All Language' button." | ||||
|     ); | ||||
|  | ||||
|     await cleanup(); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,89 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_task( | ||||
|   async function test_about_preferences_never_translate_language_settings() { | ||||
|     const { | ||||
|       cleanup, | ||||
|       elements: { settingsButton }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [["browser.translations.newSettingsUI.enable", false]], | ||||
|     }); | ||||
|  | ||||
|     info("Ensuring the list of never-translate languages is empty"); | ||||
|     is( | ||||
|       getNeverTranslateLanguagesFromPref().length, | ||||
|       0, | ||||
|       "The list of never-translate languages is empty" | ||||
|     ); | ||||
|  | ||||
|     info("Adding two languages to the neverTranslateLanguages pref"); | ||||
|     Services.prefs.setCharPref(NEVER_TRANSLATE_LANGS_PREF, "fr,de"); | ||||
|  | ||||
|     const dialogWindow = await waitForOpenDialogWindow( | ||||
|       "chrome://browser/content/preferences/dialogs/translations.xhtml", | ||||
|       () => { | ||||
|         click( | ||||
|           settingsButton, | ||||
|           "Opening the about:preferences Translations Settings" | ||||
|         ); | ||||
|       } | ||||
|     ); | ||||
|     let tree = dialogWindow.document.getElementById( | ||||
|       "neverTranslateLanguagesTree" | ||||
|     ); | ||||
|     let remove = dialogWindow.document.getElementById( | ||||
|       "removeNeverTranslateLanguage" | ||||
|     ); | ||||
|     let removeAll = dialogWindow.document.getElementById( | ||||
|       "removeAllNeverTranslateLanguages" | ||||
|     ); | ||||
|  | ||||
|     is(tree.view.rowCount, 2, "The never-translate languages list has 2 items"); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled"); | ||||
|  | ||||
|     info("Selecting the first never-translate language."); | ||||
|     tree.view.selection.select(0); | ||||
|     ok(!remove.disabled, "The 'Remove Language' button is enabled"); | ||||
|  | ||||
|     click(remove, "Clicking the remove-language button"); | ||||
|     is( | ||||
|       tree.view.rowCount, | ||||
|       1, | ||||
|       "The never-translate languages list now contains 1 item" | ||||
|     ); | ||||
|     is( | ||||
|       getNeverTranslateLanguagesFromPref().length, | ||||
|       1, | ||||
|       "One language tag in the pref" | ||||
|     ); | ||||
|  | ||||
|     info("Removing all languages from the neverTranslateLanguages pref"); | ||||
|     Services.prefs.setCharPref(NEVER_TRANSLATE_LANGS_PREF, ""); | ||||
|     is(tree.view.rowCount, 0, "The never-translate languages list is empty"); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(removeAll.disabled, "The 'Remove All Languages' button is disabled"); | ||||
|  | ||||
|     info("Adding more languages to the neverTranslateLanguages pref"); | ||||
|     Services.prefs.setCharPref(NEVER_TRANSLATE_LANGS_PREF, "fr,en,es"); | ||||
|     is(tree.view.rowCount, 3, "The never-translate languages list has 3 items"); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled"); | ||||
|  | ||||
|     click(removeAll, "Clicking the remove-all languages button"); | ||||
|     is(tree.view.rowCount, 0, "The never-translate languages list is empty"); | ||||
|     ok(remove.disabled, "The 'Remove Language' button is disabled"); | ||||
|     ok(removeAll.disabled, "The 'Remove All Languages' button is disabled"); | ||||
|     is( | ||||
|       getNeverTranslateLanguagesFromPref().length, | ||||
|       0, | ||||
|       "There are no languages in the neverTranslateLanguages pref" | ||||
|     ); | ||||
|  | ||||
|     await waitForCloseDialogWindow(dialogWindow); | ||||
|     await cleanup(); | ||||
|   } | ||||
| ); | ||||
| @@ -0,0 +1,124 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| const { PermissionTestUtils } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/PermissionTestUtils.sys.mjs" | ||||
| ); | ||||
|  | ||||
| add_task(async function test_about_preferences_never_translate_site_settings() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", false]], | ||||
|     permissionsUrls: [ | ||||
|       "https://example.com", | ||||
|       "https://example.org", | ||||
|       "https://example.net", | ||||
|     ], | ||||
|   }); | ||||
|  | ||||
|   info("Ensuring the list of never-translate sites is empty"); | ||||
|   is( | ||||
|     getNeverTranslateSitesFromPerms().length, | ||||
|     0, | ||||
|     "The list of never-translate sites is empty" | ||||
|   ); | ||||
|  | ||||
|   info("Adding two sites to the neverTranslateSites perms"); | ||||
|   PermissionTestUtils.add( | ||||
|     "https://example.com", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|   PermissionTestUtils.add( | ||||
|     "https://example.org", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|   PermissionTestUtils.add( | ||||
|     "https://example.net", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|  | ||||
|   const dialogWindow = await waitForOpenDialogWindow( | ||||
|     "chrome://browser/content/preferences/dialogs/translations.xhtml", | ||||
|     () => { | ||||
|       click( | ||||
|         settingsButton, | ||||
|         "Opening the about:preferences Translations Settings" | ||||
|       ); | ||||
|     } | ||||
|   ); | ||||
|   let tree = dialogWindow.document.getElementById("neverTranslateSitesTree"); | ||||
|   let remove = dialogWindow.document.getElementById("removeNeverTranslateSite"); | ||||
|   let removeAll = dialogWindow.document.getElementById( | ||||
|     "removeAllNeverTranslateSites" | ||||
|   ); | ||||
|  | ||||
|   is(tree.view.rowCount, 3, "The never-translate sites list has 2 items"); | ||||
|   ok(remove.disabled, "The 'Remove Site' button is disabled"); | ||||
|   ok(!removeAll.disabled, "The 'Remove All Sites' button is enabled"); | ||||
|  | ||||
|   info("Selecting the first never-translate site."); | ||||
|   tree.view.selection.select(0); | ||||
|   ok(!remove.disabled, "The 'Remove Site' button is enabled"); | ||||
|  | ||||
|   click(remove, "Clicking the remove-site button"); | ||||
|   is( | ||||
|     tree.view.rowCount, | ||||
|     2, | ||||
|     "The never-translate sites list now contains 2 items" | ||||
|   ); | ||||
|   is( | ||||
|     getNeverTranslateSitesFromPerms().length, | ||||
|     2, | ||||
|     "There are 2 sites with permissions" | ||||
|   ); | ||||
|  | ||||
|   info("Removing all sites from the neverTranslateSites perms"); | ||||
|   PermissionTestUtils.remove("https://example.com", TRANSLATIONS_PERMISSION); | ||||
|   PermissionTestUtils.remove("https://example.org", TRANSLATIONS_PERMISSION); | ||||
|   PermissionTestUtils.remove("https://example.net", TRANSLATIONS_PERMISSION); | ||||
|  | ||||
|   is(tree.view.rowCount, 0, "The never-translate sites list is empty"); | ||||
|   ok(remove.disabled, "The 'Remove Site' button is disabled"); | ||||
|   ok(removeAll.disabled, "The 'Remove All Sites' button is disabled"); | ||||
|  | ||||
|   info("Adding more sites to the neverTranslateSites perms"); | ||||
|   PermissionTestUtils.add( | ||||
|     "https://example.org", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|   PermissionTestUtils.add( | ||||
|     "https://example.com", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|   PermissionTestUtils.add( | ||||
|     "https://example.net", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|  | ||||
|   is(tree.view.rowCount, 3, "The never-translate sites list has 3 items"); | ||||
|   ok(remove.disabled, "The 'Remove Site' button is disabled"); | ||||
|   ok(!removeAll.disabled, "The 'Remove All Sites' button is enabled"); | ||||
|  | ||||
|   click(removeAll, "Clicking the remove-all sites button"); | ||||
|   is(tree.view.rowCount, 0, "The never-translate sites list is empty"); | ||||
|   ok(remove.disabled, "The 'Remove Site' button is disabled"); | ||||
|   ok(removeAll.disabled, "The 'Remove All Sites' button is disabled"); | ||||
|   is( | ||||
|     getNeverTranslateSitesFromPerms().length, | ||||
|     0, | ||||
|     "There are no sites in the neverTranslateSites perms" | ||||
|   ); | ||||
|  | ||||
|   await waitForCloseDialogWindow(dialogWindow); | ||||
|   await cleanup(); | ||||
| }); | ||||
| @@ -0,0 +1,462 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| add_task(async function test_translations_settings_pane_elements() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|   }); | ||||
|  | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneGeneral elements to be visible.", | ||||
|     visible: { settingsButton }, | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "Open translations settings page by clicking on translations settings button." | ||||
|   ); | ||||
|   const { | ||||
|     translationsSettingsBackButton, | ||||
|     translationsSettingsHeader, | ||||
|     translationsSettingsDescription, | ||||
|     translateAlwaysHeader, | ||||
|     translateNeverHeader, | ||||
|     alwaysTranslateMenuList, | ||||
|     neverTranslateMenuList, | ||||
|     translateNeverSiteHeader, | ||||
|     translateNeverSiteDesc, | ||||
|     downloadLanguageSection, | ||||
|     translateDownloadLanguagesLearnMore, | ||||
|   } = | ||||
|     await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|       settingsButton | ||||
|     ); | ||||
|  | ||||
|   const translateDownloadLanguagesHeader = | ||||
|     downloadLanguageSection.querySelector("h2"); | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneTranslations elements to be visible.", | ||||
|     visible: { | ||||
|       translationsSettingsBackButton, | ||||
|       translationsSettingsHeader, | ||||
|       translationsSettingsDescription, | ||||
|       translateAlwaysHeader, | ||||
|       translateNeverHeader, | ||||
|       alwaysTranslateMenuList, | ||||
|       neverTranslateMenuList, | ||||
|       translateNeverSiteHeader, | ||||
|       translateNeverSiteDesc, | ||||
|       translateDownloadLanguagesLearnMore, | ||||
|     }, | ||||
|     hidden: { | ||||
|       settingsButton, | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "In translations settings page, click on back button to go back to main preferences page." | ||||
|   ); | ||||
|   const paneEvent = BrowserTestUtils.waitForEvent( | ||||
|     document, | ||||
|     "paneshown", | ||||
|     false, | ||||
|     event => event.detail.category === "paneGeneral" | ||||
|   ); | ||||
|  | ||||
|   click(translationsSettingsBackButton); | ||||
|   await paneEvent; | ||||
|  | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneGeneral elements to be visible.", | ||||
|     visible: { | ||||
|       settingsButton, | ||||
|     }, | ||||
|     hidden: { | ||||
|       translationsSettingsBackButton, | ||||
|       translationsSettingsHeader, | ||||
|       translationsSettingsDescription, | ||||
|       translateAlwaysHeader, | ||||
|       translateNeverHeader, | ||||
|       alwaysTranslateMenuList, | ||||
|       neverTranslateMenuList, | ||||
|       translateNeverSiteHeader, | ||||
|       translateNeverSiteDesc, | ||||
|       translateDownloadLanguagesHeader, | ||||
|       translateDownloadLanguagesLearnMore, | ||||
|     }, | ||||
|   }); | ||||
|   await cleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_translations_settings_always_translate() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|   }); | ||||
|  | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneGeneral elements to be visible.", | ||||
|     visible: { settingsButton }, | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "Open translations settings page by clicking on translations settings button." | ||||
|   ); | ||||
|   const { | ||||
|     alwaysTranslateMenuList, | ||||
|     alwaysTranslateLanguageList, | ||||
|     alwaysTranslateMenuPopup, | ||||
|   } = | ||||
|     await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|       settingsButton | ||||
|     ); | ||||
|  | ||||
|   info("Testing the Always translate langauge settings"); | ||||
|   await testLanguageList( | ||||
|     alwaysTranslateLanguageList, | ||||
|     alwaysTranslateMenuList, | ||||
|     alwaysTranslateMenuPopup, | ||||
|     ALWAYS_TRANSLATE_LANGS_PREF, | ||||
|     "Always" | ||||
|   ); | ||||
|   await testLanguageListWithPref( | ||||
|     alwaysTranslateLanguageList, | ||||
|     ALWAYS_TRANSLATE_LANGS_PREF, | ||||
|     "Always" | ||||
|   ); | ||||
|  | ||||
|   await cleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_translations_settings_never_translate() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "Open translations settings page by clicking on translations settings button." | ||||
|   ); | ||||
|  | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneGeneral elements to be visible.", | ||||
|     visible: { settingsButton }, | ||||
|   }); | ||||
|  | ||||
|   const { | ||||
|     neverTranslateMenuList, | ||||
|     neverTranslateLanguageList, | ||||
|     neverTranslateMenuPopup, | ||||
|   } = | ||||
|     await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|       settingsButton | ||||
|     ); | ||||
|  | ||||
|   info("Testing the Never translate langauge settings"); | ||||
|   await testLanguageList( | ||||
|     neverTranslateLanguageList, | ||||
|     neverTranslateMenuList, | ||||
|     neverTranslateMenuPopup, | ||||
|     NEVER_TRANSLATE_LANGS_PREF, | ||||
|     "Never" | ||||
|   ); | ||||
|   await testLanguageListWithPref( | ||||
|     neverTranslateLanguageList, | ||||
|     NEVER_TRANSLATE_LANGS_PREF, | ||||
|     "Never" | ||||
|   ); | ||||
|   await cleanup(); | ||||
| }); | ||||
| function getLangsFromPref(pref) { | ||||
|   let rawLangs = Services.prefs.getCharPref(pref); | ||||
|   if (!rawLangs) { | ||||
|     return []; | ||||
|   } | ||||
|   let langArr = rawLangs.split(","); | ||||
|   return langArr; | ||||
| } | ||||
|  | ||||
| async function testLanguageList( | ||||
|   languageList, | ||||
|   menuList, | ||||
|   menuPopup, | ||||
|   pref, | ||||
|   sectionName | ||||
| ) { | ||||
|   info("Ensure the Always/Never list is empty initially."); | ||||
|  | ||||
|   is( | ||||
|     languageList.childElementCount, | ||||
|     0, | ||||
|     `Language list empty in ${sectionName} Translate list` | ||||
|   ); | ||||
|  | ||||
|   const menuItems = menuPopup.children; | ||||
|  | ||||
|   info( | ||||
|     "Click each language on the menulist to add it into the Always/Never list." | ||||
|   ); | ||||
|   for (const menuItem of menuItems) { | ||||
|     menuList.open = true; | ||||
|  | ||||
|     let clickMenu = BrowserTestUtils.waitForEvent( | ||||
|       menuList.querySelector("menupopup"), | ||||
|       "popuphidden" | ||||
|     ); | ||||
|     click(menuItem); | ||||
|     menuList.querySelector("menupopup").hidePopup(); | ||||
|     await clickMenu; | ||||
|  | ||||
|     /** Languages are always added on the top, so check the firstChild | ||||
|      * for newly added languages. | ||||
|      * the firstChild.querySelector("label").innerText is the language display name | ||||
|      * which is compared with the menulist display name that is selected | ||||
|      */ | ||||
|     let langElem = languageList.firstElementChild; | ||||
|     const displayName = getIntlDisplayName(menuItem.value); | ||||
|     is( | ||||
|       langElem.querySelector("label").innerText, | ||||
|       displayName, | ||||
|       `Language list has element ${displayName}` | ||||
|     ); | ||||
|  | ||||
|     const langTag = langElem.querySelector("label").getAttribute("value"); | ||||
|     ok( | ||||
|       getLangsFromPref(pref).includes(langTag), | ||||
|       `Perferences contains ${langTag}` | ||||
|     ); | ||||
|   } | ||||
|   /** The test cases has 4 languages, so check if 4 languages are added to the list */ | ||||
|   let langNum = languageList.childElementCount; | ||||
|   is(langNum, 4, "Number of languages added is 4"); | ||||
|  | ||||
|   info( | ||||
|     "Remove each language from the Always/Never list that we added initially." | ||||
|   ); | ||||
|   for (let i = 0; i < langNum; i++) { | ||||
|     // Delete the first language in the list | ||||
|     let langElem = languageList.children[0]; | ||||
|     let langName = langElem.querySelector("label").innerText; | ||||
|     const langTag = langElem.querySelector("label").getAttribute("value"); | ||||
|     let langButton = langElem.querySelector("moz-button"); | ||||
|     let clickButton = BrowserTestUtils.waitForEvent(langButton, "click"); | ||||
|     langButton.click(); | ||||
|     await clickButton; | ||||
|  | ||||
|     ok( | ||||
|       !getLangsFromPref(pref).includes(langTag), | ||||
|       `Perferences does not contain ${langTag}` | ||||
|     ); | ||||
|  | ||||
|     if (i < langNum) { | ||||
|       is( | ||||
|         languageList.childElementCount, | ||||
|         langNum - i - 1, | ||||
|         `${langName} removed from ${sectionName}  Translate` | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function testLanguageListWithPref(languageList, pref, sectionName) { | ||||
|   const langs = [ | ||||
|     "fr", | ||||
|     "de", | ||||
|     "en", | ||||
|     "es", | ||||
|     "fr,de", | ||||
|     "fr,en", | ||||
|     "fr,es", | ||||
|     "de,en", | ||||
|     "de,en,es", | ||||
|     "es,fr,en", | ||||
|     "en,es,fr,de", | ||||
|   ]; | ||||
|  | ||||
|   info("Ensure the Always/Never list is empty initially."); | ||||
|  | ||||
|   is( | ||||
|     languageList.childElementCount, | ||||
|     0, | ||||
|     `Language list is empty in ${sectionName} Translate list` | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Add languages to the Always/Never list in translations setting by setting the ALWAYS_TRANSLATE_LANGS_PREF/NEVER_TRANSLATE_LANGS_PREF." | ||||
|   ); | ||||
|  | ||||
|   for (const langOptions of langs) { | ||||
|     Services.prefs.setCharPref(pref, langOptions); | ||||
|  | ||||
|     /** Languages are always added on the top, so check the firstChild | ||||
|      * for newly added languages. | ||||
|      * the firstChild.querySelector("label").innerText is the language display name | ||||
|      * which is compared with the menulist display name that is selected | ||||
|      */ | ||||
|  | ||||
|     const langsAdded = langOptions.split(","); | ||||
|     is( | ||||
|       languageList.childElementCount, | ||||
|       langsAdded.length, | ||||
|       `Language list has ${langsAdded.length} elements ` | ||||
|     ); | ||||
|  | ||||
|     let langsAddedHtml = Array.from(languageList.querySelectorAll("label")); | ||||
|  | ||||
|     for (const lang of langsAdded) { | ||||
|       const langFind = langsAddedHtml | ||||
|         .find(el => el.getAttribute("value") === lang) | ||||
|         .getAttribute("value"); | ||||
|       is(langFind, lang, `Language list has element ${lang}`); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Services.prefs.setCharPref(pref, ""); | ||||
|   is( | ||||
|     languageList.childElementCount, | ||||
|     0, | ||||
|     `All removed from ${sectionName} Translate` | ||||
|   ); | ||||
| } | ||||
|  | ||||
| const { PermissionTestUtils } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/PermissionTestUtils.sys.mjs" | ||||
| ); | ||||
|  | ||||
| add_task(async function test_translations_settings_never_translate_site() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "Open translations settings page by clicking on translations settings button." | ||||
|   ); | ||||
|   const { neverTranslateSiteList } = | ||||
|     await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|       settingsButton | ||||
|     ); | ||||
|  | ||||
|   info("Ensuring the list of never-translate sites is empty"); | ||||
|   is( | ||||
|     getNeverTranslateSitesFromPerms().length, | ||||
|     0, | ||||
|     "The list of never-translate sites is empty" | ||||
|   ); | ||||
|  | ||||
|   is( | ||||
|     neverTranslateSiteList.childElementCount, | ||||
|     0, | ||||
|     "The never-translate sites html list is empty" | ||||
|   ); | ||||
|  | ||||
|   info("Adding sites to the neverTranslateSites perms"); | ||||
|   await PermissionTestUtils.add( | ||||
|     "https://example.com", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|   await PermissionTestUtils.add( | ||||
|     "https://example.org", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|   await PermissionTestUtils.add( | ||||
|     "https://example.net", | ||||
|     TRANSLATIONS_PERMISSION, | ||||
|     Services.perms.DENY_ACTION | ||||
|   ); | ||||
|  | ||||
|   is( | ||||
|     getNeverTranslateSitesFromPerms().length, | ||||
|     3, | ||||
|     "The list of never-translate sites has 3 elements" | ||||
|   ); | ||||
|  | ||||
|   is( | ||||
|     neverTranslateSiteList.childElementCount, | ||||
|     3, | ||||
|     "The never-translate sites html list has 3 elements" | ||||
|   ); | ||||
|  | ||||
|   const permissionsUrls = [ | ||||
|     "https://example.com", | ||||
|     "https://example.org", | ||||
|     "https://example.net", | ||||
|   ]; | ||||
|  | ||||
|   info( | ||||
|     "Ensure that the Never translate sites in permissions settings are reflected in Never translate sites section of translations settings page" | ||||
|   ); | ||||
|  | ||||
|   const siteNum = neverTranslateSiteList.children.length; | ||||
|   for (let i = siteNum; i > 0; i--) { | ||||
|     is( | ||||
|       neverTranslateSiteList.children[i - 1].querySelector("label").textContent, | ||||
|       permissionsUrls[permissionsUrls.length - i], | ||||
|       `Never translate URL ${ | ||||
|         permissionsUrls[permissionsUrls.length - i] | ||||
|       } is added` | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   info( | ||||
|     "Delete each site by clicking the button in Never translate sites section of translations settings page and check if it is removed in the Never translate sites in permissions settings" | ||||
|   ); | ||||
|   for (let i = 0; i < siteNum; i++) { | ||||
|     // Delete the first site in the list | ||||
|     let siteElem = neverTranslateSiteList.children[0]; | ||||
|     // Delete the first language in the list | ||||
|     let siteName = siteElem.querySelector("label").innerText; | ||||
|     let siteButton = siteElem.querySelector("moz-button"); | ||||
|  | ||||
|     ok( | ||||
|       neverTranslateSiteList.querySelector(`label[value="${siteName}"]`), | ||||
|       `Site ${siteName} present in the Never transalate site list` | ||||
|     ); | ||||
|  | ||||
|     ok( | ||||
|       getNeverTranslateSitesFromPerms().find(p => p.origin === siteName), | ||||
|       `Site ${siteName} present in the Never transalate site permissions list` | ||||
|     ); | ||||
|  | ||||
|     let clickButton = BrowserTestUtils.waitForEvent(siteButton, "click"); | ||||
|     siteButton.click(); | ||||
|     await clickButton; | ||||
|  | ||||
|     ok( | ||||
|       !neverTranslateSiteList.querySelector(`label[value="${siteName}"]`), | ||||
|       `Site ${siteName} removed successfully from the Never transalate site list` | ||||
|     ); | ||||
|  | ||||
|     ok( | ||||
|       !getNeverTranslateSitesFromPerms().find(p => p.origin === siteName), | ||||
|       `Site ${siteName} removed from successfully from the Never transalate site permissions list` | ||||
|     ); | ||||
|  | ||||
|     if (i < siteNum) { | ||||
|       is( | ||||
|         neverTranslateSiteList.childElementCount, | ||||
|         siteNum - i - 1, | ||||
|         `${siteName} removed from Never Translate Site` | ||||
|       ); | ||||
|     } | ||||
|     const siteLen = siteNum - i - 1; | ||||
|     is( | ||||
|       getNeverTranslateSitesFromPerms().length, | ||||
|       siteLen, | ||||
|       `There are ${siteLen} site in Never translate site` | ||||
|     ); | ||||
|   } | ||||
|   await cleanup(); | ||||
| }); | ||||
| @@ -0,0 +1,635 @@ | ||||
| /* Any copyright is dedicated to the Public Domain. | ||||
|    http://creativecommons.org/publicdomain/zero/1.0/ */ | ||||
|  | ||||
| "use strict"; | ||||
|  | ||||
| const { PermissionTestUtils } = ChromeUtils.importESModule( | ||||
|   "resource://testing-common/PermissionTestUtils.sys.mjs" | ||||
| ); | ||||
|  | ||||
| add_task(async function test_translations_settings_keyboard_a11y() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "Open translations settings page by clicking on translations settings button." | ||||
|   ); | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneGeneral elements to be visible.", | ||||
|     visible: { settingsButton }, | ||||
|   }); | ||||
|  | ||||
|   const { | ||||
|     translationsSettingsBackButton, | ||||
|     alwaysTranslateMenuList, | ||||
|     neverTranslateMenuList, | ||||
|     translateDownloadLanguagesLearnMore, | ||||
|     downloadLanguageList, | ||||
|   } = | ||||
|     await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|       settingsButton | ||||
|     ); | ||||
|  | ||||
|   const document = gBrowser.selectedBrowser.contentDocument; | ||||
|  | ||||
|   info("Press the Tab key to focus the first page element, the back button"); | ||||
|  | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     translationsSettingsBackButton.id, | ||||
|     "Key is focused on back button" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Always Translate Menulist button" | ||||
|   ); | ||||
|  | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     alwaysTranslateMenuList.id, | ||||
|     "Key is focused on Always Translate Menulist button" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Never Translate Menulist button" | ||||
|   ); | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     neverTranslateMenuList.id, | ||||
|     "Key is focused on Never Translate Menulist button" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Download Languages' Learn More link" | ||||
|   ); | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     translateDownloadLanguagesLearnMore.id, | ||||
|     "Key is focused on Download Languages' Learn More link" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Download Languages list section" | ||||
|   ); | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     downloadLanguageList.id, | ||||
|     "Key is focused on Download Languages list section" | ||||
|   ); | ||||
|  | ||||
|   await cleanup(); | ||||
| }); | ||||
|  | ||||
| add_task(async function test_translations_settings_keyboard_download_a11y() { | ||||
|   const { | ||||
|     cleanup, | ||||
|     elements: { settingsButton }, | ||||
|   } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|     prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|   }); | ||||
|  | ||||
|   info( | ||||
|     "Open translations settings page by clicking on translations settings button." | ||||
|   ); | ||||
|   assertVisibility({ | ||||
|     message: "Expect paneGeneral elements to be visible.", | ||||
|     visible: { settingsButton }, | ||||
|   }); | ||||
|  | ||||
|   const { | ||||
|     translationsSettingsBackButton, | ||||
|     alwaysTranslateMenuList, | ||||
|     neverTranslateMenuList, | ||||
|     translateDownloadLanguagesLearnMore, | ||||
|     downloadLanguageList, | ||||
|   } = | ||||
|     await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|       settingsButton | ||||
|     ); | ||||
|  | ||||
|   const document = gBrowser.selectedBrowser.contentDocument; | ||||
|  | ||||
|   info("Press the Tab key to focus the first page element, the back button"); | ||||
|  | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     translationsSettingsBackButton.id, | ||||
|     "Key is focused on back button" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Always Translate Menulist button" | ||||
|   ); | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     alwaysTranslateMenuList.id, | ||||
|     "Key is focused on Always Translate Menulist button" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Never Translate Menulist button" | ||||
|   ); | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     neverTranslateMenuList.id, | ||||
|     "Key is focused on Never Translate Menulist button" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Download Languages' Learn More link" | ||||
|   ); | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     translateDownloadLanguagesLearnMore.id, | ||||
|     "Key is focused on Download Languages' Learn More link" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Tab key to focus the next page element, the Download Languages list section" | ||||
|   ); | ||||
|   EventUtils.synthesizeKey("KEY_Tab"); | ||||
|   is( | ||||
|     document.activeElement.id, | ||||
|     downloadLanguageList.id, | ||||
|     "Key is focused on Download Languages list section" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Arrow Down key to focus the first language element in the Download List Section" | ||||
|   ); | ||||
|  | ||||
|   for (let element of downloadLanguageList.children) { | ||||
|     info( | ||||
|       "Press the Arrow Down key to focus the next language element in the Download List Section" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|     is( | ||||
|       document.activeElement.parentNode.id, | ||||
|       element.id, | ||||
|       "Key is focused on the language " + | ||||
|         element.querySelector("label").textContent + | ||||
|         " within the language list" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   is( | ||||
|     document.activeElement.parentNode.id, | ||||
|     downloadLanguageList.lastElementChild.id, | ||||
|     "Key is focused on the last language " + | ||||
|       downloadLanguageList.lastElementChild.querySelector("label").textContent + | ||||
|       " within the language list" | ||||
|   ); | ||||
|  | ||||
|   info( | ||||
|     "Press the Arrow up key to focus the previous language element in the Download List Section" | ||||
|   ); | ||||
|   for (let i = downloadLanguageList.children.length - 2; i >= 0; i--) { | ||||
|     info( | ||||
|       "Press the Arrow up key to focus the previous language element in the Download List Section" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_ArrowUp"); | ||||
|     is( | ||||
|       document.activeElement.parentNode.id, | ||||
|       downloadLanguageList.children[i].id, | ||||
|       "Key is focused on the language " + | ||||
|         downloadLanguageList.children[i].querySelector("label").textContent + | ||||
|         " within the language list" | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   is( | ||||
|     document.activeElement.parentNode.id, | ||||
|     downloadLanguageList.firstElementChild.id, | ||||
|     "Key is focused on the first language within the language list" | ||||
|   ); | ||||
|  | ||||
|   await cleanup(); | ||||
| }); | ||||
|  | ||||
| add_task( | ||||
|   async function test_translations_settings_keyboard_never_translate_site_a11y() { | ||||
|     const { | ||||
|       cleanup, | ||||
|       elements: { settingsButton }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|     }); | ||||
|  | ||||
|     info( | ||||
|       "Open translations settings page by clicking on translations settings button." | ||||
|     ); | ||||
|     assertVisibility({ | ||||
|       message: "Expect paneGeneral elements to be visible.", | ||||
|       visible: { settingsButton }, | ||||
|     }); | ||||
|  | ||||
|     const { | ||||
|       translationsSettingsBackButton, | ||||
|       alwaysTranslateMenuList, | ||||
|       neverTranslateMenuList, | ||||
|       neverTranslateSiteList, | ||||
|       translateDownloadLanguagesLearnMore, | ||||
|     } = | ||||
|       await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|         settingsButton | ||||
|       ); | ||||
|  | ||||
|     is( | ||||
|       neverTranslateSiteList.childElementCount, | ||||
|       0, | ||||
|       "The never-translate sites html list is empty" | ||||
|     ); | ||||
|  | ||||
|     info("Adding sites to the neverTranslateSites perms"); | ||||
|     await PermissionTestUtils.add( | ||||
|       "https://example.com", | ||||
|       TRANSLATIONS_PERMISSION, | ||||
|       Services.perms.DENY_ACTION | ||||
|     ); | ||||
|     await PermissionTestUtils.add( | ||||
|       "https://example.org", | ||||
|       TRANSLATIONS_PERMISSION, | ||||
|       Services.perms.DENY_ACTION | ||||
|     ); | ||||
|     await PermissionTestUtils.add( | ||||
|       "https://example.net", | ||||
|       TRANSLATIONS_PERMISSION, | ||||
|       Services.perms.DENY_ACTION | ||||
|     ); | ||||
|  | ||||
|     is( | ||||
|       getNeverTranslateSitesFromPerms().length, | ||||
|       3, | ||||
|       "The list of never-translate sites has 3 elements" | ||||
|     ); | ||||
|  | ||||
|     const document = gBrowser.selectedBrowser.contentDocument; | ||||
|  | ||||
|     info("Press the Tab key to focus the first page element, the back button"); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       translationsSettingsBackButton.id, | ||||
|       "Key is focused on back button" | ||||
|     ); | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Always Translate Menulist button" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       alwaysTranslateMenuList.id, | ||||
|       "Key is focused on Always Translate Menulist button" | ||||
|     ); | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Never Translate Menulist button" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       neverTranslateMenuList.id, | ||||
|       "Key focus is now Never Translate List Menu button" | ||||
|     ); | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Never Translate Site List section" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       neverTranslateSiteList.id, | ||||
|       "Key focus is now Never Translate Site List" | ||||
|     ); | ||||
|     info( | ||||
|       "Press the Arrow Down key to focus the first site element in the Never Translate Site List" | ||||
|     ); | ||||
|     for (const site of neverTranslateSiteList.children) { | ||||
|       info( | ||||
|         "Press the Arrow Down key to focus the next site element in the Never Translate Site List" | ||||
|       ); | ||||
|       EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|       is( | ||||
|         document.activeElement.parentNode.id, | ||||
|         site.id, | ||||
|         "Key focus is now Never Translate Site list element " + | ||||
|           site.querySelector("label").textContent | ||||
|       ); | ||||
|     } | ||||
|     is( | ||||
|       document.activeElement.parentNode.id, | ||||
|       neverTranslateSiteList.lastElementChild.id, | ||||
|       "Key is focused on the last site " + | ||||
|         neverTranslateSiteList.lastElementChild.querySelector("label") | ||||
|           .textContent + | ||||
|         " within the site list" | ||||
|     ); | ||||
|  | ||||
|     info( | ||||
|       "Press the Arrow up key to focus the previous site element in the Never Translate Site List" | ||||
|     ); | ||||
|     for (let i = neverTranslateSiteList.children.length - 2; i >= 0; i--) { | ||||
|       info( | ||||
|         "Press the Arrow up key to focus the previous site element in the Never Translate Site List" | ||||
|       ); | ||||
|       EventUtils.synthesizeKey("KEY_ArrowUp"); | ||||
|       is( | ||||
|         document.activeElement.parentNode.id, | ||||
|         neverTranslateSiteList.children[i].id, | ||||
|         "Key is focused on the site " + | ||||
|           neverTranslateSiteList.children[i].querySelector("label") | ||||
|             .textContent + | ||||
|           " within the site list" | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Download Languages' Learn More link" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       translateDownloadLanguagesLearnMore.id, | ||||
|       "Key is focused on Download Languages' Learn More link" | ||||
|     ); | ||||
|  | ||||
|     await cleanup(); | ||||
|   } | ||||
| ); | ||||
|  | ||||
| add_task( | ||||
|   async function test_translations_settings_keyboard_never_translate_a11y() { | ||||
|     const { | ||||
|       cleanup, | ||||
|       elements: { settingsButton }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|     }); | ||||
|  | ||||
|     info( | ||||
|       "Open translations settings page by clicking on translations settings button." | ||||
|     ); | ||||
|     assertVisibility({ | ||||
|       message: "Expect paneGeneral elements to be visible.", | ||||
|       visible: { settingsButton }, | ||||
|     }); | ||||
|  | ||||
|     const { | ||||
|       translationsSettingsBackButton, | ||||
|       alwaysTranslateMenuList, | ||||
|       neverTranslateMenuList, | ||||
|       neverTranslateLanguageList, | ||||
|       neverTranslateMenuPopup, | ||||
|       translateDownloadLanguagesLearnMore, | ||||
|     } = | ||||
|       await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|         settingsButton | ||||
|       ); | ||||
|  | ||||
|     const document = gBrowser.selectedBrowser.contentDocument; | ||||
|  | ||||
|     info("Press the Tab key to focus the first page element, the back button"); | ||||
|  | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       translationsSettingsBackButton.id, | ||||
|       "Key is focused on back button" | ||||
|     ); | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Always Translate Menulist button" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       alwaysTranslateMenuList.id, | ||||
|       "Key is focused on Always Translate Menulist button" | ||||
|     ); | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Never Translate Menulist button." | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       neverTranslateMenuList.id, | ||||
|       "Key is focused on Never Translate Menulist button" | ||||
|     ); | ||||
|  | ||||
|     info("Press the Arrow Down key to focus on the first list element."); | ||||
|     for (const menuItem of neverTranslateMenuPopup.children) { | ||||
|       if (AppConstants.platform === "macosx") { | ||||
|         info("Opening the menu popup."); | ||||
|         const popupPromise = BrowserTestUtils.waitForEvent( | ||||
|           neverTranslateMenuPopup, | ||||
|           "popupshown" | ||||
|         ); | ||||
|         EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|         await popupPromise; | ||||
|       } | ||||
|  | ||||
|       EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|  | ||||
|       if (AppConstants.platform === "macosx") { | ||||
|         info("Closing the menu popup."); | ||||
|         const popupPromise = BrowserTestUtils.waitForEvent( | ||||
|           neverTranslateMenuPopup, | ||||
|           "popuphidden" | ||||
|         ); | ||||
|         EventUtils.synthesizeKey("KEY_Enter"); | ||||
|         await popupPromise; | ||||
|       } else { | ||||
|         const { promise, resolve } = Promise.withResolvers(); | ||||
|         requestAnimationFrame(() => { | ||||
|           requestAnimationFrame(resolve); | ||||
|         }); | ||||
|  | ||||
|         EventUtils.synthesizeKey("KEY_Enter"); | ||||
|         await promise; | ||||
|       } | ||||
|  | ||||
|       is( | ||||
|         neverTranslateLanguageList.firstElementChild.querySelector("label") | ||||
|           .textContent, | ||||
|         menuItem.textContent, | ||||
|         menuItem.textContent + "is added to never translate language" | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Never Translate list" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       neverTranslateLanguageList.id, | ||||
|       document.activeElement.id, | ||||
|       "Key is focused on Always Translate list." | ||||
|     ); | ||||
|  | ||||
|     info("Press the Arrow Down key to focus on the first list element."); | ||||
|  | ||||
|     for (const lang of neverTranslateLanguageList.children) { | ||||
|       EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|       is( | ||||
|         document.activeElement.parentNode.id, | ||||
|         lang.id, | ||||
|         "Key is focused on " + | ||||
|           lang.querySelector("label").textContent + | ||||
|           " element of Never Translate list." | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Download Languages' Learn More link" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       translateDownloadLanguagesLearnMore.id, | ||||
|       "Key is focused on Download Languages' Learn More link" | ||||
|     ); | ||||
|  | ||||
|     await cleanup(); | ||||
|   } | ||||
| ); | ||||
|  | ||||
| add_task( | ||||
|   async function test_translations_settings_keyboard_always_translate_a11y() { | ||||
|     const { | ||||
|       cleanup, | ||||
|       elements: { settingsButton }, | ||||
|     } = await setupAboutPreferences(LANGUAGE_PAIRS, { | ||||
|       prefs: [["browser.translations.newSettingsUI.enable", true]], | ||||
|     }); | ||||
|  | ||||
|     info( | ||||
|       "Open translations settings page by clicking on translations settings button." | ||||
|     ); | ||||
|     assertVisibility({ | ||||
|       message: "Expect paneGeneral elements to be visible.", | ||||
|       visible: { settingsButton }, | ||||
|     }); | ||||
|  | ||||
|     const { | ||||
|       translationsSettingsBackButton, | ||||
|       alwaysTranslateMenuList, | ||||
|       neverTranslateMenuList, | ||||
|       alwaysTranslateLanguageList, | ||||
|       alwaysTranslateMenuPopup, | ||||
|     } = | ||||
|       await TranslationsSettingsTestUtils.openAboutPreferencesTranslationsSettingsPane( | ||||
|         settingsButton | ||||
|       ); | ||||
|  | ||||
|     const document = gBrowser.selectedBrowser.contentDocument; | ||||
|  | ||||
|     info("Press the Tab key to focus the first page element, the back button"); | ||||
|  | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       translationsSettingsBackButton.id, | ||||
|       "Key is focused on back button" | ||||
|     ); | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Always Translate Menulist button" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       alwaysTranslateMenuList.id, | ||||
|       "Key is focused on Always Translate Menulist button" | ||||
|     ); | ||||
|  | ||||
|     info("Press the Arrow Down key to focus on the first list element."); | ||||
|     for (const menuItem of alwaysTranslateMenuPopup.children) { | ||||
|       if (AppConstants.platform === "macosx") { | ||||
|         info("Opening the menu popup."); | ||||
|         const popupPromise = BrowserTestUtils.waitForEvent( | ||||
|           alwaysTranslateMenuPopup, | ||||
|           "popupshown" | ||||
|         ); | ||||
|         EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|         await popupPromise; | ||||
|       } | ||||
|  | ||||
|       EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|  | ||||
|       if (AppConstants.platform === "macosx") { | ||||
|         info("Closing the menu popup."); | ||||
|         const popupPromise = BrowserTestUtils.waitForEvent( | ||||
|           alwaysTranslateMenuPopup, | ||||
|           "popuphidden" | ||||
|         ); | ||||
|         EventUtils.synthesizeKey("KEY_Enter"); | ||||
|         await popupPromise; | ||||
|       } else { | ||||
|         const { promise, resolve } = Promise.withResolvers(); | ||||
|         requestAnimationFrame(() => { | ||||
|           requestAnimationFrame(resolve); | ||||
|         }); | ||||
|  | ||||
|         EventUtils.synthesizeKey("KEY_Enter"); | ||||
|         await promise; | ||||
|       } | ||||
|  | ||||
|       is( | ||||
|         alwaysTranslateLanguageList.firstElementChild.querySelector("label") | ||||
|           .textContent, | ||||
|         menuItem.textContent, | ||||
|         menuItem.textContent + "is added to always translate language" | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Always Translate list" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       alwaysTranslateLanguageList.id, | ||||
|       document.activeElement.id, | ||||
|       "Key is focused on Always Translate list." | ||||
|     ); | ||||
|  | ||||
|     info("Press the Arrow Down key to focus on the first list element."); | ||||
|  | ||||
|     for (const lang of alwaysTranslateLanguageList.children) { | ||||
|       EventUtils.synthesizeKey("KEY_ArrowDown"); | ||||
|       is( | ||||
|         document.activeElement.parentNode.id, | ||||
|         lang.id, | ||||
|         "Key is focused on " + | ||||
|           lang.querySelector("label").textContent + | ||||
|           " element of Always Translate list." | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     info( | ||||
|       "Press the Tab key to focus the next page element, the Never Translate list" | ||||
|     ); | ||||
|     EventUtils.synthesizeKey("KEY_Tab"); | ||||
|     is( | ||||
|       document.activeElement.id, | ||||
|       neverTranslateMenuList.id, | ||||
|       "Key focus is now Never Translate List Menu button" | ||||
|     ); | ||||
|  | ||||
|     await cleanup(); | ||||
|   } | ||||
| ); | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user