Compare commits

..

7 Commits

Author SHA1 Message Date
mr. m
0ae7c19c30 test: Import some mochitests from firefox, p=#10897
* test: Import some mochitests from firefox, b=no-bug, c=tests, scripts, tabs

* feat: Added lint rules to ignore mochi tests, b=no-bug, c=tests

* chore: Finish importing tests, b=no-bug, c=workflows, tests, scripts, tabs
2025-12-15 12:09:42 +01:00
Blake Gearin
e525b32c18 feat: Enable original extensions panel on hide-unified-extensions-button: false, p=#11335
* feat: Enable original extensions panel on hide-unified-extensions-button: false

* Update to fix prettier issues

* Add site-data attribute for CSS usage

* Update to set gUnifiedExtensions._panel

* Update to fix prettier issues

* Set default panel on onToolbarVisibilityChange

* Update panel initialization

* Remove extra char

* Restore unified-extensions-panel-template deletion

* Reduce reimplementation

* Update patch against Firefox 145.0.2

* Fix conflict

* Add panelUIPosition case

* Fix lint

* feat: Improve and reduce patch sizes, b=no-bug, c=common

---------

Signed-off-by: Blake Gearin <hello@blakeg.me>
Co-authored-by: mr. m <mr.m@tuta.com>
2025-12-15 11:19:03 +01:00
mr. m
e3d13d534e chore: Stick closer to firefox default preferences, p=#11611, c=no-component 2025-12-14 18:21:28 +01:00
mr. m
e73ea97ea0 fix: Fixed zen not starting up on macos, b=closes #11599, c=no-component 2025-12-14 01:47:54 +01:00
salmonumbrella
fb9bbc3a51 feat(downloads): add pref to control download popup position independently, p=#11607
* feat(downloads): add pref to control download popup position independently

Add `zen.downloads.icon-popup-position` preference that allows users to
control the download popup/indicator position independently from the
vertical tabs position.

Valid values:
- "follow-tabs" (default): popup appears on same side as vertical tabs
- "left": popup always appears on the left
- "right": popup always appears on the right

This is useful for users who have vertical tabs on the right but prefer
the download indicator to appear on the left side of the screen.

* feat: Convert pref to integers, b=no-bug, c=no-component

---------

Co-authored-by: salmonumbrella <salmonumbrella@users.noreply.github.com>
Co-authored-by: mr. m <mr.m@tuta.com>
2025-12-14 01:08:13 +01:00
mr. m
bdfb810212 fix: Dont use a new timestamp when changing config dumps, b=closes #11601, c=configs 2025-12-13 21:15:37 +01:00
mr. m
f1c6c1b321 fix: Fixed missing space between update and media player, b=closes #11592, c=scripts, common, tabs, workspaces 2025-12-12 19:54:15 +01:00
94 changed files with 6477 additions and 102 deletions

View File

@@ -95,6 +95,10 @@ jobs:
echo "Checking if patches apply cleanly..." echo "Checking if patches apply cleanly..."
npm run import npm run import
- name: Import external tests
if: steps.git-check.outputs.files_changed == 'true'
run: python3 scripts/import_external_tests.py
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7
if: steps.git-check.outputs.files_changed == 'true' if: steps.git-check.outputs.files_changed == 'true'

View File

@@ -17,6 +17,8 @@ engine/
surfer.json surfer.json
src/zen/tests/mochitests/*
src/browser/app/profile/*.js src/browser/app/profile/*.js
pnpm-lock.yaml pnpm-lock.yaml

View File

@@ -15,6 +15,5 @@
"ebay", "ebay",
"ebay-*" "ebay-*"
] ]
}, }
"timestamp": 1765455207275
} }

View File

@@ -4,7 +4,7 @@
import js from '@eslint/js'; import js from '@eslint/js';
import globals from 'globals'; import globals from 'globals';
import { defineConfig } from 'eslint/config'; import { defineConfig, globalIgnores } from 'eslint/config';
import zenGlobals from './src/zen/zen.globals.js'; import zenGlobals from './src/zen/zen.globals.js';
export default defineConfig([ export default defineConfig([
@@ -23,4 +23,5 @@ export default defineConfig([
}, },
ignores: ['**/vendor/**', '**/tests/**'], ignores: ['**/vendor/**', '**/tests/**'],
}, },
globalIgnores(['**/mochitests/**']),
]); ]);

View File

@@ -191,6 +191,9 @@ category-zen-CKS =
.tooltiptext = { pane-zen-CKS-title } .tooltiptext = { pane-zen-CKS-title }
pane-settings-CKS-title = { -brand-short-name } Keyboard Shortcuts 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-header = Customize your keyboard shortcuts
zen-settings-CKS-description = Change the default keyboard shortcuts to your liking and improve your browsing experience zen-settings-CKS-description = Change the default keyboard shortcuts to your liking and improve your browsing experience

View File

@@ -8,9 +8,6 @@
- name: browser.sessionstore.restore_pinned_tabs_on_demand - name: browser.sessionstore.restore_pinned_tabs_on_demand
value: true value: true
- name: browser.tabs.loadBookmarksInTabs
value: false
- name: browser.tabs.hoverPreview.enabled - name: browser.tabs.hoverPreview.enabled
value: false value: false
@@ -23,18 +20,6 @@
- name: browser.toolbars.bookmarks.visibility - name: browser.toolbars.bookmarks.visibility
value: 'never' value: 'never'
- name: browser.bookmarks.openInTabClosesMenu
value: false
- name: browser.menu.showViewImageInfo
value: true
- name: findbar.highlightAll
value: true
- name: layout.word_select.eat_space_to_next_word
value: false
- name: widget.non-native-theme.scrollbar.style - name: widget.non-native-theme.scrollbar.style
value: 2 value: 2
@@ -58,9 +43,6 @@
- name: browser.download.manager.addToRecentDocs - name: browser.download.manager.addToRecentDocs
value: false value: false
- name: browser.download.open_pdf_attachments_inline
value: true
- name: browser.download.alwaysOpenPanel - name: browser.download.alwaysOpenPanel
value: false value: false

View File

@@ -8,3 +8,12 @@
- name: network.predictor.enable-hover-on-ssl - name: network.predictor.enable-hover-on-ssl
value: true value: true
# See https://github.com/zen-browser/desktop/issues/11599, this pref seems to
# have disabled itself on macos for some unknown reason.
# Make sure its in sync with:
# https://searchfox.org/firefox-main/rev/1477feb9706f4ccc5bd571c1c215832a6fbb7464/modules/libpref/init/StaticPrefList.yaml#7741-7748
- name: gfx.webrender.compositor
condition: 'defined(XP_WIN) || defined(XP_DARWIN)'
value: '@cond'
mirror: once

View File

@@ -26,7 +26,6 @@
value: true value: true
locked: true locked: true
# Enable private suggestions
- name: browser.search.suggest.enabled - name: browser.search.suggest.enabled
value: false value: false

View File

@@ -7,3 +7,9 @@
- name: zen.downloads.download-animation-duration - name: zen.downloads.download-animation-duration
value: 1000 # ms value: 1000 # ms
- name: zen.downloads.icon-popup-position
# 0: Follow tab's position
# 1: Left side always
# 2: Right side always
value: 0

View File

@@ -0,0 +1,124 @@
# 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()

View File

@@ -5,6 +5,7 @@
import json import json
from typing import Any from typing import Any
class JSONWithCommentsDecoder(json.JSONDecoder): class JSONWithCommentsDecoder(json.JSONDecoder):
def __init__(self, **kw): def __init__(self, **kw):
super().__init__(**kw) super().__init__(**kw)

View File

@@ -15,6 +15,8 @@ IGNORE_PREFS_FILE_OUT = os.path.join(
'engine', 'testing', 'mochitest', 'ignorePrefs.json' 'engine', 'testing', 'mochitest', 'ignorePrefs.json'
) )
MOCHITEST_NAME = "mochitests"
def copy_ignore_prefs(): def copy_ignore_prefs():
print("Copying ignorePrefs.json from src/zen/tests to engine/testing/mochitest...") print("Copying ignorePrefs.json from src/zen/tests to engine/testing/mochitest...")
@@ -59,7 +61,9 @@ def main():
os.execvp(command[0], command) os.execvp(command[0], command)
if path in ("", "all"): 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] test_paths = [str(p) for p in test_dirs]
run_mach_with_paths(test_paths) run_mach_with_paths(test_paths)
else: else:

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js
index fadcbfca95ee28140579430c0371baad0e2f216a..7454b801b4ad892d6ad122277eb7c7736e976f9f 100644 index fadcbfca95ee28140579430c0371baad0e2f216a..22a92ca450ef5087610169b15d7f3344c9accddf 100644
--- a/browser/base/content/browser-addons.js --- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js +++ b/browser/base/content/browser-addons.js
@@ -1069,7 +1069,7 @@ var gXPInstallObserver = { @@ -1069,7 +1069,7 @@ var gXPInstallObserver = {
@@ -38,37 +38,131 @@ index fadcbfca95ee28140579430c0371baad0e2f216a..7454b801b4ad892d6ad122277eb7c773
} }
return anchorID; return anchorID;
@@ -2657,11 +2657,7 @@ var gUnifiedExtensions = { @@ -2547,7 +2547,7 @@ var gUnifiedExtensions = {
// Lazy load the unified-extensions-panel panel the first time we need to requestAnimationFrame(() => this.updateAttention());
// display it. },
if (!this._panel) {
- let template = document.getElementById( - onToolbarVisibilityChange(toolbarId, isVisible) {
- "unified-extensions-panel-template" + onToolbarVisibilityChange(toolbarId, isVisible, panel = this.panel) {
- ); // A list of extension widget IDs (possibly empty).
- template.replaceWith(template.content); let widgetIDs;
- this._panel = document.getElementById("unified-extensions-panel");
+ this._panel = document.getElementById("zen-unified-site-data-panel"); @@ -2561,7 +2561,7 @@ var gUnifiedExtensions = {
let customizationArea = this._panel.querySelector( }
"#unified-extensions-area"
// The list of overflowed extensions in the extensions panel.
- const overflowedExtensionsList = this.panel.querySelector(
+ const overflowedExtensionsList = panel.querySelector(
"#overflowed-extensions-list"
);
@@ -2662,37 +2662,41 @@ var gUnifiedExtensions = {
); );
@@ -2714,6 +2710,7 @@ var gUnifiedExtensions = { template.replaceWith(template.content);
// and no alternative content is available for display in the panel. this._panel = document.getElementById("unified-extensions-panel");
const policies = this.getActivePolicies(); - let customizationArea = this._panel.querySelector(
- "#unified-extensions-area"
- );
- CustomizableUI.registerPanelNode(
- customizationArea,
- CustomizableUI.AREA_ADDONS
- );
- CustomizableUI.addPanelCloseListeners(this._panel);
-
- this._panel
- .querySelector("#unified-extensions-manage-extensions")
- .addEventListener("command", () => {
- BrowserAddonUI.openAddonsMgr("addons://list/extension");
- });
-
- // Lazy-load the l10n strings. Those strings are used for the CUI and
- // non-CUI extensions in the unified extensions panel.
- document
- .getElementById("unified-extensions-context-menu")
- .querySelectorAll("[data-lazy-l10n-id]")
- .forEach(el => {
- el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
- el.removeAttribute("data-lazy-l10n-id");
- });
+ this.initializePanel(this._panel);
}
return this._panel;
},
+ initializePanel(panel) {
+ let customizationArea = panel.querySelector(
+ "#unified-extensions-area"
+ );
+ CustomizableUI.registerPanelNode(
+ customizationArea,
+ CustomizableUI.AREA_ADDONS
+ );
+ CustomizableUI.addPanelCloseListeners(panel);
+
+ panel
+ .querySelector("#unified-extensions-manage-extensions")
+ .addEventListener("command", () => {
+ BrowserAddonUI.openAddonsMgr("addons://list/extension");
+ });
+
+ // Lazy-load the l10n strings. Those strings are used for the CUI and
+ // non-CUI extensions in the unified extensions panel.
+ document
+ .getElementById("unified-extensions-context-menu")
+ .querySelectorAll("[data-lazy-l10n-id]")
+ .forEach(el => {
+ el.setAttribute("data-l10n-id", el.getAttribute("data-lazy-l10n-id"));
+ el.removeAttribute("data-lazy-l10n-id");
+ });
+ },
+
// `aEvent` and `reason` are optional. If `reason` is specified, it should be
// a valid argument to gUnifiedExtensions.recordButtonTelemetry().
- async togglePanel(aEvent, reason) {
+ async togglePanel(aEvent, reason, panel = this._panel, view = "unified-extensions-view", button = this._button) {
if (!CustomizationHandler.isCustomizing()) {
if (aEvent) {
if ( if (
+ false && @@ -2729,32 +2733,30 @@ var gUnifiedExtensions = {
policies.length && this.blocklistAttentionInfo =
!this.hasExtensionsInPanel(policies) && await AddonManager.getBlocklistAttentionInfo();
!this.isPrivateWindowMissingExtensionsWithoutPBMAccess() &&
@@ -2754,7 +2751,7 @@ var gUnifiedExtensions = { - let panel = this.panel;
-
if (!this._listView) {
this._listView = PanelMultiView.getViewNode(
document,
- "unified-extensions-view"
+ view,
);
this._listView.addEventListener("ViewShowing", this);
this._listView.addEventListener("ViewHiding", this);
}
- if (this._button.open) {
+ if (button.open) {
PanelMultiView.hidePopup(panel);
- this._button.open = false;
+ button.open = false;
} else {
// Overflow extensions placed in collapsed toolbars, if any.
for (const toolbarId of CustomizableUI.getCollapsedToolbarIds(window)) {
// We pass `false` because all these toolbars are collapsed.
- this.onToolbarVisibilityChange(toolbarId, /* isVisible */ false);
+ this.onToolbarVisibilityChange(toolbarId, /* isVisible */ false, panel);
}
panel.hidden = false;
this.recordButtonTelemetry(reason || "extensions_panel_showing"); this.recordButtonTelemetry(reason || "extensions_panel_showing");
this.ensureButtonShownBeforeAttachingPanel(panel); this.ensureButtonShownBeforeAttachingPanel(panel);
PanelMultiView.openPopup(panel, this._button, { - PanelMultiView.openPopup(panel, this._button, {
- position: "bottomright topright", - position: "bottomright topright",
+ position: gZenUIManager.panelUIPosition(panel, this._button), + PanelMultiView.openPopup(panel, button, {
+ position: gZenUIManager.panelUIPosition(panel, button),
triggerEvent: aEvent, triggerEvent: aEvent,
}); });
} }
@@ -2941,18 +2938,20 @@ var gUnifiedExtensions = { @@ -2941,18 +2943,20 @@ var gUnifiedExtensions = {
this._maybeMoveWidgetNodeBack(widgetId); this._maybeMoveWidgetNodeBack(widgetId);
} }

View File

@@ -1,5 +1,5 @@
diff --git a/browser/base/content/navigator-toolbox.js b/browser/base/content/navigator-toolbox.js diff --git a/browser/base/content/navigator-toolbox.js b/browser/base/content/navigator-toolbox.js
index 7b776b15d52367a008ce6bf53dcfcbbe007b7453..d9a3404905b73db7c8f202ab166d5f3c625351f6 100644 index 7b776b15d52367a008ce6bf53dcfcbbe007b7453..da23f716c753f5a43f17bb5ed7a3d335891168c2 100644
--- a/browser/base/content/navigator-toolbox.js --- a/browser/base/content/navigator-toolbox.js
+++ b/browser/base/content/navigator-toolbox.js +++ b/browser/base/content/navigator-toolbox.js
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
@@ -27,21 +27,28 @@ index 7b776b15d52367a008ce6bf53dcfcbbe007b7453..d9a3404905b73db7c8f202ab166d5f3c
gBrowser.handleNewTabMiddleClick(element, event); gBrowser.handleNewTabMiddleClick(element, event);
break; break;
@@ -315,7 +317,7 @@ document.addEventListener( @@ -316,6 +318,7 @@ document.addEventListener(
#pageActionButton,
#downloads-button, #downloads-button,
#fxa-toolbar-menu-button, #fxa-toolbar-menu-button,
- #unified-extensions-button, #unified-extensions-button,
+ #zen-site-data-icon-button, + #zen-site-data-icon-button,
#library-button #library-button
`); `);
if (!element) { if (!element) {
@@ -394,7 +396,7 @@ document.addEventListener( @@ -398,6 +401,16 @@ document.addEventListener(
gSync.toggleAccountPanel(element, event);
break;
- case "unified-extensions-button":
+ case "zen-site-data-icon-button":
gUnifiedExtensions.togglePanel(event); gUnifiedExtensions.togglePanel(event);
break; break;
+ case "zen-site-data-icon-button":
+ gUnifiedExtensions.togglePanel(
+ event,
+ null,
+ window.gZenSiteDataPanel.unifiedPanel,
+ window.gZenSiteDataPanel.unifiedPanelView,
+ window.gZenSiteDataPanel.anchor,
+ );
+ break;
+
case "library-button":
PanelUI.showSubView("appMenu-libraryView", element, event);
break;

View File

@@ -32,7 +32,7 @@
command="Browser:AddBookmarkAs" command="Browser:AddBookmarkAs"
flex="1" /> flex="1" />
</hbox> </hbox>
<vbox class="zen-site-data-section"> <vbox id="zen-site-data-section-addons" class="zen-site-data-section">
<hbox class="zen-site-data-section-header"> <hbox class="zen-site-data-section-header">
<label data-l10n-id="unified-extensions-header-title" flex="1" /> <label data-l10n-id="unified-extensions-header-title" flex="1" />
<label data-l10n-id="zen-generic-manage" id="zen-site-data-manage-addons" /> <label data-l10n-id="zen-generic-manage" id="zen-site-data-manage-addons" />
@@ -72,7 +72,7 @@
# for this specific button / id # for this specific button / id
<toolbarbutton id="unified-extensions-manage-extensions" <toolbarbutton id="unified-extensions-manage-extensions"
class="subviewbutton panel-subview-footer-button unified-extensions-manage-extensions" class="subviewbutton panel-subview-footer-button unified-extensions-manage-extensions"
data-l10n-id="unified-extensions-manage-extensions" data-l10n-id="unified-extensions-manage-extensions"
hidden="true" /> hidden="true" />
</vbox> </vbox>
<vbox class="zen-site-data-section"> <vbox class="zen-site-data-section">
@@ -85,7 +85,7 @@
</vbox> </vbox>
</vbox> </vbox>
<hbox id="zen-site-data-footer"> <hbox id="zen-site-data-footer">
<toolbarbutton id="zen-site-data-security-info" <toolbarbutton id="zen-site-data-security-info"
class="subviewbutton zen-interactive-button" /> class="subviewbutton zen-interactive-button" />
<toolbarbutton id="zen-site-data-actions" <toolbarbutton id="zen-site-data-actions"
class="subviewbutton zen-interactive-button" class="subviewbutton zen-interactive-button"

View File

@@ -669,7 +669,9 @@ window.gZenUIManager = {
} }
if ( if (
(gZenVerticalTabsManager._hasSetSingleToolbar && gZenVerticalTabsManager._prefsRightSide) || (gZenVerticalTabsManager._hasSetSingleToolbar && gZenVerticalTabsManager._prefsRightSide) ||
(panel?.id === 'zen-unified-site-data-panel' && !gZenVerticalTabsManager._hasSetSingleToolbar) (panel?.id === 'zen-unified-site-data-panel' &&
!gZenVerticalTabsManager._hasSetSingleToolbar) ||
(panel?.id === 'unified-extensions-panel' && gZenVerticalTabsManager._hasSetSingleToolbar)
) { ) {
block = 'bottomright'; block = 'bottomright';
inline = 'topright'; inline = 'topright';

View File

@@ -261,6 +261,7 @@ body > #confetti {
} }
#TabsToolbar { #TabsToolbar {
gap: var(--zen-toolbox-padding);
-moz-window-dragging: unset; -moz-window-dragging: unset;
} }
@@ -494,20 +495,22 @@ body > #confetti {
} }
@media -moz-pref('zen.theme.hide-unified-extensions-button') { @media -moz-pref('zen.theme.hide-unified-extensions-button') {
#unified-extensions-button { #unified-extensions-button:not([showing]) {
display: none !important; display: none !important;
} }
} }
#unified-extensions-button:not([showing]) { @media not -moz-pref('zen.theme.hide-unified-extensions-button') {
display: none !important; #zen-site-data-section-addons {
display: none;
}
} }
#zen-site-data-header { #zen-site-data-header {
gap: 8px; gap: 8px;
align-items: center; align-items: center;
padding: 10px 9px; padding: 10px 9px;
padding-bottom: 0; padding-bottom: 8px;
:root[zen-single-toolbar='true']:not([zen-right-side='true']) & { :root[zen-single-toolbar='true']:not([zen-right-side='true']) & {
flex-direction: row-reverse; flex-direction: row-reverse;
@@ -519,7 +522,7 @@ body > #confetti {
-moz-context-properties: fill; -moz-context-properties: fill;
fill: currentColor; fill: currentColor;
color: light-dark(rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0.8)); color: light-dark(rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0.8));
max-width: 48px; width: 48px;
height: 32px; height: 32px;
position: relative; position: relative;

View File

@@ -131,6 +131,9 @@ class nsZenDownloadAnimationElement extends HTMLElement {
} }
#areTabsOnRightSide() { #areTabsOnRightSide() {
const position = Services.prefs.getIntPref('zen.downloads.icon-popup-position', 0);
if (position === 1) return false;
if (position === 2) return true;
return Services.prefs.getBoolPref('zen.tabs.vertical.right-side'); return Services.prefs.getBoolPref('zen.tabs.vertical.right-side');
} }

View File

@@ -439,7 +439,6 @@
background: transparent; background: transparent;
gap: 5px; gap: 5px;
align-items: center; align-items: center;
padding-top: var(--zen-element-separation);
& > toolbarbutton:not(#zen-workspaces-button) { & > toolbarbutton:not(#zen-workspaces-button) {
padding: 0 !important; padding: 0 !important;

View File

@@ -10,5 +10,9 @@
"zen.mods.last-update", "zen.mods.last-update",
"zen.view.compact.enable-at-startup", "zen.view.compact.enable-at-startup",
"zen.urlbar.suggestions-learner", "zen.urlbar.suggestions-learner",
"browser.newtabpage.activity-stream.trendingSearch.defaultSearchEngine" "browser.newtabpage.activity-stream.trendingSearch.defaultSearchEngine",
// From the imported safebrowsing tests
"urlclassifier.phishTable",
"urlclassifier.malwareTable"
] ]

View File

@@ -0,0 +1,30 @@
# 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
disable = [
"browser_1119088.js",
"browser_setDesktopBackgroundPreview.js",
]
[tooltiptext]
source = "toolkit/components/tooltiptext"

View File

@@ -0,0 +1,14 @@
# 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",
]

View File

@@ -0,0 +1,57 @@
[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
"os == 'linux' && os_version == '24.04' && processor == 'x86_64' && asan", # Bug 1867132
"os == 'linux' && os_version == '24.04' && processor == 'x86_64' && debug", # Bug 1867132
"os == 'win' && os_version == '11.26100' && processor == 'x86_64' && asan", # Bug 1867132
]
["browser_learn_more_link.js"]
["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",
]

View File

@@ -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();
});

View File

@@ -0,0 +1,126 @@
/* 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);
function getEtpCategory() {
return Services.prefs.getStringPref(
"browser.contentblocking.category",
"standard"
);
}
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",
blockedOrigins: null,
isPrivateBrowsing: true,
hasTrackingContentBlocked: true,
hasMixedActiveContentBlocked: true,
hasMixedDisplayContentBlocked: true,
btpHasPurgedSite: false,
etpCategory: getEtpCategory(),
},
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",
blockedOrigins: ["https://trackertest.org"],
isPrivateBrowsing: true,
hasTrackingContentBlocked: true,
hasMixedActiveContentBlocked: true,
hasMixedDisplayContentBlocked: true,
btpHasPurgedSite: false,
etpCategory: getEtpCategory(),
},
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);
});

View File

@@ -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();
});
});

View File

@@ -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();
}
});
});

View File

@@ -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();
}
});

View File

@@ -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();
}
});
});

View File

@@ -0,0 +1,36 @@
/* 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();
async function ensureLearnMoreLinkWorks(menu) {
const rbs = await menu.openReportBrokenSite();
const { win, mainView, learnMoreLink } = rbs;
ok(learnMoreLink, "Found a learn more link");
const promises = [
BrowserTestUtils.waitForEvent(mainView, "ViewHiding"),
BrowserTestUtils.waitForNewTab(win.gBrowser, LEARN_MORE_TEST_URL),
];
EventUtils.synthesizeMouseAtCenter(learnMoreLink, {}, win);
const results = await Promise.all(promises);
gBrowser.removeTab(results[1]);
}
add_task(async function testLearnMoreLink() {
ensureReportBrokenSitePreffedOn();
await BrowserTestUtils.withNewTab(REPORTABLE_PAGE_URL, async function () {
await ensureLearnMoreLinkWorks(AppMenu());
await ensureLearnMoreLinkWorks(HelpMenu());
await ensureLearnMoreLinkWorks(ProtectionsPanel());
});
const telemetry = Glean.webcompatreporting.learnMore.testGetValue();
is(telemetry.length, 3, "Got telemetry");
});

View File

@@ -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`
);
}
});
});

View File

@@ -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();
}
);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -0,0 +1,134 @@
/* 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");
order.push("#report-broken-site-popup-blocked-trackers-checkbox");
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-learn-more-link");
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());
});
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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>

View File

@@ -0,0 +1,918 @@
/* 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 SUMO_BASE_URL = Services.urlFormatter.formatURLPref(
"app.support.baseURL"
);
const LEARN_MORE_TEST_URL = `${SUMO_BASE_URL}report-broken-site`;
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);
}
Services.telemetry.clearEvents();
Services.fog.testResetFOG();
});
});
}
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 learnMoreLink() {
return this.getViewNode("report-broken-site-popup-learn-more-link");
}
get sendMoreInfoLink() {
return this.getViewNode("report-broken-site-popup-send-more-info-link");
}
get backButton() {
return this.mainView.querySelector(".subviewbutton-back");
}
get blockedTrackersCheckbox() {
return this.getViewNode(
"report-broken-site-popup-blocked-trackers-checkbox"
);
}
set blockedTrackersCheckbox(checked) {
this.blockedTrackersCheckbox.checked = checked;
}
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);
});
}

View File

@@ -0,0 +1,355 @@
/* 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",
blockedOrigins: null,
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.antitracking.blockedOrigins) {
rbs.blockedTrackersCheckbox = true;
}
}
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"
);
if (rbs.blockedTrackersCheckbox.checked) {
ok(
Array.isArray(tabInfo.antitracking.blockedOrigins),
"Got an array for blockedOrigins"
);
} else {
ok(!tabInfo.antitracking.blockedOrigins, "No blockedOrigins included");
}
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();
const telemetry = Glean.webcompatreporting.send.testGetValue();
is(telemetry?.length, 1, "Got a 'send' telemetry event");
is(
telemetry[0].extra.sent_with_blocked_trackers,
String(!!expectedOverrides.antitracking?.blockedOrigins),
"Got correct 'sent_with_blocked_trackers' flag"
);
// re-opening the panel, the url and description should be reset
rbs = await menu.openReportBrokenSite();
rbs.isMainViewResetToCurrentTab();
ok(
!rbs.blockedTrackersCheckbox.checked,
"blocked trackers checkbox is reset"
);
rbs.close();
}

View File

@@ -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>

View File

@@ -0,0 +1,307 @@
/* 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 blockedOrigins =
atOverrides?.blockedOrigins ?? antitracking.blockedOrigins ?? [];
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,
blockedOrigins,
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 [
"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");
}

View 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"]

View 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();
});

View 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"
);
});
});

View File

@@ -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!");
}
);
});

View 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);
});

View File

@@ -0,0 +1 @@
<html><body></body></html>

View 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();

View File

@@ -0,0 +1,103 @@
[DEFAULT]
["browser_1119088.js"]
disabled="Disabled by import_external_tests.py"
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"]
disabled="Disabled by import_external_tests.py"
tags = "os_integration"

View File

@@ -0,0 +1,173 @@
// 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 =
"/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;
defaultBackgroundPath = kDefaultBackgroundImage;
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);
}
);
});

View 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);
}
}
);
});

View 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"
);
}

View 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;
}
}

View 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"
);
});

View File

@@ -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
);
});

View File

@@ -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
);
});

View File

@@ -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
);
});

View File

@@ -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
);
});

View File

@@ -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
);
});

View File

@@ -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
);
});

View 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"
);
});

View 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();
}
);

View 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",
Ci.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();
}
});

View File

@@ -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;
}
);
});

View File

@@ -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));
}

View 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);
}

View 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"

View 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);
}

View 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>

View File

@@ -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>

View 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>

View File

@@ -0,0 +1,2 @@
HTTP 302 Moved Temporarily
Location: headless.html

View 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)

View File

@@ -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");
});

View File

@@ -0,0 +1,7 @@
[DEFAULT]
run-if = ["os != 'android'"]
firefox-appdir = "browser"
tags = "os_integration"
["test_macOS_showSecurityPreferences.js"]
run-if = ["os == 'mac'"]

View 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"]

View 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);
});
}
);
});

View File

@@ -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");
});
}
);
});

View 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"
);
});
}
);
});

View 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]);
}
}
);
});

View File

@@ -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;
}

View 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"
);
}
);
});

View File

@@ -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);
});

View 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 &lt;title&gt;
<title>
This is a title
</title>
</text>
<text id="text2" x="10px" y="96px" font-size="24px">
This contains only &lt;desc&gt;
<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 &lt;title&gt;
<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 &lt;title&gt;
<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 &lt;title&gt; &amp; 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

View 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>

View File

@@ -15,3 +15,7 @@ BROWSER_CHROME_MANIFESTS += [
"welcome/browser.toml", "welcome/browser.toml",
"workspaces/browser.toml", "workspaces/browser.toml",
] ]
DIRS += [
"mochitests",
]

View File

@@ -4,6 +4,11 @@
import { AppConstants } from 'resource://gre/modules/AppConstants.sys.mjs'; import { AppConstants } from 'resource://gre/modules/AppConstants.sys.mjs';
const ADDONS_BUTTONS_HIDDEN = Services.prefs.getBoolPref(
'zen.theme.hide-unified-extensions-button',
true
);
const lazy = {}; const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
@@ -21,7 +26,19 @@ export class nsZenSiteDataPanel {
this.window = window; this.window = window;
this.document = window.document; this.document = window.document;
this.panel = this.document.getElementById('zen-unified-site-data-panel'); this.unifiedPanel = this.#initUnifiedPanel();
this.unifiedPanelView = 'unified-extensions-view';
this.extensionsPanelView = 'original-unified-extensions-view';
if (ADDONS_BUTTONS_HIDDEN) {
this.window.gUnifiedExtensions._panel = this.unifiedPanel;
// Remove the old permissions dialog
this.document.getElementById('unified-extensions-panel-template').remove();
} else {
this.extensionsPanel = this.#initExtensionsPanel();
}
this.#init(); this.#init();
} }
@@ -34,22 +51,23 @@ export class nsZenSiteDataPanel {
`); `);
this.anchor = button.querySelector('#zen-site-data-icon-button'); this.anchor = button.querySelector('#zen-site-data-icon-button');
this.document.getElementById('identity-icon-box').before(button); this.document.getElementById('identity-icon-box').before(button);
this.window.gUnifiedExtensions._button = this.anchor;
this.extensionsPanelButton = this.document.getElementById('unified-extensions-button');
this.window.gUnifiedExtensions._button = ADDONS_BUTTONS_HIDDEN
? this.anchor
: this.extensionsPanelButton;
this.document this.document
.getElementById('nav-bar') .getElementById('nav-bar')
.setAttribute('addon-webext-overflowbutton', 'zen-site-data-icon-button'); .setAttribute('addon-webext-overflowbutton', 'zen-site-data-icon-button');
// Remove the old permissions dialog
this.document.getElementById('unified-extensions-panel-template').remove();
this.#initCopyUrlButton(); this.#initCopyUrlButton();
this.#initEventListeners(); this.#initEventListeners();
this.#maybeShowFeatureCallout(); this.#maybeShowFeatureCallout();
} }
#initEventListeners() { #initEventListeners() {
this.panel.addEventListener('popupshowing', this); this.unifiedPanel.addEventListener('popupshowing', this);
this.document.getElementById('zen-site-data-manage-addons').addEventListener('click', this); this.document.getElementById('zen-site-data-manage-addons').addEventListener('click', this);
this.document.getElementById('zen-site-data-settings-more').addEventListener('click', this); this.document.getElementById('zen-site-data-settings-more').addEventListener('click', this);
this.anchor.addEventListener('click', this); this.anchor.addEventListener('click', this);
@@ -124,6 +142,24 @@ export class nsZenSiteDataPanel {
} }
} }
#initExtensionsPanel() {
const panel = this.window.gUnifiedExtensions.panel;
const extensionsView = panel?.querySelector('#unified-extensions-view');
extensionsView.setAttribute('id', this.extensionsPanelView);
const panelMultiView = panel?.querySelector('panelmultiview');
panelMultiView.setAttribute('mainViewId', this.extensionsPanelView);
return panel;
}
#initUnifiedPanel() {
const panel = this.document.getElementById('zen-unified-site-data-panel');
this.window.gUnifiedExtensions.initializePanel(panel);
return panel;
}
#preparePanel() { #preparePanel() {
this.#setSitePermissions(); this.#setSitePermissions();
this.#setSiteSecurityInfo(); this.#setSiteSecurityInfo();
@@ -498,7 +534,7 @@ export class nsZenSiteDataPanel {
this.window.gZenCommonActions.copyCurrentURLToClipboard(); this.window.gZenCommonActions.copyCurrentURLToClipboard();
} }
if (AppConstants.platform !== 'macosx') { if (AppConstants.platform !== 'macosx') {
this.panel.hidePopup(); this.unifiedPanel.hidePopup();
} }
} }
} }
@@ -556,7 +592,13 @@ export class nsZenSiteDataPanel {
break; break;
} }
case 'zen-site-data-icon-button': { case 'zen-site-data-icon-button': {
this.window.gUnifiedExtensions.togglePanel(event); this.window.gUnifiedExtensions.togglePanel(
event,
null,
this.unifiedPanel,
this.unifiedPanelView,
this.anchor
);
break; break;
} }
default: { default: {

View File

@@ -3140,7 +3140,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
return; return;
} }
const maxButtonSize = 32; // IMPORTANT: This should match the CSS size of the icons const maxButtonSize = 32; // IMPORTANT: This should match the CSS size of the icons
const minButtonSize = 15; const minButtonSize = maxButtonSize / 2; // Minimum size for icons when space is limited
const separation = 3; // Space between icons const separation = 3; // Space between icons
// Calculate the total width needed for all icons // Calculate the total width needed for all icons
@@ -3160,9 +3160,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
// Set the width of each icon to the maximum size they can fit on // Set the width of each icon to the maximum size they can fit on
const widthPerButton = Math.max( const widthPerButton = Math.max(
Math.floor( (parent.clientWidth - separation * (parent.children.length - 1)) / parent.children.length,
(parent.clientWidth - separation * (parent.children.length - 1)) / parent.children.length
),
minButtonSize minButtonSize
); );
for (const icon of parent.children) { for (const icon of parent.children) {

View File

@@ -4,31 +4,40 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
&::after {
content: '';
position: absolute;
width: 4px;
border-radius: 99px;
height: 4px;
background: color-mix(
in srgb,
var(--zen-primary-color) 10%,
light-dark(rgba(0, 0, 0, 0.4), rgba(255, 255, 255, 0.4)) 90%
);
left: 50%;
top: 50%;
filter: saturate(140%) brightness(110%) !important;
transform: translate(-50%, -50%);
transition: opacity 150ms ease-in-out;
opacity: 0;
}
& .zen-workspace-icon {
transition: opacity 150ms ease-in-out, transform 150ms ease-in-out;
z-index: 2;
transform-origin: center;
}
&:not(:hover) { &:not(:hover) {
min-width: 10px; min-width: 10px;
&::after { &::after {
content: ''; opacity: 1;
position: absolute;
width: 4px;
border-radius: 99px;
height: 4px;
background: color-mix(
in srgb,
var(--zen-primary-color) 10%,
light-dark(rgba(0, 0, 0, 0.4), rgba(255, 255, 255, 0.4)) 90%
);
left: 50%;
top: 50%;
filter: saturate(140%) brightness(110%) !important;
transform: translate(-50%, -50%);
} }
& .zen-workspace-icon { & .zen-workspace-icon {
display: none; opacity: 0;
transform: scale(0);
} }
} }
&:hover {
width: 20px !important;
}

View File

@@ -94,7 +94,7 @@
&[icons-overflow] { &[icons-overflow] {
gap: 0 !important; gap: 0 !important;
justify-content: space-between; justify-content: center;
& toolbarbutton { & toolbarbutton {
margin: 0; margin: 0;
@@ -108,7 +108,7 @@
} }
&:has(toolbarbutton:hover) toolbarbutton[active='true']:not([dragged='true']) { &:has(toolbarbutton:hover) toolbarbutton[active='true']:not([dragged='true']) {
%include overflow-icons.inc.css %include overflow-icons.inc.css
} }
} }
} }

View File

@@ -19,7 +19,7 @@
"brandShortName": "Zen", "brandShortName": "Zen",
"brandFullName": "Zen Browser", "brandFullName": "Zen Browser",
"release": { "release": {
"displayVersion": "1.17.13b", "displayVersion": "1.17.14b",
"github": { "github": {
"repo": "zen-browser/desktop" "repo": "zen-browser/desktop"
}, },