mirror of
https://github.com/zen-browser/desktop.git
synced 2026-04-02 21:59:18 +00:00
feat: Add Arc's boosts implementation, b=no-bug, c=boosts (#11071)
Co-authored-by: Mr. M <mr.m@tuta.com> Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
This commit is contained in:
@@ -20,3 +20,5 @@ files:
|
||||
translation: browser/browser/preferences/zen-preferences.ftl
|
||||
- source: en-US/browser/browser/zen-folders.ftl
|
||||
translation: browser/browser/zen-folders.ftl
|
||||
- source: en-US/browser/browser/zen-boosts.ftl
|
||||
translation: browser/browser/zen-boosts.ftl
|
||||
|
||||
58
locales/en-US/browser/browser/zen-boosts.ftl
Normal file
58
locales/en-US/browser/browser/zen-boosts.ftl
Normal file
@@ -0,0 +1,58 @@
|
||||
# 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/.
|
||||
|
||||
zen-boost-edit-rename =
|
||||
.label = Rename Boost
|
||||
zen-boost-edit-shuffle =
|
||||
.label = Shuffle Vibes
|
||||
zen-boost-edit-reset =
|
||||
.label = Reset All Edits
|
||||
zen-boost-edit-delete =
|
||||
.label = Delete Boost
|
||||
zen-boost-size = Size
|
||||
zen-boost-zap = Zap
|
||||
zen-boost-code = Code
|
||||
zen-boost-back = Back
|
||||
zen-boost-shuffle =
|
||||
.tooltiptext = Shuffle Boost Settings
|
||||
zen-boost-invert =
|
||||
.tooltiptext = Smart Invert Colors
|
||||
zen-boost-controls =
|
||||
.tooltiptext = Advanced Color Controls
|
||||
zen-boost-disable =
|
||||
.tooltiptext = Disable Color Adjustments
|
||||
zen-boost-text-case-toggle =
|
||||
.tooltiptext = Toggle Text Case
|
||||
zen-boost-css-picker =
|
||||
.tooltiptext = Pick Selector
|
||||
zen-boost-css-inspector =
|
||||
.tooltiptext = Open Inspector
|
||||
zen-bootst-color-contrast = Contrast
|
||||
zen-bootst-color-brightness = Brightness
|
||||
zen-bootst-color-original-saturation = Original Saturation
|
||||
zen-add-zap-helper = Click elements on the page to <b>Zap</b> them
|
||||
zen-remove-zap-helper = ← Click to Unzap
|
||||
zen-select-this = Insert selector for this
|
||||
zen-select-related = Insert selector for related
|
||||
zen-select-cancel = Cancel
|
||||
zen-zap-this = Zap this
|
||||
zen-zap-related = Zap all related elements
|
||||
zen-zap-cancel = Cancel
|
||||
zen-zap-done = Done
|
||||
zen-unzap-tooltip =
|
||||
{
|
||||
$elementCount ->
|
||||
[0] No elements zapped
|
||||
[1] { $elementCount } element zapped
|
||||
*[other] { $elementCount } elements zapped
|
||||
}
|
||||
zen-boost-save =
|
||||
.tooltiptext = Export Boost
|
||||
zen-boost-load =
|
||||
.tooltiptext = Import Boost
|
||||
zen-panel-ui-boosts-exported-message = Boost exported!
|
||||
zen-site-data-boosts = Boosts
|
||||
zen-site-data-create-boost =
|
||||
.tooltiptext = Create new boost
|
||||
zen-boost-rename-boost-prompt = Rename Boost?
|
||||
9
prefs/zen/boosts.yaml
Normal file
9
prefs/zen/boosts.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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/.
|
||||
|
||||
- name: zen.boosts.enabled
|
||||
value: "@IS_TWILIGHT@"
|
||||
|
||||
- name: zen.boosts.dissolve-on-zap
|
||||
value: true
|
||||
@@ -18,4 +18,5 @@
|
||||
#include ../../../zen/images/jar.inc.mn
|
||||
#include ../../../zen/vendor/jar.inc.mn
|
||||
#include ../../../zen/fonts/jar.inc.mn
|
||||
#include ../../../zen/boosts/jar.inc.mn
|
||||
#include ../../../zen/live-folders/jar.inc.mn
|
||||
|
||||
@@ -9,5 +9,6 @@
|
||||
<link rel="localization" href="browser/zen-menubar.ftl"/>
|
||||
<link rel="localization" href="browser/zen-vertical-tabs.ftl"/>
|
||||
<link rel="localization" href="browser/zen-folders.ftl"/>
|
||||
<link rel="localization" href="browser/zen-boosts.ftl"/>
|
||||
<link rel="localization" href="browser/zen-live-folders.ftl"/>
|
||||
</linkset>
|
||||
|
||||
@@ -75,6 +75,18 @@
|
||||
data-l10n-id="unified-extensions-manage-extensions"
|
||||
hidden="true" />
|
||||
</vbox>
|
||||
<vbox class="zen-site-data-section">
|
||||
<hbox class="zen-site-data-section-header">
|
||||
<label data-l10n-id="zen-site-data-boosts" flex="1" />
|
||||
#if 0
|
||||
<!-- This can later be used to open Zen Library on the Boosts tab to display a list of all boosts -->
|
||||
<label data-l10n-id="zen-generic-more" id="zen-site-data-boosts-more" hidden="true" />
|
||||
#endif
|
||||
</hbox>
|
||||
<vbox id="zen-site-data-boost-list">
|
||||
<!-- settings will be inserted here -->
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox class="zen-site-data-section">
|
||||
<hbox class="zen-site-data-section-header">
|
||||
<label data-l10n-id="zen-site-data-settings" flex="1" />
|
||||
@@ -87,6 +99,8 @@
|
||||
<hbox id="zen-site-data-footer">
|
||||
<toolbarbutton id="zen-site-data-security-info"
|
||||
class="subviewbutton zen-interactive-button" />
|
||||
<toolbarbutton id="zen-site-data-boost" data-l10n-id="zen-site-data-create-boost"
|
||||
class="subviewbutton zen-interactive-button" />
|
||||
<toolbarbutton id="zen-site-data-actions"
|
||||
class="subviewbutton zen-interactive-button"
|
||||
closemenu="none"
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#zen-welcome-start-button,
|
||||
.zen-toast button,
|
||||
.zen-current-workspace-indicator-chevron,
|
||||
.pinned-tabs-container-separator toolbarbutton {
|
||||
.pinned-tabs-container-separator toolbarbutton,
|
||||
.zen-permission-popup-boost-editor-button {
|
||||
-moz-context-properties: fill, fill-opacity !important;
|
||||
fill: currentColor !important;
|
||||
}
|
||||
@@ -39,10 +40,21 @@
|
||||
.close-icon,
|
||||
.zen-glance-sidebar-close,
|
||||
.zen-theme-picker-custom-list-item-remove,
|
||||
#appMenu-quit-button2 {
|
||||
#appMenu-quit-button2,
|
||||
#zen-boost-delete {
|
||||
list-style-image: url("close.svg") !important;
|
||||
}
|
||||
|
||||
#PanelUI-zen-emojis-picker-none,
|
||||
#zen-emojis-picker-none {
|
||||
list-style-image: url("trash.svg");
|
||||
}
|
||||
|
||||
#PanelUI-zen-gradient-generator-color-remove,
|
||||
#zen-gradient-generator-color-remove {
|
||||
list-style-image: url("unpin.svg") !important;
|
||||
}
|
||||
|
||||
#PanelUI-zen-emojis-picker-none {
|
||||
list-style-image: url("trash.svg");
|
||||
}
|
||||
@@ -90,7 +102,8 @@
|
||||
|
||||
#appMenu-zoom-controls,
|
||||
#PanelUI-zen-gradient-generator-color-add,
|
||||
#zen-site-data-new-addon-button {
|
||||
#zen-site-data-new-addon-button,
|
||||
.zen-create-new-boost {
|
||||
list-style-image: url("plus.svg") !important;
|
||||
}
|
||||
|
||||
@@ -115,7 +128,8 @@
|
||||
}
|
||||
|
||||
.zen-current-workspace-indicator-chevron,
|
||||
#PanelUI-zen-gradient-generator-color-page-right {
|
||||
#PanelUI-zen-gradient-generator-color-page-right,
|
||||
.zen-permission-popup-boost-editor-button {
|
||||
list-style-image: url("arrow-right.svg");
|
||||
}
|
||||
|
||||
@@ -410,7 +424,8 @@
|
||||
}
|
||||
|
||||
.panel-header > .subviewbutton-back,
|
||||
#PanelUI-zen-gradient-generator-color-page-left {
|
||||
#PanelUI-zen-gradient-generator-color-page-left,
|
||||
#zen-boost-back {
|
||||
list-style-image: url("arrow-left.svg") !important;
|
||||
}
|
||||
|
||||
@@ -502,6 +517,27 @@
|
||||
&:is([open], [starred]) image {
|
||||
list-style-image: url("permissions-fill.svg");
|
||||
}
|
||||
&[boosting] image {
|
||||
color: var(--color-accent-primary);
|
||||
list-style-image: url("permissions-fill.svg");
|
||||
}
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media not (prefers-reduced-motion: reduce) {
|
||||
#zen-site-data-icon-button[boosting]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
color: var(--color-accent-primary);
|
||||
background: url("chrome://browser/content/zen-images/boost-indicator.svg") no-repeat;
|
||||
transform: translateX(-20%);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.geo-icon {
|
||||
@@ -876,6 +912,31 @@
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
#zen-site-data-boost {
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
fill: currentColor;
|
||||
border-radius: 6px;
|
||||
appearance: none;
|
||||
padding: 6px 10px 6px 10px;
|
||||
|
||||
position: relative;
|
||||
|
||||
list-style-image: url("paintbrush.svg");
|
||||
|
||||
& .toolbarbutton-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media -moz-pref("zen.boosts.enabled", false) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-site-data-boost[boosting],
|
||||
.boost-brush {
|
||||
list-style-image: url("paintbrush-fill.svg");
|
||||
}
|
||||
|
||||
#zen-site-data-security-info {
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
fill: currentColor;
|
||||
@@ -935,3 +996,79 @@
|
||||
list-style-image: url("link.svg");
|
||||
fill-opacity: 0.65;
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle {
|
||||
list-style-image: url("text-title-case.svg");
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="uppercase"] {
|
||||
list-style-image: url("text-uppercase.svg");
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="lowercase"] {
|
||||
list-style-image: url("text-lowercase.svg");
|
||||
}
|
||||
|
||||
#zen-boost-code {
|
||||
list-style-image: url("brackets-curly.svg");
|
||||
}
|
||||
|
||||
#zen-boost-zap {
|
||||
list-style-image: url("bolt.svg");
|
||||
}
|
||||
|
||||
#zen-boost-controls {
|
||||
list-style-image: url("sliders.svg");
|
||||
}
|
||||
|
||||
#zen-boost-invert {
|
||||
list-style-image: url("lightbulb.svg");
|
||||
}
|
||||
|
||||
#zen-boost-disable {
|
||||
list-style-image: url("block.svg");
|
||||
}
|
||||
|
||||
#zen-boost-close {
|
||||
list-style-image: url("close.svg");
|
||||
|
||||
@media (-moz-platform: macos) {
|
||||
list-style-image: url("close-filled-round.svg");
|
||||
}
|
||||
|
||||
@media (-moz-platform: windows) {
|
||||
list-style-image: none;
|
||||
list-style: none;
|
||||
display: inline-flex;
|
||||
|
||||
&::before {
|
||||
font-family: "Segoe Fluent Icons", "Segoe MDL2 Assets";
|
||||
content: "\e8bb";
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-magic-theme {
|
||||
list-style-image: url("sparkles.svg");
|
||||
}
|
||||
|
||||
#zen-boost-shuffle {
|
||||
list-style-image: url("arrow-rotate-anticlockwise.svg");
|
||||
}
|
||||
|
||||
#zen-boost-css-picker {
|
||||
list-style-image: url("eyedropper.svg");
|
||||
}
|
||||
|
||||
#zen-boost-css-inspector {
|
||||
list-style-image: url("hammer.svg");
|
||||
}
|
||||
|
||||
#zen-boost-save {
|
||||
list-style-image: url("downloads.svg");
|
||||
}
|
||||
|
||||
#zen-boost-load {
|
||||
list-style-image: url("open.svg");
|
||||
}
|
||||
|
||||
@@ -7,20 +7,27 @@
|
||||
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/nucleo/arrow-down.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/nucleo/arrow-left.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/nucleo/arrow-right.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-rotate-anticlockwise.svg (../shared/zen-icons/nucleo/arrow-rotate-anticlockwise.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/nucleo/arrow-up.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/nucleo/autoplay-media-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg)
|
||||
* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg)
|
||||
* skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg)
|
||||
* skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg)
|
||||
* skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg)
|
||||
* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg)
|
||||
* skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg)
|
||||
* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg)
|
||||
* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/nucleo/camera.svg)
|
||||
* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/nucleo/canvas-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/nucleo/canvas.svg)
|
||||
* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/nucleo/chevron.svg)
|
||||
* skin/classic/browser/zen-icons/close-filled-round.svg (../shared/zen-icons/nucleo/close-filled-round.svg)
|
||||
* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/nucleo/close.svg)
|
||||
* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/nucleo/container-tab.svg)
|
||||
* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/nucleo/cookies-fill.svg)
|
||||
@@ -44,6 +51,7 @@
|
||||
* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/nucleo/extension-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/nucleo/extension-fill.svg)
|
||||
* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/nucleo/extension.svg)
|
||||
* skin/classic/browser/zen-icons/eyedropper.svg (../shared/zen-icons/nucleo/eyedropper.svg)
|
||||
* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/nucleo/face-sun.svg)
|
||||
* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/nucleo/folder.svg)
|
||||
* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/nucleo/forget.svg)
|
||||
@@ -53,12 +61,14 @@
|
||||
* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/nucleo/geo-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/nucleo/geo-fill.svg)
|
||||
* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/nucleo/geo.svg)
|
||||
* skin/classic/browser/zen-icons/hammer.svg (../shared/zen-icons/nucleo/hammer.svg)
|
||||
* skin/classic/browser/zen-icons/heart-circle-fill.svg (../shared/zen-icons/nucleo/heart-circle-fill.svg)
|
||||
* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/nucleo/help.svg)
|
||||
* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/nucleo/history.svg)
|
||||
* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/nucleo/home.svg)
|
||||
* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/nucleo/info.svg)
|
||||
* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/nucleo/library.svg)
|
||||
* skin/classic/browser/zen-icons/lightbulb.svg (../shared/zen-icons/nucleo/lightbulb.svg)
|
||||
* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/nucleo/link.svg)
|
||||
* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/nucleo/mail.svg)
|
||||
* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/nucleo/manage.svg)
|
||||
@@ -78,6 +88,8 @@
|
||||
* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/nucleo/new-tab-image.svg)
|
||||
* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/nucleo/open.svg)
|
||||
* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/nucleo/page-portrait.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush-fill.svg (../shared/zen-icons/nucleo/paintbrush-fill.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush.svg (../shared/zen-icons/nucleo/paintbrush.svg)
|
||||
* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/nucleo/palette.svg)
|
||||
* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/nucleo/passwords.svg)
|
||||
* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/nucleo/permissions-fill.svg)
|
||||
@@ -110,13 +122,19 @@
|
||||
* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg)
|
||||
* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg)
|
||||
* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg)
|
||||
* skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg)
|
||||
* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg)
|
||||
* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg)
|
||||
* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/nucleo/split.svg)
|
||||
* skin/classic/browser/zen-icons/square-wand-sparkle.svg (../shared/zen-icons/nucleo/square-wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/nucleo/tab-audio-blocked-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/nucleo/tab-audio-muted-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/nucleo/tab-audio-playing-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/nucleo/tab.svg)
|
||||
* skin/classic/browser/zen-icons/text-lowercase.svg (../shared/zen-icons/nucleo/text-lowercase.svg)
|
||||
* skin/classic/browser/zen-icons/text-size.svg (../shared/zen-icons/nucleo/text-size.svg)
|
||||
* skin/classic/browser/zen-icons/text-title-case.svg (../shared/zen-icons/nucleo/text-title-case.svg)
|
||||
* skin/classic/browser/zen-icons/text-uppercase.svg (../shared/zen-icons/nucleo/text-uppercase.svg)
|
||||
* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/nucleo/tool-profiler.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/nucleo/tracking-protection-fill.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/nucleo/tracking-protection.svg)
|
||||
@@ -125,6 +143,7 @@
|
||||
* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/nucleo/unpin.svg)
|
||||
* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/nucleo/video-blocked-fill.svg)
|
||||
* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/nucleo/video-fill.svg)
|
||||
* skin/classic/browser/zen-icons/wand-sparkle.svg (../shared/zen-icons/nucleo/wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/nucleo/window.svg)
|
||||
* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/nucleo/xr-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/nucleo/xr-fill.svg)
|
||||
@@ -136,20 +155,27 @@
|
||||
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/nucleo/arrow-down.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/nucleo/arrow-left.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/nucleo/arrow-right.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-rotate-anticlockwise.svg (../shared/zen-icons/nucleo/arrow-rotate-anticlockwise.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/nucleo/arrow-up.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/nucleo/autoplay-media-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg)
|
||||
* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg)
|
||||
* skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg)
|
||||
* skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg)
|
||||
* skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg)
|
||||
* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg)
|
||||
* skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg)
|
||||
* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg)
|
||||
* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/nucleo/camera.svg)
|
||||
* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/nucleo/canvas-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/nucleo/canvas.svg)
|
||||
* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/nucleo/chevron.svg)
|
||||
* skin/classic/browser/zen-icons/close-filled-round.svg (../shared/zen-icons/nucleo/close-filled-round.svg)
|
||||
* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/nucleo/close.svg)
|
||||
* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/nucleo/container-tab.svg)
|
||||
* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/nucleo/cookies-fill.svg)
|
||||
@@ -173,6 +199,7 @@
|
||||
* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/nucleo/extension-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/nucleo/extension-fill.svg)
|
||||
* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/nucleo/extension.svg)
|
||||
* skin/classic/browser/zen-icons/eyedropper.svg (../shared/zen-icons/nucleo/eyedropper.svg)
|
||||
* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/nucleo/face-sun.svg)
|
||||
* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/nucleo/folder.svg)
|
||||
* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/nucleo/forget.svg)
|
||||
@@ -182,12 +209,14 @@
|
||||
* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/nucleo/geo-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/nucleo/geo-fill.svg)
|
||||
* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/nucleo/geo.svg)
|
||||
* skin/classic/browser/zen-icons/hammer.svg (../shared/zen-icons/nucleo/hammer.svg)
|
||||
* skin/classic/browser/zen-icons/heart-circle-fill.svg (../shared/zen-icons/nucleo/heart-circle-fill.svg)
|
||||
* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/nucleo/help.svg)
|
||||
* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/nucleo/history.svg)
|
||||
* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/nucleo/home.svg)
|
||||
* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/nucleo/info.svg)
|
||||
* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/nucleo/library.svg)
|
||||
* skin/classic/browser/zen-icons/lightbulb.svg (../shared/zen-icons/nucleo/lightbulb.svg)
|
||||
* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/nucleo/link.svg)
|
||||
* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/nucleo/mail.svg)
|
||||
* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/nucleo/manage.svg)
|
||||
@@ -207,6 +236,8 @@
|
||||
* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/nucleo/new-tab-image.svg)
|
||||
* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/nucleo/open.svg)
|
||||
* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/nucleo/page-portrait.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush-fill.svg (../shared/zen-icons/nucleo/paintbrush-fill.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush.svg (../shared/zen-icons/nucleo/paintbrush.svg)
|
||||
* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/nucleo/palette.svg)
|
||||
* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/nucleo/passwords.svg)
|
||||
* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/nucleo/permissions-fill.svg)
|
||||
@@ -239,13 +270,19 @@
|
||||
* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg)
|
||||
* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg)
|
||||
* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg)
|
||||
* skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg)
|
||||
* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg)
|
||||
* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg)
|
||||
* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/nucleo/split.svg)
|
||||
* skin/classic/browser/zen-icons/square-wand-sparkle.svg (../shared/zen-icons/nucleo/square-wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/nucleo/tab-audio-blocked-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/nucleo/tab-audio-muted-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/nucleo/tab-audio-playing-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/nucleo/tab.svg)
|
||||
* skin/classic/browser/zen-icons/text-lowercase.svg (../shared/zen-icons/nucleo/text-lowercase.svg)
|
||||
* skin/classic/browser/zen-icons/text-size.svg (../shared/zen-icons/nucleo/text-size.svg)
|
||||
* skin/classic/browser/zen-icons/text-title-case.svg (../shared/zen-icons/nucleo/text-title-case.svg)
|
||||
* skin/classic/browser/zen-icons/text-uppercase.svg (../shared/zen-icons/nucleo/text-uppercase.svg)
|
||||
* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/nucleo/tool-profiler.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/nucleo/tracking-protection-fill.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/nucleo/tracking-protection.svg)
|
||||
@@ -254,6 +291,7 @@
|
||||
* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/nucleo/unpin.svg)
|
||||
* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/nucleo/video-blocked-fill.svg)
|
||||
* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/nucleo/video-fill.svg)
|
||||
* skin/classic/browser/zen-icons/wand-sparkle.svg (../shared/zen-icons/nucleo/wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/nucleo/window.svg)
|
||||
* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/nucleo/xr-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/nucleo/xr-fill.svg)
|
||||
@@ -265,20 +303,27 @@
|
||||
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/nucleo/arrow-down.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/nucleo/arrow-left.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/nucleo/arrow-right.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-rotate-anticlockwise.svg (../shared/zen-icons/nucleo/arrow-rotate-anticlockwise.svg)
|
||||
* skin/classic/browser/zen-icons/arrow-up.svg (../shared/zen-icons/nucleo/arrow-up.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-blocked.svg (../shared/zen-icons/nucleo/autoplay-media-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg)
|
||||
* skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg)
|
||||
* skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg)
|
||||
* skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg)
|
||||
* skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg)
|
||||
* skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg)
|
||||
* skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg)
|
||||
* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg)
|
||||
* skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg)
|
||||
* skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg)
|
||||
* skin/classic/browser/zen-icons/camera.svg (../shared/zen-icons/nucleo/camera.svg)
|
||||
* skin/classic/browser/zen-icons/canvas-blocked.svg (../shared/zen-icons/nucleo/canvas-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/canvas.svg (../shared/zen-icons/nucleo/canvas.svg)
|
||||
* skin/classic/browser/zen-icons/chevron.svg (../shared/zen-icons/nucleo/chevron.svg)
|
||||
* skin/classic/browser/zen-icons/close-filled-round.svg (../shared/zen-icons/nucleo/close-filled-round.svg)
|
||||
* skin/classic/browser/zen-icons/close.svg (../shared/zen-icons/nucleo/close.svg)
|
||||
* skin/classic/browser/zen-icons/container-tab.svg (../shared/zen-icons/nucleo/container-tab.svg)
|
||||
* skin/classic/browser/zen-icons/cookies-fill.svg (../shared/zen-icons/nucleo/cookies-fill.svg)
|
||||
@@ -302,6 +347,7 @@
|
||||
* skin/classic/browser/zen-icons/extension-blocked.svg (../shared/zen-icons/nucleo/extension-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/extension-fill.svg (../shared/zen-icons/nucleo/extension-fill.svg)
|
||||
* skin/classic/browser/zen-icons/extension.svg (../shared/zen-icons/nucleo/extension.svg)
|
||||
* skin/classic/browser/zen-icons/eyedropper.svg (../shared/zen-icons/nucleo/eyedropper.svg)
|
||||
* skin/classic/browser/zen-icons/face-sun.svg (../shared/zen-icons/nucleo/face-sun.svg)
|
||||
* skin/classic/browser/zen-icons/folder.svg (../shared/zen-icons/nucleo/folder.svg)
|
||||
* skin/classic/browser/zen-icons/forget.svg (../shared/zen-icons/nucleo/forget.svg)
|
||||
@@ -311,12 +357,14 @@
|
||||
* skin/classic/browser/zen-icons/geo-blocked.svg (../shared/zen-icons/nucleo/geo-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/geo-fill.svg (../shared/zen-icons/nucleo/geo-fill.svg)
|
||||
* skin/classic/browser/zen-icons/geo.svg (../shared/zen-icons/nucleo/geo.svg)
|
||||
* skin/classic/browser/zen-icons/hammer.svg (../shared/zen-icons/nucleo/hammer.svg)
|
||||
* skin/classic/browser/zen-icons/heart-circle-fill.svg (../shared/zen-icons/nucleo/heart-circle-fill.svg)
|
||||
* skin/classic/browser/zen-icons/help.svg (../shared/zen-icons/nucleo/help.svg)
|
||||
* skin/classic/browser/zen-icons/history.svg (../shared/zen-icons/nucleo/history.svg)
|
||||
* skin/classic/browser/zen-icons/home.svg (../shared/zen-icons/nucleo/home.svg)
|
||||
* skin/classic/browser/zen-icons/info.svg (../shared/zen-icons/nucleo/info.svg)
|
||||
* skin/classic/browser/zen-icons/library.svg (../shared/zen-icons/nucleo/library.svg)
|
||||
* skin/classic/browser/zen-icons/lightbulb.svg (../shared/zen-icons/nucleo/lightbulb.svg)
|
||||
* skin/classic/browser/zen-icons/link.svg (../shared/zen-icons/nucleo/link.svg)
|
||||
* skin/classic/browser/zen-icons/mail.svg (../shared/zen-icons/nucleo/mail.svg)
|
||||
* skin/classic/browser/zen-icons/manage.svg (../shared/zen-icons/nucleo/manage.svg)
|
||||
@@ -336,6 +384,8 @@
|
||||
* skin/classic/browser/zen-icons/new-tab-image.svg (../shared/zen-icons/nucleo/new-tab-image.svg)
|
||||
* skin/classic/browser/zen-icons/open.svg (../shared/zen-icons/nucleo/open.svg)
|
||||
* skin/classic/browser/zen-icons/page-portrait.svg (../shared/zen-icons/nucleo/page-portrait.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush-fill.svg (../shared/zen-icons/nucleo/paintbrush-fill.svg)
|
||||
* skin/classic/browser/zen-icons/paintbrush.svg (../shared/zen-icons/nucleo/paintbrush.svg)
|
||||
* skin/classic/browser/zen-icons/palette.svg (../shared/zen-icons/nucleo/palette.svg)
|
||||
* skin/classic/browser/zen-icons/passwords.svg (../shared/zen-icons/nucleo/passwords.svg)
|
||||
* skin/classic/browser/zen-icons/permissions-fill.svg (../shared/zen-icons/nucleo/permissions-fill.svg)
|
||||
@@ -368,13 +418,19 @@
|
||||
* skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg)
|
||||
* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg)
|
||||
* skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg)
|
||||
* skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg)
|
||||
* skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg)
|
||||
* skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg)
|
||||
* skin/classic/browser/zen-icons/split.svg (../shared/zen-icons/nucleo/split.svg)
|
||||
* skin/classic/browser/zen-icons/square-wand-sparkle.svg (../shared/zen-icons/nucleo/square-wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-blocked-small.svg (../shared/zen-icons/nucleo/tab-audio-blocked-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-muted-small.svg (../shared/zen-icons/nucleo/tab-audio-muted-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab-audio-playing-small.svg (../shared/zen-icons/nucleo/tab-audio-playing-small.svg)
|
||||
* skin/classic/browser/zen-icons/tab.svg (../shared/zen-icons/nucleo/tab.svg)
|
||||
* skin/classic/browser/zen-icons/text-lowercase.svg (../shared/zen-icons/nucleo/text-lowercase.svg)
|
||||
* skin/classic/browser/zen-icons/text-size.svg (../shared/zen-icons/nucleo/text-size.svg)
|
||||
* skin/classic/browser/zen-icons/text-title-case.svg (../shared/zen-icons/nucleo/text-title-case.svg)
|
||||
* skin/classic/browser/zen-icons/text-uppercase.svg (../shared/zen-icons/nucleo/text-uppercase.svg)
|
||||
* skin/classic/browser/zen-icons/tool-profiler.svg (../shared/zen-icons/nucleo/tool-profiler.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection-fill.svg (../shared/zen-icons/nucleo/tracking-protection-fill.svg)
|
||||
* skin/classic/browser/zen-icons/tracking-protection.svg (../shared/zen-icons/nucleo/tracking-protection.svg)
|
||||
@@ -383,6 +439,7 @@
|
||||
* skin/classic/browser/zen-icons/unpin.svg (../shared/zen-icons/nucleo/unpin.svg)
|
||||
* skin/classic/browser/zen-icons/video-blocked-fill.svg (../shared/zen-icons/nucleo/video-blocked-fill.svg)
|
||||
* skin/classic/browser/zen-icons/video-fill.svg (../shared/zen-icons/nucleo/video-fill.svg)
|
||||
* skin/classic/browser/zen-icons/wand-sparkle.svg (../shared/zen-icons/nucleo/wand-sparkle.svg)
|
||||
* skin/classic/browser/zen-icons/window.svg (../shared/zen-icons/nucleo/window.svg)
|
||||
* skin/classic/browser/zen-icons/xr-blocked.svg (../shared/zen-icons/nucleo/xr-blocked.svg)
|
||||
* skin/classic/browser/zen-icons/xr-fill.svg (../shared/zen-icons/nucleo/xr-fill.svg)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="polyline1"
|
||||
d="M 1.88,3.305 2.288,6.25 5.232,5.843 M 3,13.071 c 1.304,1.919 3.505,3.179 6,3.179 4.004,0 7.25,-3.246 7.25,-7.25 0,-4.004 -3.246,-7.25 -7.25,-7.25 -3.031,0 -5.627,1.86 -6.71,4.5" />
|
||||
</g>
|
||||
</svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/block.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/block.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="circle1"
|
||||
d="M 16.25,9 A 7.25,7.25 0 0 1 9,16.25 7.25,7.25 0 0 1 1.75,9 7.25,7.25 0 0 1 9,1.75 7.25,7.25 0 0 1 16.25,9 Z M 14.127,3.8729999 3.8729999,14.127" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,5 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="1.5" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><polyline points="14 6.25 16.25 4 14 1.75" data-color="color-2"></polyline><path d="M6.596,2.75h-1.846c-1.105,0-2,.896-2,2V13.25c0,1.104,.895,2,2,2H13.25c1.105,0,2-.896,2-2v-4.846"></path><polyline points="11 6.25 8.75 4 11 1.75" data-color="color-2"></polyline></g></svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/bolt.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/bolt.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="M 14.7505,7.25 H 9.49905 L 9.81065,1.9868 C 9.82325,1.7732 9.55085,1.6734 9.42255,1.8446 l -6.3729,8.5055 C 2.92615,10.5149 3.04376,10.75 3.24976,10.75 h 5.25139 l -0.3116,5.2632 c -0.0126,0.2136 0.2598,0.3134 0.3881,0.1422 L 14.9506,7.6499 C 15.0741,7.4851 14.9565,7.25 14.7505,7.25 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
28
src/browser/themes/shared/zen-icons/nucleo/boost.svg
Normal file
28
src/browser/themes/shared/zen-icons/nucleo/boost.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 14.7 16.72"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3">
|
||||
<path
|
||||
id="circle3"
|
||||
class="cls-1"
|
||||
d="M 3.4,14.17 A 0.86000001,0.86000001 0 0 1 2.54,15.03 0.86000001,0.86000001 0 0 1 1.6799999,14.17 0.86000001,0.86000001 0 0 1 2.54,13.31 0.86000001,0.86000001 0 0 1 3.4,14.17 Z M 10.53,11.6 C 11.76,10.89 13.88,8.39 14.12,7.61 14.46,6.52 13.63,5.97 12.86,5.52 12.22,5.13 10.95,6.95 10.25,7.72 m -0.01,0 C 10.79,7.17 11.75,5.7 11.75,5.18 11.75,2.67 6.38,-0.99 6.8,1.16 7.09,2.63 5.72,5.24 5.17,6.11 M 5.08,6.35 C 4.76,6.03 4.24,6.02 3.91,6.33 L 3.22,6.98 C 2.88,7.3 2.87,7.85 3.2,8.18 l 1.69,1.69 -3.54,2.65 c -1.02,0.73 -1.15,2.2 -0.27,3.09 v 0 c 0.87,0.88 2.31,0.79 3.06,-0.18 l 2.71,-3.63 1.6,1.6 c 0.33,0.33 0.85,0.33 1.18,0 l 0.64,-0.63 c 0.33,-0.33 0.33,-0.86 0,-1.19 L 5.06,6.33 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="path2"
|
||||
d="m 11.75,15.25 h 1 c 1.105,0 2,-0.895 2,-2 V 10.625 C 14.75,9.728 15.478,9 16.375,9 15.478,9 14.75,8.272 14.75,7.375 V 4.75 c 0,-1.105 -0.895,-2 -2,-2 h -1 m -5.5,12.5 h -1 c -1.105,0 -2,-0.895 -2,-2 V 10.625 C 3.25,9.728 2.522,9 1.625,9 2.522,9 3.25,8.272 3.25,7.375 V 4.75 c 0,-1.105 0.895,-2 2,-2 h 1" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,28 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="20px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="20px"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="context-fill"
|
||||
stroke="none"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="m 480,-424 116,116 q 11,11 28,11 17,0 28,-11 11,-11 11,-28 0,-17 -11,-28 L 536,-480 652,-596 q 11,-11 11,-28 0,-17 -11,-28 -11,-11 -28,-11 -17,0 -28,11 L 480,-536 364,-652 q -11,-11 -28,-11 -17,0 -28,11 -11,11 -11,28 0,17 11,28 l 116,116 -116,116 q -11,11 -11,28 0,17 11,28 11,11 28,11 17,0 28,-11 z m 0,344 Q 397,-80 324,-111.5 251,-143 197,-197 143,-251 111.5,-324 80,-397 80,-480 q 0,-83 31.5,-156 31.5,-73 85.5,-127 54,-54 127,-85.5 73,-31.5 156,-31.5 83,0 156,31.5 73,31.5 127,85.5 54,54 85.5,127 31.5,73 31.5,156 0,83 -31.5,156 -31.5,73 -85.5,127 -54,54 -127,85.5 Q 563,-80 480,-80 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,5 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" stroke="context-fill" stroke-opacity="context-fill-opacity"><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m9.235 5.735-6.107 6.107a2.142 2.142 0 1 0 3.03 3.03l6.107-6.107M3.128 14.872 1.75 16.25"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m12.265 8.765 2.857-2.857a2.142 2.142 0 1 0-3.03-3.03L9.235 5.735M7.75 4.25l6 6"/></svg>
|
||||
5
src/browser/themes/shared/zen-icons/nucleo/hammer.svg
Normal file
5
src/browser/themes/shared/zen-icons/nucleo/hammer.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" stroke="context-fill" stroke-opacity="context-fill-opacity"><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m11.529 8.721-6.563 6.563a1.59 1.59 0 0 1-2.25 0h0a1.59 1.59 0 0 1 0-2.25l6.563-6.563"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m16.207 8.944-1.521 1.521a1 1 0 0 1-1.414 0l-7.53-7.531L6.676 2l4.191.493a1 1 0 0 1 .59.286l4.75 4.75a1 1 0 0 1 0 1.414Z"/></svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/lightbulb.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/lightbulb.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="M 14,6.75 C 14,3.637 11.154,1.188 7.922,1.863 5.99,2.266 4.447,3.856 4.088,5.796 3.654,8.14 4.859,10.255 6.75,11.211 v 3.039 c 0,1.105 0.895,2 2,2 h 0.5 c 1.105,0 2,-0.895 2,-2 V 11.211 C 12.88,10.387 14,8.701 14,6.75 Z m -7.25,6.5 h 4.5" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,27 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="16px"
|
||||
height="16px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="path2"
|
||||
d="M 8.97,9.291 C 8.301,8.62 7.416,8.25 6.476,8.25 H 6.47 C 5.534,8.252 4.653,8.622 3.99,9.293 c -0.952,0.962 -1.119,1.933 -1.252,2.712 -0.149,0.869 -0.247,1.442 -1.347,2.043 -0.266,0.145 -0.418,0.435 -0.387,0.736 0.032,0.301 0.241,0.554 0.53,0.641 1.058,0.318 2.185,0.58 3.306,0.58 1.438,0 2.867,-0.43 4.13,-1.724 1.371,-1.375 1.371,-3.614 0,-4.989 z m 7.348,-7.36 c -0.79,-0.789 -2.166,-0.79 -2.957,0 L 8.216,7.076 c 0.673,0.25 1.294,0.631 1.817,1.156 0.525,0.527 0.897,1.145 1.14,1.802 l 5.146,-5.146 c 0.815,-0.815 0.815,-2.142 0,-2.958 z" />
|
||||
</g>
|
||||
</svg>
|
||||
36
src/browser/themes/shared/zen-icons/nucleo/paintbrush.svg
Normal file
36
src/browser/themes/shared/zen-icons/nucleo/paintbrush.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="16px"
|
||||
height="16px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="path2"
|
||||
d="m 1.75,14.706 c 2.703,0.812 4.896,0.88 6.689,-0.955 C 9.52,12.666 9.52,10.906 8.439,9.82 7.358,8.734 5.613,8.718 4.523,9.82 2.75,11.612 4.298,13.314 1.75,14.706 Z M 6.956,9.044 13.534,2.466 c 0.621,-0.621 1.629,-0.621 2.25,0 v 0 c 0.621,0.621 0.621,1.629 0,2.25 l -6.578,6.578" />
|
||||
</g>
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3" />
|
||||
</svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/sliders.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/sliders.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g8">
|
||||
<path
|
||||
id="circle8"
|
||||
d="M 8.5,14.25 A 1.75,1.75 0 0 1 6.75,16 1.75,1.75 0 0 1 5,14.25 1.75,1.75 0 0 1 6.75,12.5 1.75,1.75 0 0 1 8.5,14.25 Z m 0,-10.5 A 1.75,1.75 0 0 1 6.75,5.5 1.75,1.75 0 0 1 5,3.75 1.75,1.75 0 0 1 6.75,2 1.75,1.75 0 0 1 8.5,3.75 Z M 12.75,9 A 1.75,1.75 0 0 1 11,10.75 1.75,1.75 0 0 1 9.25,9 1.75,1.75 0 0 1 11,7.25 1.75,1.75 0 0 1 12.75,9 Z m 3.5,5.25 h -5 M 5,14.25 H 1.75 m 14.5,-10.5 h -5 M 5,3.75 H 1.75 M 1.75,9 H 9 m 6.25,0 h 1" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,36 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg7"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs7" />
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g7">
|
||||
<path
|
||||
id="path7"
|
||||
d="m 6.25,9.75 2,2 m -1.74799,-9 H 4.75 c -1.105,0 -2,0.896 -2,2 V 9.00732 M 8.992,15.25 h 4.258 c 1.105,0 2,-0.896 2,-2 V 10.616 M 7.95696,8.04302 2.04396,13.956 c -0.39,0.39 -0.39,1.023 0,1.414 l 0.586,0.585 c 0.39,0.391 1.024,0.391 1.414,0 l 5.913,-5.912 c 0.39004,-0.39108 0.39004,-1.02402 0,-1.41502 l -0.586,-0.58496 c -0.39,-0.3911 -1.023,-0.3911 -1.414,0 z M 15.25,2.5 C 15.664,2.5 16,2.164 16,1.75 16,1.336 15.664,1 15.25,1 14.836,1 14.5,1.336 14.5,1.75 c 0,0.414 0.336,0.75 0.75,0.75 z M 16.659,5.98996 15.396,5.56894 14.975,4.30606 c -0.137,-0.408 -0.812,-0.408 -0.949,0 l -0.421,1.26288 -1.263,0.42102 c -0.204,0.068 -0.342,0.259 -0.342,0.474 0,0.215 0.138,0.406 0.342,0.474 l 1.263,0.42102 0.421,1.263 c 0.068,0.204 0.26,0.34198 0.475,0.34198 0.215,0 0.406,-0.13798 0.475,-0.34198 l 0.421,-1.263 1.263,-0.42102 c 0.204,-0.068 0.342,-0.259 0.342,-0.474 0,-0.215 -0.139,-0.406 -0.343,-0.474 z M 11.744,2.49201 10.798,2.17701 10.482,1.22999 c -0.102,-0.306002 -0.609,-0.306002 -0.711,0 L 9.45499,2.17701 8.509,2.49201 c -0.153,0.051 -0.25699,0.19402 -0.25699,0.35602 0,0.162 0.10399,0.30496 0.25699,0.35596 l 0.94599,0.315 0.31601,0.94702 c 0.051,0.153 0.19401,0.25598 0.355,0.25598 0.161,0 0.305,-0.10398 0.355,-0.25598 l 0.316,-0.94702 0.946,-0.315 C 11.896,3.15299 12,3.01003 12,2.84803 12,2.68603 11.897,2.54301 11.744,2.49201 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg3"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs3" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g3">
|
||||
<path
|
||||
id="path3"
|
||||
d="m 14.75,6.75 v 6.303 c 0,1.214 -0.984,2.197 -2.197,2.197 v 0 c -0.728,0 -1.373,-0.354 -1.773,-0.899 M 12.45,6.75 h 0.204999 C 13.81563,6.75 14.75,7.68437 14.75,8.845 v 0.81 c 0,1.16063 -0.93437,2.095 -2.095001,2.095 H 12.45 c -1.16063,0 -2.095,-0.93437 -2.095,-2.095 v -0.81 c 0,-1.16063 0.93437,-2.095 2.095,-2.095 z M 7.25,9.5 c 0,1.518 -1.232,2.75 -2.75,2.75 H 4.3386 C 3.5993,12.25 3,11.6507 3,10.9114 v 0 C 3,10.2678 3.458,9.7153 4.0904,9.596 L 6.3677,9.1961 C 6.9062,9.0945 7.2576,8.581 7.1689,8.0499 M 3.234,7.234 C 3.72,6.711 4.412,6.5 5.115,6.5 v 0 c 1.179,0 2.134,0.956 2.134,2.134 v 3.616" />
|
||||
</g>
|
||||
</svg>
|
||||
31
src/browser/themes/shared/zen-icons/nucleo/text-size.svg
Normal file
31
src/browser/themes/shared/zen-icons/nucleo/text-size.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2">
|
||||
<path
|
||||
id="line2"
|
||||
d="M 1.38,11.75 H 5.7160001 M 6.346,13.25 3.616,6.75 H 3.48 l -2.73,6.5 m 9.49,-2.5 h 6.040001 M 17.25,13.25 13.565,3.75 h -0.61 l -3.685,9.5" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="m 15.75,6.75 v 6.303 c 0,1.214 -0.984,2.197 -2.197,2.197 v 0 c -0.728,0 -1.373,-0.354 -1.773,-0.899 M 13.45,6.75 h 0.204999 C 14.81563,6.75 15.75,7.68437 15.75,8.845 v 0.81 c 0,1.16063 -0.93437,2.095 -2.095001,2.095 H 13.45 c -1.16063,0 -2.095,-0.93437 -2.095,-2.095 v -0.81 c 0,-1.16063 0.93437,-2.095 2.095,-2.095 z M 2.5899999,10.25 H 8.2279997 M 9.068,12.25 5.498,3.75 H 5.32 l -3.57,8.5" />
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,31 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g1">
|
||||
<path
|
||||
id="path1"
|
||||
d="M 16.53,6.048 C 16.201,5.615 15.361,4.75 13.905,4.75 c -2.136,0 -3.655,1.522 -3.655,4.304 0,2.444 1.492,4.196 3.439,4.196 1.125,0 3.266,-0.173 3.266,-3.958 H 13.797 M 1.84,11.25 H 7.4780002 M 8.318,13.25 4.748,4.75 H 4.57 L 1,13.25" />
|
||||
</g>
|
||||
</svg>
|
||||
36
src/browser/themes/shared/zen-icons/nucleo/wand-sparkle.svg
Normal file
36
src/browser/themes/shared/zen-icons/nucleo/wand-sparkle.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
#filter dumbComments emptyLines substitution
|
||||
# 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/.
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="18px"
|
||||
height="18px"
|
||||
viewBox="0 0 18 18"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs5" />
|
||||
<g
|
||||
stroke-linecap="round"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
stroke="context-fill"
|
||||
stroke-opacity="context-fill-opacity"
|
||||
stroke-linejoin="round"
|
||||
class="nc-icon-wrapper"
|
||||
id="g2" />
|
||||
<g
|
||||
fill="context-fill"
|
||||
fill-opacity="context-fill-opacity"
|
||||
class="nc-icon-wrapper"
|
||||
id="g5">
|
||||
<path
|
||||
id="path5"
|
||||
d="M 9.25,2.5 C 9.664,2.5 10,2.16 10,1.75 10,1.34 9.664,1 9.25,1 8.836,1 8.5,1.34 8.5,1.75 8.5,2.16 8.836,2.5 9.25,2.5 Z m 7.408,9.49 -1.263,-0.42 -0.421,-1.2599 c -0.137,-0.41004 -0.812,-0.41004 -0.949,0 l -0.421,1.2599 -1.263,0.42 c -0.204,0.0701 -0.342,0.26 -0.342,0.47 0,0.2201 0.138,0.4099 0.342,0.4799 l 1.263,0.4201 0.421,1.26 c 0.068,0.21 0.26,0.34 0.475,0.34 0.215,0 0.406,-0.13 0.475,-0.34 l 0.421,-1.26 1.263,-0.4201 c 0.204,-0.0699 0.342,-0.2598 0.342,-0.4799 0,-0.21 -0.139,-0.3999 -0.343,-0.47 z M 7.24297,3.48999 6.29697,3.18006 5.98097,2.22998 c -0.102,-0.3099 -0.609,-0.3099 -0.711,0 L 4.95396,3.18006 4.00797,3.48999 c -0.153,0.05 -0.25699,0.19999 -0.25699,0.35999 0,0.16 0.10399,0.29997 0.25699,0.34997 L 4.95396,4.52014 5.26997,5.4701 c 0.051,0.15 0.194,0.25 0.355,0.25 0.161,0 0.305,-0.1 0.355,-0.25 l 0.316,-0.94996 0.946,-0.32019 c 0.153,-0.0499 0.25699,-0.18987 0.25699,-0.34997 0,-0.1601 -0.10299,-0.30989 -0.25599,-0.35999 z m 3.14403,1.87 2.25,2.25 M 12.5717,2.92528 2.91893,12.583 c -0.3899,0.39 -0.3903,1.0221 -0.0011,1.4127 l 1.08521,1.0892 c 0.391,0.39 1.02399,0.39 1.41499,0 L 15.0701,5.42687 c 0.3898,-0.3901 0.3903,-1.02212 0.0011,-1.41272 L 13.9874,2.92638 C 13.597,2.53458 12.9627,2.53408 12.5717,2.92528 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
31
src/docshell/base/BrowsingContext-h.patch
Normal file
31
src/docshell/base/BrowsingContext-h.patch
Normal file
@@ -0,0 +1,31 @@
|
||||
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
|
||||
index 42e3b22a366db0a8c470b458696d81e4bdbd537c..5d82dae436f502fa087f94f6baa7a280736d0b3e 100644
|
||||
--- a/docshell/base/BrowsingContext.h
|
||||
+++ b/docshell/base/BrowsingContext.h
|
||||
@@ -264,6 +264,8 @@ struct EmbedderColorSchemes {
|
||||
FIELD(IsInBFCache, bool) \
|
||||
FIELD(HasRestoreData, bool) \
|
||||
FIELD(SessionStoreEpoch, uint32_t) \
|
||||
+ FIELD(ZenBoostsData, nscolor) \
|
||||
+ FIELD(IsZenBoostsInverted, bool) \
|
||||
/* Whether we can execute scripts in this BrowsingContext. Has no effect \
|
||||
* unless scripts are also allowed in the parent WindowContext. */ \
|
||||
FIELD(AllowJavascript, bool) \
|
||||
@@ -665,6 +667,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
|
||||
bool FullscreenAllowed() const;
|
||||
|
||||
+ auto ZenBoostsData() const { return GetZenBoostsData(); }
|
||||
+ auto IsZenBoostsInverted() const { return GetIsZenBoostsInverted(); }
|
||||
float FullZoom() const { return GetFullZoom(); }
|
||||
float TextZoom() const { return GetTextZoom(); }
|
||||
|
||||
@@ -1233,6 +1237,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
}
|
||||
|
||||
void DidSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aOldValue);
|
||||
+ void DidSet(FieldIndex<IDX_ZenBoostsData>, nscolor aOldValue);
|
||||
+ void DidSet(FieldIndex<IDX_IsZenBoostsInverted>, bool aOldValue);
|
||||
|
||||
using CanSetResult = syncedcontext::CanSetResult;
|
||||
|
||||
14
src/dom/chrome-webidl/BrowsingContext-webidl.patch
Normal file
14
src/dom/chrome-webidl/BrowsingContext-webidl.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
index 3bb17157c1dba1ca75ad9cfee2c21fcafb2dd0ee..a43f8aa1e4c808418f1cc7ccc69ced12c9ebfef0 100644
|
||||
--- a/dom/chrome-webidl/BrowsingContext.webidl
|
||||
+++ b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
@@ -169,6 +169,9 @@ interface BrowsingContext {
|
||||
|
||||
[SetterThrows] attribute float textZoom;
|
||||
|
||||
+ [SetterThrows] attribute long zenBoostsData;
|
||||
+ [SetterThrows] attribute boolean isZenBoostsInverted;
|
||||
+
|
||||
// Override the dots-per-CSS-pixel scaling factor in this BrowsingContext
|
||||
// and all of its descendants. May only be set on the top BC, and should
|
||||
// only be set from the parent process.
|
||||
21
src/gfx/layers/AnimationInfo-cpp.patch
Normal file
21
src/gfx/layers/AnimationInfo-cpp.patch
Normal file
@@ -0,0 +1,21 @@
|
||||
diff --git a/gfx/layers/AnimationInfo.cpp b/gfx/layers/AnimationInfo.cpp
|
||||
index dd67af463d383dd98da80ce44f41b395e4a9a7cf..9da3849630a80fea2b254659a218e5ed38b44bc0 100644
|
||||
--- a/gfx/layers/AnimationInfo.cpp
|
||||
+++ b/gfx/layers/AnimationInfo.cpp
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "mozilla/MotionPathUtils.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/ScrollContainerFrame.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
@@ -344,7 +345,7 @@ static void SetAnimatable(NonCustomCSSPropertyId aProperty,
|
||||
// resolve currentColor at this moment.
|
||||
nscolor foreground =
|
||||
aFrame->Style()->GetVisitedDependentColor(&nsStyleText::mColor);
|
||||
- aAnimatable = aAnimationValue.GetColor(foreground);
|
||||
+ aAnimatable = zen::nsZenBoostsBackend::FilterColorFromPresContext(aAnimationValue.GetColor(foreground), aFrame->PresContext());
|
||||
break;
|
||||
}
|
||||
case eCSSProperty_opacity:
|
||||
21
src/layout/base/PresShell-cpp.patch
Normal file
21
src/layout/base/PresShell-cpp.patch
Normal file
@@ -0,0 +1,21 @@
|
||||
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
|
||||
index dd4df91b14137c2aa1ffd978faf1f4c91e234f8a..99976e9416d8a5b814bbb3a5421899f450ceeee5 100644
|
||||
--- a/layout/base/PresShell.cpp
|
||||
+++ b/layout/base/PresShell.cpp
|
||||
@@ -136,6 +136,7 @@
|
||||
#include "mozilla/layers/WebRenderLayerManager.h"
|
||||
#include "mozilla/layers/WebRenderUserData.h"
|
||||
#include "mozilla/layout/ScrollAnchorContainer.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
#include "nsAnimationManager.h"
|
||||
#include "nsAutoLayoutPhase.h"
|
||||
#include "nsCOMArray.h"
|
||||
@@ -5546,7 +5547,7 @@ nscolor PresShell::GetDefaultBackgroundColorToDraw() const {
|
||||
if (!mPresContext) {
|
||||
return NS_RGB(255, 255, 255);
|
||||
}
|
||||
- return mPresContext->DefaultBackgroundColor();
|
||||
+ return zen::nsZenBoostsBackend::FilterColorFromPresContext(mPresContext->DefaultBackgroundColor(), mPresContext);
|
||||
}
|
||||
|
||||
void PresShell::UpdateCanvasBackground() {
|
||||
33
src/layout/generic/ViewportFrame-cpp.patch
Normal file
33
src/layout/generic/ViewportFrame-cpp.patch
Normal file
@@ -0,0 +1,33 @@
|
||||
diff --git a/layout/generic/ViewportFrame.cpp b/layout/generic/ViewportFrame.cpp
|
||||
index ea018fff9f549cd8e07b20f4c8073391a6fdf40d..acb9428cf2276ed04064484600024d9e2c7e2bb7 100644
|
||||
--- a/layout/generic/ViewportFrame.cpp
|
||||
+++ b/layout/generic/ViewportFrame.cpp
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsPlaceholderFrame.h"
|
||||
#include "nsSubDocumentFrame.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
@@ -299,15 +300,20 @@ ViewportFrame::BuildDisplayListForViewTransitionsAndNACTopLayer(
|
||||
}
|
||||
}
|
||||
|
||||
+ bool isAnonContent = false;
|
||||
if (dom::Element* container = doc->GetCustomContentContainer()) {
|
||||
if (nsIFrame* frame = container->GetPrimaryFrame()) {
|
||||
MOZ_ASSERT(frame->StyleDisplay()->mTopLayer != StyleTopLayer::None,
|
||||
"ua.css should ensure this");
|
||||
MOZ_ASSERT(frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
|
||||
+ isAnonContent = frame->ContentIsRootOfNativeAnonymousSubtree();
|
||||
BuildDisplayListForTopLayerFrame(aBuilder, frame, &topLayerList);
|
||||
}
|
||||
}
|
||||
|
||||
+ if (auto zenBackend = zen::nsZenBoostsBackend::GetInstance()) {
|
||||
+ zenBackend->mCurrentFrameIsAnonymousContent = isAnonContent;
|
||||
+ }
|
||||
return MaybeWrapTopLayerList(
|
||||
aBuilder, uint16_t(TopLayerIndex::ViewTransitionsAndAnonymousContent),
|
||||
topLayerList);
|
||||
23
src/layout/painting/nsDisplayList-cpp.patch
Normal file
23
src/layout/painting/nsDisplayList-cpp.patch
Normal file
@@ -0,0 +1,23 @@
|
||||
diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp
|
||||
index 3f9aea06257f162ded7d7cf8b32fc6597383cf27..099e825c251d4edf99b41f59160e276f5325217d 100644
|
||||
--- a/layout/painting/nsDisplayList.cpp
|
||||
+++ b/layout/painting/nsDisplayList.cpp
|
||||
@@ -82,6 +82,7 @@
|
||||
#include "mozilla/layers/WebRenderLayerManager.h"
|
||||
#include "mozilla/layers/WebRenderMessages.h"
|
||||
#include "mozilla/layers/WebRenderScrollData.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
#include "nsCSSProps.h"
|
||||
#include "nsCSSRendering.h"
|
||||
#include "nsCSSRenderingGradients.h"
|
||||
@@ -1212,6 +1213,10 @@ void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame,
|
||||
docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed);
|
||||
}
|
||||
|
||||
+ if (auto zenBackend = zen::nsZenBoostsBackend::GetInstance(); zenBackend && !mIsInChromePresContext) {
|
||||
+ zenBackend->onPresShellEntered(pc->Document());
|
||||
+ }
|
||||
+
|
||||
state->mTouchEventPrefEnabledDoc = dom::TouchEvent::PrefEnabled(docShell);
|
||||
|
||||
if (auto* vt = pc->Document()->GetActiveViewTransition()) {
|
||||
30
src/layout/style/StyleColor-cpp.patch
Normal file
30
src/layout/style/StyleColor-cpp.patch
Normal file
@@ -0,0 +1,30 @@
|
||||
diff --git a/layout/style/StyleColor.cpp b/layout/style/StyleColor.cpp
|
||||
index 5fd5f7efba9bcb2febdc9a6b8f8812df673513d8..200e9bf82aacbf38a50f5b99671e403de5b4f61b 100644
|
||||
--- a/layout/style/StyleColor.cpp
|
||||
+++ b/layout/style/StyleColor.cpp
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsStyleStruct.h"
|
||||
+#include "mozilla/nsZenBoostsBackend.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -23,6 +24,8 @@ bool StyleColor::MaybeTransparent() const {
|
||||
template <>
|
||||
StyleAbsoluteColor StyleColor::ResolveColor(
|
||||
const StyleAbsoluteColor& aForegroundColor) const {
|
||||
+ auto ResolveColorInner = [this,
|
||||
+ &aForegroundColor]() -> StyleAbsoluteColor {
|
||||
if (IsAbsolute()) {
|
||||
return AsAbsolute();
|
||||
}
|
||||
@@ -32,6 +35,8 @@ StyleAbsoluteColor StyleColor::ResolveColor(
|
||||
}
|
||||
|
||||
return Servo_ResolveColor(this, &aForegroundColor);
|
||||
+ };
|
||||
+ return zen::nsZenBoostsBackend::ResolveStyleColor(ResolveColorInner());
|
||||
}
|
||||
|
||||
template <>
|
||||
119
src/zen/boosts/ZenBoostStyles.sys.mjs
Normal file
119
src/zen/boosts/ZenBoostStyles.sys.mjs
Normal file
@@ -0,0 +1,119 @@
|
||||
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
const lazy = XPCOMUtils.declareLazy({
|
||||
styleSheetService: {
|
||||
service: "@mozilla.org/content/style-sheet-service;1",
|
||||
iid: Ci.nsIStyleSheetService,
|
||||
},
|
||||
});
|
||||
|
||||
const AGENT_SHEET = Ci.nsIStyleSheetService.AGENT_SHEET;
|
||||
|
||||
export class nsZenBoostStyles {
|
||||
#stylesCache = new Map();
|
||||
|
||||
/**
|
||||
* Retrieves the CSS style string for a given boost configuration.
|
||||
* Caches styles to optimize performance.
|
||||
*
|
||||
* @param {object} boostData - The boost configuration data.
|
||||
* @returns {string} The generated CSS style string.
|
||||
*/
|
||||
getStyleForBoost(boostData, domain) {
|
||||
if (this.#stylesCache.has(domain)) {
|
||||
return this.#stylesCache.get(domain);
|
||||
}
|
||||
|
||||
const rawStyle = this.#generateStyleString(boostData);
|
||||
if (!rawStyle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const styleUri = this.#convertStyleToDataUri(rawStyle);
|
||||
this.#cacheStyle(styleUri, domain);
|
||||
return this.getStyleForBoost(boostData, domain);
|
||||
}
|
||||
|
||||
invalidateStyleForDomain(domain) {
|
||||
if (this.#stylesCache.has(domain)) {
|
||||
const { uri } = this.#stylesCache.get(domain);
|
||||
lazy.styleSheetService.unregisterSheet(uri, AGENT_SHEET);
|
||||
this.#stylesCache.delete(domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a CSS style string based on the boost configuration.
|
||||
*
|
||||
* @param {object} boostData - The boost configuration data.
|
||||
* @returns {string} The generated CSS style string.
|
||||
* @private
|
||||
*/
|
||||
#generateStyleString(boostData) {
|
||||
let style = ``;
|
||||
|
||||
const fontFamily =
|
||||
boostData.fontFamily != "" ? `font-family: ${boostData.fontFamily} !important;` : ``;
|
||||
const fontCase = `text-transform: ${boostData.textCaseOverride} !important;`;
|
||||
|
||||
let zapBlocks = "";
|
||||
if (boostData.zapSelectors) {
|
||||
for (const selector of boostData.zapSelectors) {
|
||||
zapBlocks += `html > body ${selector}:not([zen-zap-unhide]){ display: none !important; }\n`;
|
||||
}
|
||||
|
||||
if (zapBlocks != "") {
|
||||
style += `/* Zen-Zaps */\n`;
|
||||
style += `${zapBlocks}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
if (fontCase != "" || fontFamily != "") {
|
||||
style += `/* Text Format */\n`;
|
||||
style += `body :is(p, h1, h2, h3, h4, h5, a, span, textarea, input) {\n`;
|
||||
style += `${fontFamily}\n`;
|
||||
style += `${fontCase}\n`;
|
||||
style += `}\n`;
|
||||
}
|
||||
|
||||
if (boostData.customCSS != "") {
|
||||
style += `/* USER CSS */\n`;
|
||||
style += `${boostData.customCSS || ""}\n`;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a raw CSS style string into a data URI.
|
||||
*
|
||||
* @param {string} rawStyle - The raw CSS style string.
|
||||
* @returns {string} The data URI representing the CSS style.
|
||||
* @private
|
||||
*/
|
||||
#convertStyleToDataUri(rawStyle) {
|
||||
const encodedStyle = encodeURIComponent(rawStyle);
|
||||
return Services.io.newURI(`data:text/css;charset=utf-8,${encodedStyle}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetches the style from the data URI and caches it.
|
||||
*
|
||||
* @param {string} styleUri - The data URI of the CSS style.
|
||||
* @param {string} domain - The domain associated with the boost.
|
||||
* @returns {string} The cached style sheet URI.
|
||||
* @private
|
||||
*/
|
||||
#cacheStyle(styleUri, domain) {
|
||||
this.#stylesCache.set(domain, {
|
||||
uuid: Services.uuid.generateUUID().toString(),
|
||||
uri: styleUri,
|
||||
});
|
||||
}
|
||||
}
|
||||
1223
src/zen/boosts/ZenBoostsEditor.mjs
Normal file
1223
src/zen/boosts/ZenBoostsEditor.mjs
Normal file
File diff suppressed because it is too large
Load Diff
754
src/zen/boosts/ZenBoostsManager.sys.mjs
Normal file
754
src/zen/boosts/ZenBoostsManager.sys.mjs
Normal file
@@ -0,0 +1,754 @@
|
||||
/* 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 { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs";
|
||||
import { nsZenBoostStyles } from "resource:///modules/zen/boosts/ZenBoostStyles.sys.mjs";
|
||||
|
||||
class nsZenBoostsManager {
|
||||
registeredDomains = new Map(); // <domain, { boosts: <id, boostEntry>, activeBoostID: null }>
|
||||
#stylesManager = new nsZenBoostStyles();
|
||||
|
||||
#saveFilename = "zen-boosts.jsonlz4";
|
||||
|
||||
#file = null;
|
||||
|
||||
constructor() {
|
||||
this.#readBoostsFromStore(this.notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object} New domain entry with empty boost map and active boost id
|
||||
*/
|
||||
#createDomainEntry() {
|
||||
return {
|
||||
boostEntries: new Map(), // <id, boostEntry>
|
||||
activeBoostId: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Will get or create a domain entry for the given domain
|
||||
*
|
||||
* @param {string} domain - The domain of which the data will be fetched
|
||||
* @returns {object} The domain entry
|
||||
*/
|
||||
#getOrCreateDomainEntry(domain) {
|
||||
if (!this.registeredDomains.has(domain)) {
|
||||
this.registeredDomains.set(domain, this.#createDomainEntry());
|
||||
}
|
||||
return this.registeredDomains.get(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will get a domain entry for the given domain
|
||||
*
|
||||
* @param {string} domain - The domain of which the data will be fetched
|
||||
* @returns {object|null} The domain entry
|
||||
*/
|
||||
#getDomainEntry(domain) {
|
||||
if (!this.registeredDomains.has(domain)) {
|
||||
return null;
|
||||
}
|
||||
return this.registeredDomains.get(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will delete the domain entry for a domain
|
||||
*
|
||||
* @param {string} domain - The given domain
|
||||
*/
|
||||
#deleteDomainEntry(domain) {
|
||||
if (this.registeredDomains.has(domain)) {
|
||||
this.registeredDomains.delete(domain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the active boost id for a given domain
|
||||
*
|
||||
* @param {string} domain - The target domain
|
||||
* @returns {string | null} Will return the active boost id or null
|
||||
*/
|
||||
getActiveBoostId(domain) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
if (domainEntry) {
|
||||
return domainEntry.activeBoostId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a boost for the specified domain and persists the change to disk.
|
||||
*
|
||||
* @param {object} boost - The targeted boost.
|
||||
*/
|
||||
deleteBoost(boost) {
|
||||
const { domain, id } = boost;
|
||||
|
||||
if (this.registeredDomains.has(domain)) {
|
||||
let domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
domainEntry.boostEntries.delete(id);
|
||||
}
|
||||
if (domainEntry.activeBoostId == id) {
|
||||
domainEntry.activeBoostId = null;
|
||||
}
|
||||
|
||||
if (domainEntry.boostEntries.size === 0) {
|
||||
this.#deleteDomainEntry(domain);
|
||||
}
|
||||
}
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify(true);
|
||||
|
||||
this.#writeToDisk(this.registeredDomains);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object} Returns a new empty boost entry
|
||||
*/
|
||||
getEmptyBoostEntry() {
|
||||
return {
|
||||
boostData: {
|
||||
boostName: "My Boost",
|
||||
|
||||
dotAngleDeg: 0,
|
||||
dotPos: { x: null, y: null },
|
||||
dotDistance: 0,
|
||||
|
||||
brightness: 0.5,
|
||||
saturation: 0.5,
|
||||
contrast: 0.25,
|
||||
|
||||
fontFamily: "",
|
||||
|
||||
enableColorBoost: false,
|
||||
smartInvert: false,
|
||||
|
||||
// Choses theme based on Zen's workspace theme
|
||||
autoTheme: false,
|
||||
|
||||
textCaseOverride: "none",
|
||||
|
||||
zapSelectors: [],
|
||||
customCSS: "",
|
||||
|
||||
changeWasMade: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create a new boost
|
||||
*
|
||||
* @param {string} domain - The domain which will be affected by the boost
|
||||
* @returns {object|null} The created boost with { id, domain, boostEntry: { boostData } } or null
|
||||
*/
|
||||
createNewBoost(domain) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
const boostEntry = this.getEmptyBoostEntry(domain);
|
||||
|
||||
const domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
domainEntry.boostEntries.set(id, boostEntry);
|
||||
|
||||
const boost = { id, domain, boostEntry };
|
||||
return boost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the boost configuration for the specified domain from storage.
|
||||
*
|
||||
* @param {string} domain - The domain for which to load the boost
|
||||
* @returns {object[] | null} All boosts for the domain or null
|
||||
*/
|
||||
loadBoostsFromStore(domain) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
}
|
||||
|
||||
const boosts = [];
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
domainEntry.boostEntries.forEach((value, key) => {
|
||||
const boost = { id: key, domain, boostEntry: value };
|
||||
boosts.push(boost);
|
||||
});
|
||||
return boosts;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the boost for the specified domain and id from storage.
|
||||
* If no boost is present, a new one will be created.
|
||||
*
|
||||
* @param {string} domain - The domain of the boost
|
||||
* @param {string} id - The id of the boost
|
||||
* @returns {object} Returns the boost with { id, domain, boostEntry: { boostData } }
|
||||
*/
|
||||
loadBoostFromStore(domain, id) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
}
|
||||
if (!id) {
|
||||
console.error("[ZenBoostsManager] ID expected but got null.");
|
||||
}
|
||||
|
||||
const domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
const boostEntry = domainEntry.boostEntries.get(id);
|
||||
return { id, domain, boostEntry };
|
||||
}
|
||||
const boost = this.createNewBoost(domain);
|
||||
return boost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the active boost for the specified domain from storage.
|
||||
*
|
||||
* @param {string} domain - The domain of the boost
|
||||
* @returns {object | null} Returns the boost with { id, domain, boostEntry: { boostData } } or null
|
||||
*/
|
||||
loadActiveBoostFromStore(domain) {
|
||||
if (!domain) {
|
||||
console.error("[ZenBoostsManager] Domain expected but got null.");
|
||||
}
|
||||
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
if (domainEntry.boostEntries.size === 0) {
|
||||
return this.createNewBoost(domain);
|
||||
}
|
||||
|
||||
if (domainEntry.boostEntries.has(domainEntry.activeBoostId)) {
|
||||
const boostEntry = domainEntry.boostEntries.get(domainEntry.activeBoostId);
|
||||
return { id: domainEntry.activeBoostId, domain, boostEntry };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the zap selector to the selectors list and updates the website.
|
||||
*
|
||||
* @param {string} selector - Selector which will hide the elements
|
||||
* @param {string} domain - Domain of the target boost
|
||||
*/
|
||||
addZapSelectorToActive(selector, domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
console.error("[ZenBoostsManager] Active boost is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
|
||||
if (!boostData.zapSelectors) {
|
||||
boostData.zapSelectors = [];
|
||||
}
|
||||
if (!boostData.zapSelectors.includes(selector)) {
|
||||
boostData.zapSelectors.push(selector);
|
||||
}
|
||||
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the zap selector to the selectors list and updates the website.
|
||||
*
|
||||
* @param {string} selector - Selector which will no longer hide the elements
|
||||
* @param {string} domain - Domain of the target boost
|
||||
*/
|
||||
removeZapSelectorToActive(selector, domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
console.error("[ZenBoostsManager] Active boost is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
|
||||
if (boostData.zapSelectors && boostData.zapSelectors.includes(selector)) {
|
||||
const i = boostData.zapSelectors.indexOf(selector);
|
||||
if (i !== -1) {
|
||||
boostData.zapSelectors.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the boost at the domain with the id active
|
||||
*
|
||||
* @param {string} domain The target domain
|
||||
* @param {string} id The target boost id
|
||||
*/
|
||||
makeBoostActiveForDomain(domain, id) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
domainEntry.activeBoostId = id;
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, "zen-boosts-active-change", { id });
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the boost activeness at the domain with the id active
|
||||
*
|
||||
* @param {string} domain The target domain
|
||||
* @param {string} id The target boost id
|
||||
*/
|
||||
toggleBoostActiveForDomain(domain, id) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
if (domainEntry.boostEntries.has(id)) {
|
||||
if (domainEntry.activeBoostId === id) {
|
||||
domainEntry.activeBoostId = null;
|
||||
Services.obs.notifyObservers(null, "zen-boosts-active-change", { id: null });
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify(true);
|
||||
} else {
|
||||
domainEntry.activeBoostId = id;
|
||||
Services.obs.notifyObservers(null, "zen-boosts-active-change", { id });
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all zap selectors from a boost
|
||||
*
|
||||
* @param {string} domain - Target boost domain
|
||||
*/
|
||||
clearZapSelectorsForActive(domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
console.error("[ZenBoostsManager] Active boost is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
boostData.zapSelectors = [];
|
||||
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the boost for a domain in memory and notifies observers of the change.
|
||||
*
|
||||
* @param {object} boost - The target boost
|
||||
*/
|
||||
updateBoost(boost) {
|
||||
const { domain, id, boostEntry } = boost;
|
||||
|
||||
const domainEntry = this.#getOrCreateDomainEntry(domain);
|
||||
domainEntry.boostEntries.set(id, boostEntry);
|
||||
|
||||
this.#stylesManager.invalidateStyleForDomain(domain);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all observers that boost data has been updated.
|
||||
* This triggers a 'zen-boosts-update' notification event.
|
||||
*/
|
||||
notify(unloadStyles = false) {
|
||||
Services.obs.notifyObservers(null, "zen-boosts-update", { unloadStyles });
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a boost configuration to persistent storage and notifies observers.
|
||||
*
|
||||
* @param {object | null} boost - The boost data object to save. If null, only saves existing boosts.
|
||||
*/
|
||||
saveBoostToStore(boost) {
|
||||
if (boost) {
|
||||
this.updateBoost(boost);
|
||||
}
|
||||
|
||||
this.#writeToDisk(this.registeredDomains);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all boosts from persistent storage and updates the registered boosts map.
|
||||
*
|
||||
* @param {Function} done - Callback function to execute after reading is complete.
|
||||
* @private
|
||||
*/
|
||||
#readBoostsFromStore(done) {
|
||||
this.#readFromDisk().then((data) => {
|
||||
this.registeredDomains = data;
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file path where boost data is stored in the user's profile directory.
|
||||
*
|
||||
* @returns {string} The full path to the boost storage file.
|
||||
* @private
|
||||
*/
|
||||
get #storePath() {
|
||||
const profilePath = PathUtils.profileDir;
|
||||
return PathUtils.join(profilePath, this.#saveFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory path where user css is stored in the user's profile directory.
|
||||
*
|
||||
* @returns {string} The full path to the boost userCSS directory.
|
||||
* @private
|
||||
*/
|
||||
get #cssPath() {
|
||||
const profilePath = PathUtils.profileDir;
|
||||
return PathUtils.join(profilePath, "zen-boosts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads boost data from disk, decompresses it, and converts it to a Map.
|
||||
*
|
||||
* @returns {Promise<Map>} A promise that resolves to a Map of domain to boost data.
|
||||
* @private
|
||||
*/
|
||||
async #readFromDisk() {
|
||||
this.#file = new JSONFile({
|
||||
path: this.#storePath,
|
||||
compression: "lz4",
|
||||
});
|
||||
|
||||
await this.#file.load();
|
||||
|
||||
const raw = this.#file.data ?? {};
|
||||
const map = new Map();
|
||||
|
||||
for (const [domain, entry] of Object.entries(raw)) {
|
||||
const boostsMap = new Map();
|
||||
for (const [id, boostEntry] of Object.entries(entry.boostEntries ?? {})) {
|
||||
// Reuinite the user css with the boost data if any exists
|
||||
const userCSS = await this.#readBoostCSS(id);
|
||||
if (userCSS) {
|
||||
boostEntry.boostData.customCSS = userCSS;
|
||||
}
|
||||
|
||||
boostsMap.set(id, boostEntry);
|
||||
}
|
||||
|
||||
map.set(domain, {
|
||||
activeBoostId: entry.activeBoostId ?? null,
|
||||
boostEntries: boostsMap,
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the user CSS of a boost with the given id from the dedicated folder.
|
||||
* Returns null if the file doesn't exist.
|
||||
*
|
||||
* @param {string} id - The id of the boost
|
||||
* @returns {string|null} Returns the user CSS or null
|
||||
*/
|
||||
async #readBoostCSS(id) {
|
||||
const fileName = `${id}.css`;
|
||||
const directoryPath = this.#cssPath;
|
||||
const savePath = PathUtils.join(directoryPath, fileName);
|
||||
|
||||
await IOUtils.makeDirectory(directoryPath, { createAncestors: true });
|
||||
|
||||
if (await IOUtils.exists(savePath)) {
|
||||
const css = await IOUtils.readUTF8(savePath);
|
||||
return css;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes boost data to disk by converting the Map to JSON and compressing it.
|
||||
*
|
||||
* @param {Map} map - The Map of domain to boost data to write to disk.
|
||||
* @private
|
||||
*/
|
||||
#writeToDisk(map) {
|
||||
const obj = {};
|
||||
|
||||
for (const [domain, entry] of map) {
|
||||
const boostsObj = {};
|
||||
for (const [id, boostEntry] of entry.boostEntries) {
|
||||
// Split the user css from the boost data
|
||||
boostsObj[id] = structuredClone(boostEntry);
|
||||
delete boostsObj[id].boostData.customCSS;
|
||||
|
||||
this.#writeBoostCSS(id, boostEntry.boostData.customCSS);
|
||||
}
|
||||
obj[domain] = {
|
||||
activeBoostId: entry.activeBoostId ?? null,
|
||||
boostEntries: boostsObj,
|
||||
};
|
||||
}
|
||||
|
||||
this.#file.data = obj;
|
||||
this.#file.saveSoon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the user CSS of a boost with the given id to a dedicated folder.
|
||||
*
|
||||
* @param {string} id - The id of the boost
|
||||
* @param {string} css - The user CSS
|
||||
*/
|
||||
async #writeBoostCSS(id, css) {
|
||||
const fileName = `${id}.css`;
|
||||
const directoryPath = this.#cssPath;
|
||||
const savePath = PathUtils.join(directoryPath, fileName);
|
||||
|
||||
await IOUtils.makeDirectory(directoryPath, { createAncestors: true });
|
||||
await IOUtils.writeUTF8(savePath, css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any boost is registered and active for the specified domain.
|
||||
*
|
||||
* @param {string} domain - The domain to check for any registered and active boost.
|
||||
* @returns {boolean} True if a boost exists for the domain and is active, false otherwise.
|
||||
*/
|
||||
registeredBoostForDomain(domain) {
|
||||
const domainEntry = this.#getDomainEntry(domain);
|
||||
|
||||
if (domainEntry) {
|
||||
return domainEntry.boostEntries.has(domainEntry.activeBoostId);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a boost can be created for the given URI.
|
||||
* Only HTTP and HTTPS schemes are supported for boosting.
|
||||
*
|
||||
* @param {nsIURI} uri - The URI to check for boost eligibility.
|
||||
* @returns {boolean} True if the URI scheme is http or https, false otherwise.
|
||||
*/
|
||||
canBoostSite(uri) {
|
||||
if (!uri || !uri.schemeIs) {
|
||||
return false;
|
||||
}
|
||||
return uri.schemeIs("http") || uri.schemeIs("https");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets from cache or creates and caches a new style sheet for the given boost data.
|
||||
*
|
||||
* @param {string} domain - The domain of the boosts.
|
||||
* @returns {nsIStyleSheet} The style sheet corresponding to the boost data.
|
||||
*/
|
||||
getStyleSheetForBoost(domain) {
|
||||
const boost = this.loadActiveBoostFromStore(domain);
|
||||
if (!boost) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { boostData } = boost.boostEntry;
|
||||
return this.#stylesManager.getStyleForBoost(boostData, domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the boost editor in a new popup window.
|
||||
*
|
||||
* @param {Window} parentWindow - The parent browser window
|
||||
* @param {Boost} boost - The boost which will be edited
|
||||
* @param {nsIURI} domainUri - The boost which will be edited
|
||||
* @returns {Window|null} The instanced editor window
|
||||
*/
|
||||
openBoostWindow(parentWindow, boost, domainUri) {
|
||||
if (!this.canBoostSite(domainUri)) {
|
||||
console.error("[ZenBoostsManager] Cannot open editor for boost with invalid domain.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const domain = boost.domain;
|
||||
const { availLeft, availWidth } = parentWindow.screen;
|
||||
const screenX = parentWindow.screenX;
|
||||
const screenY = parentWindow.screenY;
|
||||
const width = parentWindow.outerWidth;
|
||||
const height = parentWindow.outerHeight;
|
||||
const editorWidth = 185;
|
||||
const editorHeight = 565;
|
||||
const pad = 20;
|
||||
|
||||
let left = screenX + width + pad;
|
||||
if (this.#areTabsOnRightSide()) {
|
||||
left = screenX - (editorWidth + pad);
|
||||
}
|
||||
|
||||
let top = screenY + height / 2 - editorHeight / 2;
|
||||
|
||||
if (left + editorWidth > availLeft + availWidth || left < availLeft) {
|
||||
left = screenX + width - (editorWidth + pad);
|
||||
if (this.#areTabsOnRightSide()) {
|
||||
left = screenX + pad;
|
||||
}
|
||||
}
|
||||
|
||||
const editor = Services.ww.openWindow(
|
||||
parentWindow,
|
||||
"chrome://browser/content/zen-components/windows/zen-boost-editor.xhtml",
|
||||
null,
|
||||
`left=${left},top=${top},chrome,alwaysontop,resizable=no,minimizable=no,dependent,dialog=yes`,
|
||||
null
|
||||
);
|
||||
|
||||
// Close the editor if the tab is switched
|
||||
parentWindow.gBrowser.tabContainer.addEventListener("TabSelect", editor.close.bind(editor), {
|
||||
once: true,
|
||||
});
|
||||
|
||||
const progressListener = {
|
||||
onLocationChange: (webProgress) => {
|
||||
if (webProgress.isTopLevel) {
|
||||
editor.close();
|
||||
parentWindow.gBrowser.removeTabsProgressListener(progressListener);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
parentWindow.gBrowser.addProgressListener(progressListener);
|
||||
|
||||
// Give the domain
|
||||
editor.domain = domain;
|
||||
editor.openerWindow = parentWindow;
|
||||
editor.focus();
|
||||
|
||||
// Make boost active
|
||||
this.makeBoostActiveForDomain(domain, boost.id);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn a file save dialog and export the selected boost
|
||||
*
|
||||
* @param {Window} parentWindow The window that will instance the file picker
|
||||
* @returns {Promise<void>} Returns a promise which will be resolved after the export action is complete
|
||||
*/
|
||||
exportBoost(parentWindow, boostData) {
|
||||
// From: firefox-main/browser/base/content/browser-commands.js:354
|
||||
// https://searchfox.org/firefox-main/source/browser/base/content/browser-commands.js#355:~:text=try%20%7B-,const,fp%2Eopen%28fpCallback%29%3B
|
||||
|
||||
const nsIFilePicker = Ci.nsIFilePicker;
|
||||
const fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
|
||||
fp.init(
|
||||
parentWindow.browsingContext,
|
||||
`Exporting Boost ${boostData.boostName}...`,
|
||||
nsIFilePicker.modeSave
|
||||
);
|
||||
|
||||
// Sanitizing filename
|
||||
// From: https://gist.github.com/barbietunnie/7bc6d48a424446c44ff4#:~:text=bytes%22%29%3B-,var,%7D
|
||||
const illegalRe = /[\/\?<>\\:\*\|":]/g;
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
|
||||
let sanitized = boostData.boostName
|
||||
.replace(illegalRe, "")
|
||||
.replace(controlRe, "")
|
||||
.replace(reservedRe, "")
|
||||
.replace(windowsReservedRe, "");
|
||||
|
||||
// Replace if resulting filename is empty
|
||||
if (!sanitized) {
|
||||
sanitized = "New Boost";
|
||||
}
|
||||
|
||||
fp.defaultString = sanitized;
|
||||
fp.defaultExtension = "json";
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
fp.open(async (result) => {
|
||||
if (result === nsIFilePicker.returnOK && fp.file) {
|
||||
try {
|
||||
const boostJSON = JSON.stringify(boostData);
|
||||
await IOUtils.writeUTF8(fp.file.path, boostJSON);
|
||||
resolve(true);
|
||||
} catch (ex) {
|
||||
console.error("Export failed:", ex);
|
||||
resolve(false);
|
||||
}
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will spawn a file open dialog and import the selected boost
|
||||
*
|
||||
* @param {Window} parentWindow The window that will instance the file picker
|
||||
* @returns {Promise<object | null>} Returns a promise with the boost data or null
|
||||
*/
|
||||
importBoost(parentWindow) {
|
||||
const nsIFilePicker = Ci.nsIFilePicker;
|
||||
const fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
|
||||
fp.init(parentWindow.browsingContext, "Importing Boost from JSON", nsIFilePicker.modeOpen);
|
||||
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
fp.open(async (result) => {
|
||||
if (result === nsIFilePicker.returnOK && fp.file) {
|
||||
try {
|
||||
const fileContent = await IOUtils.readUTF8(fp.file.path);
|
||||
resolve(JSON.parse(fileContent));
|
||||
} catch (e) {
|
||||
console.error("Import failed:", e);
|
||||
resolve(null);
|
||||
}
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine if tabs are on the right side.
|
||||
* From: ZenDownloadAnimation.mjs
|
||||
*/
|
||||
#areTabsOnRightSide() {
|
||||
return Services.prefs.getBoolPref("zen.tabs.vertical.right-side");
|
||||
}
|
||||
}
|
||||
|
||||
export const gZenBoostsManager = new nsZenBoostsManager();
|
||||
759
src/zen/boosts/ZenSelectorComponent.sys.mjs
Normal file
759
src/zen/boosts/ZenSelectorComponent.sys.mjs
Normal file
@@ -0,0 +1,759 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "overlayLocalization", () => {
|
||||
return new Localization(["browser/zen-boosts.ftl"], true);
|
||||
});
|
||||
|
||||
export class SelectorComponent {
|
||||
document = null;
|
||||
window = null;
|
||||
#initialized = false;
|
||||
#content = null;
|
||||
|
||||
#currentState = null;
|
||||
#selectedElement = null;
|
||||
#lastOverElement = null;
|
||||
|
||||
#relatedValueIndex = 0;
|
||||
|
||||
static STATES = {
|
||||
SELECTING: "selecting",
|
||||
SELECTED: "selected",
|
||||
};
|
||||
|
||||
#zenContentIDs = [];
|
||||
#onSelect = null;
|
||||
#localizationArray = [
|
||||
{ id: "zen-select-this" },
|
||||
{ id: "zen-select-related" },
|
||||
{ id: "zen-select-cancel" },
|
||||
];
|
||||
|
||||
safeAreaPadding = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
|
||||
/**
|
||||
* @param {Document} document Webpage document
|
||||
* @param {ZenBoostsChild} zenBoostsChild Boost JSActor child
|
||||
* @param {string[]} additionalZenContentIDs Additional IDs that will be considered as non website content
|
||||
* @param {Function<string>} onSelect Callback for when a selection was made. The parameter is the css selector
|
||||
* @param {object[]} localizationArray An array of 3 { id: 'l10n-id' } fluent IDs for localization going from the inputs left to right
|
||||
*/
|
||||
constructor(
|
||||
document,
|
||||
zenBoostsChild,
|
||||
additionalZenContentIDs,
|
||||
onSelect,
|
||||
localizationArray = null
|
||||
) {
|
||||
this.document = document;
|
||||
this.window = document.ownerGlobal;
|
||||
this.zenBoostsChild = zenBoostsChild;
|
||||
this.#onSelect = onSelect;
|
||||
|
||||
if (localizationArray != null) {
|
||||
this.#localizationArray = localizationArray;
|
||||
}
|
||||
|
||||
const baseSelectorIDs = ["select-controls", "select-controls-container"];
|
||||
this.#zenContentIDs = [...additionalZenContentIDs, ...baseSelectorIDs];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the zap mode and inserts anonymous content
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.#initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#content = this.document.insertAnonymousContent();
|
||||
this.#content.root.appendChild(this.fragment);
|
||||
this.#initializeElements();
|
||||
this.setState(SelectorComponent.STATES.SELECTING);
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all anonymous content and events
|
||||
*/
|
||||
#initializeElements() {
|
||||
this.hoverDiv = this.getElementById("hover-div");
|
||||
this.selectorComponent = this.getElementById("select-component");
|
||||
|
||||
this.cancelButton = this.getElementById("select-cancel");
|
||||
this.cancelButton.addEventListener("click", this.#cancelSelect.bind(this));
|
||||
|
||||
this.selectThisButton = this.getElementById("select-this");
|
||||
this.selectThisButton.addEventListener("click", this.#handleSelect.bind(this));
|
||||
|
||||
this.selectRelatedSlider = this.getElementById("select-related");
|
||||
this.selectRelatedSlider.addEventListener("click", this.#handleSelect.bind(this));
|
||||
|
||||
// Initialize the related elements button
|
||||
this.selectRelatedSlider.addEventListener("mousemove", (e) => {
|
||||
const r = e.currentTarget.getBoundingClientRect();
|
||||
const mouseX = e.clientX;
|
||||
|
||||
let value = (mouseX - r.left) / r.width;
|
||||
value = Math.max(0, Math.min(1, value));
|
||||
|
||||
e.target.style = `--related-elements-value: ${value * 100}%;`;
|
||||
|
||||
const lastIndex = this.#relatedValueIndex;
|
||||
this.#relatedValueIndex = Math.round(value * 8);
|
||||
|
||||
if (lastIndex != this.#relatedValueIndex) {
|
||||
this.updateHighlight();
|
||||
this.#updatePathTextField();
|
||||
}
|
||||
});
|
||||
|
||||
this.selectRelatedSlider.addEventListener("mouseout", (e) => {
|
||||
e.currentTarget.style.removeProperty("--related-elements-value");
|
||||
|
||||
this.#relatedValueIndex = 0;
|
||||
this.updateHighlight();
|
||||
this.#updatePathTextField();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the zap mode
|
||||
*
|
||||
* @param {STATES} newState New state
|
||||
* @param {*} data Optional additional data
|
||||
*/
|
||||
setState(newState, data = null) {
|
||||
this.#currentState = newState;
|
||||
|
||||
switch (newState) {
|
||||
case SelectorComponent.STATES.SELECTED:
|
||||
this.#selectedElement = data;
|
||||
this.#relatedValueIndex = 0; // Reset index
|
||||
|
||||
this.#hideHoverDiv();
|
||||
this.#showSelectorComponent();
|
||||
this.updateHighlight();
|
||||
this.#updatePathTextField();
|
||||
break;
|
||||
case SelectorComponent.STATES.SELECTING:
|
||||
this.#selectedElement = null;
|
||||
this.#showHoverDiv();
|
||||
this.#hideSelectorComponent();
|
||||
this.removeHighlight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
get content() {
|
||||
if (!this.#content || Cu.isDeadWrapper(this.#content)) {
|
||||
return null;
|
||||
}
|
||||
return this.#content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an anonymous element by id
|
||||
*/
|
||||
getElementById(id) {
|
||||
return this.content.root.getElementById(id);
|
||||
}
|
||||
|
||||
get markup() {
|
||||
// Fetch localizations
|
||||
let [thisElement, relatedElements, cancelAction] = lazy.overlayLocalization.formatMessagesSync(
|
||||
this.#localizationArray
|
||||
);
|
||||
|
||||
return `
|
||||
<template>
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/content/zen-selector.css" />
|
||||
<div id="select-component">
|
||||
<div id="select-controls">
|
||||
<input type="button" id="select-this" value="${thisElement.value}"/>
|
||||
<input type="button" id="select-related" value="${relatedElements.value}"/>
|
||||
<input type="button" id="select-cancel" value="${cancelAction.value}"/>
|
||||
</div>
|
||||
<div id="selector-preview">
|
||||
<p id="selector-element-preview-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="hover-div"></div>
|
||||
<div id="highlight-container"></div>
|
||||
<div id="highlight-shadow" style="display:none;"></div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
get fragment() {
|
||||
if (!this.template) {
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(this.markup, "text/html");
|
||||
this.template = this.document.importNode(doc.querySelector("template"), true);
|
||||
}
|
||||
let fragment = this.template.content.cloneNode(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the addition of the current zap selector
|
||||
*/
|
||||
#handleSelect() {
|
||||
const cssPath = this.getSelectionPath(
|
||||
this.document,
|
||||
this.#relatedValueIndex,
|
||||
this.#selectedElement
|
||||
);
|
||||
|
||||
this.removeHighlight();
|
||||
this.#resetHoverDiv();
|
||||
|
||||
// The highlight should be gone before the onSelect is called
|
||||
this.window.requestAnimationFrame(() => {
|
||||
this.setState(SelectorComponent.STATES.SELECTING);
|
||||
if (cssPath) {
|
||||
this.#onSelect(cssPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancles the current selection operation
|
||||
*/
|
||||
#cancelSelect() {
|
||||
this.setState(SelectorComponent.STATES.SELECTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the selection highlight
|
||||
*/
|
||||
updateHighlight() {
|
||||
this.removeHighlight();
|
||||
this.showHighlight(this.getSelection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights a selection of elements on the website
|
||||
*
|
||||
* @param {List} selection A list of the web elements that should be highlighted
|
||||
*/
|
||||
showHighlight(selection) {
|
||||
const highlightContainerDiv = this.getElementById("highlight-container");
|
||||
highlightContainerDiv.style.display = "initial";
|
||||
|
||||
let counter = 0;
|
||||
for (const element of selection) {
|
||||
if (counter >= 100) {
|
||||
break;
|
||||
} // Avoid too many instanced objects
|
||||
counter++;
|
||||
|
||||
const padding = 5;
|
||||
const elementMeasurement = element?.getBoundingClientRect() ?? undefined;
|
||||
if (elementMeasurement == undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const highlightDiv = this.document.createElement("div");
|
||||
highlightDiv.classList.add("highlight");
|
||||
|
||||
Object.assign(highlightDiv.style, {
|
||||
left: `${elementMeasurement.left - padding}px`,
|
||||
top: `${elementMeasurement.top - padding}px`,
|
||||
width: `${elementMeasurement.width + padding * 2}px`,
|
||||
height: `${elementMeasurement.height + padding * 2}px`,
|
||||
});
|
||||
|
||||
highlightContainerDiv.appendChild(highlightDiv);
|
||||
}
|
||||
|
||||
this.getElementById("highlight-shadow").style.display = "initial";
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the highlight
|
||||
*/
|
||||
removeHighlight() {
|
||||
const highlightContainerDiv = this.getElementById("highlight-container");
|
||||
highlightContainerDiv.style.display = "none";
|
||||
|
||||
// Clear all children elements
|
||||
highlightContainerDiv.innerHTML = "";
|
||||
this.getElementById("highlight-shadow").style.display = "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the path display text on the selector component
|
||||
* based on the current selection
|
||||
*/
|
||||
#updatePathTextField() {
|
||||
const maxPathLength = 64;
|
||||
const selection = this.getSelection();
|
||||
const selectionPath = this.getSelectionPath(
|
||||
this.document,
|
||||
this.#relatedValueIndex,
|
||||
this.#selectedElement
|
||||
);
|
||||
|
||||
if (!selectionPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pathText = `<b>[${selection.length}]</b> ${selectionPath.substring(0, Math.min(maxPathLength, selectionPath.length))}`;
|
||||
this.getElementById("selector-element-preview-text").setHTML(pathText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners and removes the overlay from the Anonymous Content
|
||||
*/
|
||||
tearDown() {
|
||||
if (this.#content) {
|
||||
try {
|
||||
this.document.removeAnonymousContent(this.#content);
|
||||
} catch {
|
||||
/* This might fail but that's not an issue */
|
||||
}
|
||||
}
|
||||
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
this.#initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the hover selection box
|
||||
*/
|
||||
#hideHoverDiv() {
|
||||
this.hoverDiv.style.display = "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the hover selection box
|
||||
*/
|
||||
#showHoverDiv() {
|
||||
this.hoverDiv.style.display = "initial";
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the hover selection box bounds
|
||||
*/
|
||||
#resetHoverDiv() {
|
||||
Object.assign(this.getElementById("hover-div").style, {
|
||||
top: `0px`,
|
||||
left: `0px`,
|
||||
width: `0px`,
|
||||
height: `0px`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the selector component
|
||||
*/
|
||||
#hideSelectorComponent() {
|
||||
this.selectorComponent.style.visibility = "hidden";
|
||||
this.selectorComponent.setAttribute("is-appearing", "false");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the selector component
|
||||
*/
|
||||
#showSelectorComponent() {
|
||||
this.selectorComponent.style.visibility = "visible";
|
||||
this.#setSelectorComponentPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aligned and clamped position for the zap component on the document
|
||||
* relative to #selectedElement
|
||||
*/
|
||||
#setSelectorComponentPosition() {
|
||||
const bounds = this.#selectedElement.getBoundingClientRect();
|
||||
const distance = 8;
|
||||
|
||||
const rect = this.selectorComponent.getBoundingClientRect();
|
||||
const zapComponentWidth = rect.width;
|
||||
const zapComponentHeight = rect.height;
|
||||
|
||||
const windowWidth = this.window.innerWidth;
|
||||
const windowHeight = this.window.innerHeight;
|
||||
const windowPadding = 10;
|
||||
|
||||
this.selectorComponent.setAttribute("is-appearing", "false");
|
||||
|
||||
// This clamps the position so the zap component never goes out of the client bounds and adds a small padding
|
||||
const top = this.clamp(
|
||||
bounds.top + bounds.height + distance,
|
||||
windowPadding + this.safeAreaPadding.top,
|
||||
windowHeight - zapComponentHeight - windowPadding - this.safeAreaPadding.bottom
|
||||
);
|
||||
const left = this.clamp(
|
||||
bounds.left + bounds.width / 2 - zapComponentWidth / 2,
|
||||
windowPadding + this.safeAreaPadding.left,
|
||||
windowWidth - zapComponentWidth - windowPadding - this.safeAreaPadding.right
|
||||
);
|
||||
|
||||
Object.assign(this.selectorComponent.style, {
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
});
|
||||
|
||||
// Adjust transform origin for animation
|
||||
const targetCenterX = bounds.left + bounds.width / 2;
|
||||
const targetBottomY = bounds.top + bounds.height;
|
||||
let originX = this.clamp(targetCenterX - left, 0, zapComponentWidth);
|
||||
let originY = this.clamp(targetBottomY - top, 0, zapComponentHeight);
|
||||
|
||||
this.selectorComponent.style.transformOrigin = `${originX}px ${originY}px`;
|
||||
|
||||
this.window.requestAnimationFrame(() => {
|
||||
this.selectorComponent.setAttribute("is-appearing", "true");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles page events while the overlay is active
|
||||
*
|
||||
* @param {Event} event The event which will be handled by the overlay
|
||||
* @param {boolean} prevent True if the event should be prevented
|
||||
*/
|
||||
handleEvent(event, prevent) {
|
||||
let isZenContent = false;
|
||||
if (event?.originalTarget?.closest) {
|
||||
const closestID = event.originalTarget.closest("div")?.id ?? "";
|
||||
isZenContent = this.#zenContentIDs.includes(closestID);
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case "click":
|
||||
this.#handleClick(event, isZenContent);
|
||||
break;
|
||||
case "mousemove":
|
||||
this.#handleMouseMove(event, isZenContent);
|
||||
break;
|
||||
case "scroll":
|
||||
this.#handlePageChange(event);
|
||||
return;
|
||||
case "resize":
|
||||
this.#handlePageChange(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Let the interactable ids pass through
|
||||
if (isZenContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prevent) {
|
||||
// From ScreenshotsComponentChild.sys.mjs:103
|
||||
// Preventing a pointerdown event throws an error in debug builds.
|
||||
// See https://searchfox.org/mozilla-central/rev/b41bb321fe4bd7d03926083698ac498ebec0accf/widget/WidgetEventImpl.cpp#566-572
|
||||
// Don't prevent the default context menu.
|
||||
if (!["contextmenu", "pointerdown"].includes(event.type)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a page change to update the highlight and selector component position
|
||||
*/
|
||||
#handlePageChange() {
|
||||
if (this.#currentState !== SelectorComponent.STATES.SELECTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateHighlight();
|
||||
this.#setSelectorComponentPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse move event
|
||||
*
|
||||
* @param {Event} event Mouse move event params
|
||||
* @param {boolean} isZenContent Flag if the target element is a zen related element
|
||||
*/
|
||||
#handleMouseMove(event, isZenContent) {
|
||||
if (this.#lastOverElement === event.target) {
|
||||
return;
|
||||
}
|
||||
if (!isZenContent) {
|
||||
this.#lastOverElement = event.target;
|
||||
if (this.#currentState === SelectorComponent.STATES.SELECTING) {
|
||||
this.#showHoverDiv();
|
||||
}
|
||||
} else {
|
||||
this.#hideHoverDiv();
|
||||
}
|
||||
|
||||
if (this.#currentState !== SelectorComponent.STATES.SELECTING || !event.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = event.target.getBoundingClientRect();
|
||||
const padding = 5;
|
||||
|
||||
Object.assign(this.getElementById("hover-div").style, {
|
||||
top: `${bounds.top - padding}px`,
|
||||
left: `${bounds.left - padding}px`,
|
||||
width: `${bounds.width + padding * 2}px`,
|
||||
height: `${bounds.height + padding * 2}px`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse click event
|
||||
*
|
||||
* @param {Event} event Mouse move event params
|
||||
* @param {boolean} isZenContent Flag if the target element is a zen related element
|
||||
*/
|
||||
#handleClick(event, isZenContent) {
|
||||
// Safeguards for protecting anonymous content from being zapped
|
||||
if (
|
||||
event.target === this.document.documentElement ||
|
||||
event.target === this.document.body ||
|
||||
!this.document.documentElement.contains(event.target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#currentState === SelectorComponent.STATES.SELECTING && !isZenContent) {
|
||||
this.setState(SelectorComponent.STATES.SELECTED, event.target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} x Value
|
||||
* @param {number} min Minimum limit
|
||||
* @param {number} max Maximum limit
|
||||
* @returns {number} A value which always lies between min and max
|
||||
*/
|
||||
clamp(x, min, max) {
|
||||
return Math.min(Math.max(x, min), max);
|
||||
}
|
||||
|
||||
/**
|
||||
* When selecting an area to zap there can be a set of zapped elements
|
||||
* since related elements can be included.
|
||||
* This method returns all targeted elements for the zapping process.
|
||||
*
|
||||
* @returns {Element[]} An array of selected elements
|
||||
*/
|
||||
getSelection() {
|
||||
const selector = this.getSelectionPath(
|
||||
this.document,
|
||||
this.#relatedValueIndex,
|
||||
this.#selectedElement
|
||||
);
|
||||
if (!selector) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.document.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for retreiving the css path from the selected element and taking
|
||||
* the related objects into account
|
||||
*/
|
||||
getSelectionPath(document, relatedValueIndex, selectedElement) {
|
||||
let path = [];
|
||||
|
||||
const cssescape = (str) => CSS.escape(str);
|
||||
|
||||
// Body and Html nodes are not considered valid here
|
||||
const isValidNode = (element) => {
|
||||
if (!element) {
|
||||
return false;
|
||||
} else if (element.tagName.toLowerCase() === "body") {
|
||||
return false;
|
||||
} else if (element.tagName.toLowerCase() === "html") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const nthChild = (element) => {
|
||||
if (!element) {
|
||||
return "";
|
||||
}
|
||||
if (!element.parentNode) {
|
||||
return "";
|
||||
}
|
||||
const parent = element.parentNode;
|
||||
const index = Array.prototype.indexOf.call(parent.children, element) + 1;
|
||||
|
||||
if (index === 1) {
|
||||
return ":first-child";
|
||||
}
|
||||
if (index === parent.children.length) {
|
||||
return ":last-child";
|
||||
}
|
||||
return `:nth-child(${index})`;
|
||||
};
|
||||
|
||||
const getIdentification = (element, specifity = 0) => {
|
||||
if (!element) {
|
||||
return "";
|
||||
}
|
||||
const id = specifity < 2 && element.id ? `#${cssescape(element.id)}` : "";
|
||||
const cls =
|
||||
specifity < 1 && element.classList.length
|
||||
? "." + [...element.classList].map((c) => cssescape(c)).join(".")
|
||||
: "";
|
||||
const tag = element.tagName ? element.tagName.toLowerCase() : "";
|
||||
return `${tag}${id}${cls}`;
|
||||
};
|
||||
|
||||
const traverse = (element, specifity = 0, pathArray) => {
|
||||
let currentElement = element;
|
||||
if (!isValidNode(currentElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pathArray.push(nthChild(currentElement));
|
||||
pathArray.push(getIdentification(currentElement, specifity));
|
||||
|
||||
if (currentElement && currentElement.parentNode) {
|
||||
pathArray.push(" > ");
|
||||
pathArray.push(getIdentification(currentElement.parentNode, 0));
|
||||
const tempBuild = build(pathArray);
|
||||
|
||||
while (
|
||||
tempBuild &&
|
||||
document.querySelectorAll(tempBuild).length > 1 &&
|
||||
isValidNode(currentElement.parentNode)
|
||||
) {
|
||||
currentElement = currentElement.parentNode;
|
||||
pathArray.push(" > ");
|
||||
pathArray.push(nthChild(currentElement.parentNode));
|
||||
pathArray.push(getIdentification(currentElement.parentNode, 0));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const build = (pathArray) => pathArray.toReversed().join("");
|
||||
|
||||
const findBestExactSelector = (element, doc) => {
|
||||
let buildMap = new Map();
|
||||
|
||||
let pathExactElement = [];
|
||||
traverse(element, 0, pathExactElement);
|
||||
|
||||
const pathExactElementBuilt = build(pathExactElement);
|
||||
const pathExactElementLength = (
|
||||
pathExactElementBuilt ? doc.querySelectorAll(pathExactElementBuilt) : []
|
||||
).length;
|
||||
|
||||
buildMap.set(pathExactElementLength, pathExactElementBuilt);
|
||||
|
||||
let pathTypeElement = [];
|
||||
pathTypeElement.push(getIdentification(element, 2));
|
||||
if (isValidNode(element.parentNode)) {
|
||||
pathTypeElement.push(" > ");
|
||||
traverse(element.parentNode, 0, pathTypeElement);
|
||||
}
|
||||
|
||||
const pathTypeElementBuilt = build(pathTypeElement);
|
||||
const pathTypeElementLength = (
|
||||
pathTypeElementBuilt ? doc.querySelectorAll(pathTypeElementBuilt) : []
|
||||
).length;
|
||||
|
||||
if (!buildMap.has(pathTypeElementLength)) {
|
||||
buildMap.set(pathTypeElementLength, pathTypeElementBuilt);
|
||||
}
|
||||
|
||||
let parentExactElement = [];
|
||||
if (isValidNode(element.parentNode)) {
|
||||
traverse(element.parentNode, 0, parentExactElement);
|
||||
}
|
||||
|
||||
const pathParentElementBuilt = build(parentExactElement);
|
||||
const pathParentElementLength = (
|
||||
pathParentElementBuilt ? doc.querySelectorAll(pathParentElementBuilt) : []
|
||||
).length;
|
||||
|
||||
if (!buildMap.has(pathParentElementLength)) {
|
||||
buildMap.set(pathParentElementLength, pathParentElementBuilt);
|
||||
}
|
||||
|
||||
let smallestLength = Number.MAX_VALUE;
|
||||
let smallestLengthPath = null;
|
||||
buildMap.forEach((buildPath, length) => {
|
||||
if (length < smallestLength) {
|
||||
smallestLength = length;
|
||||
smallestLengthPath = buildPath;
|
||||
}
|
||||
});
|
||||
|
||||
return smallestLengthPath;
|
||||
};
|
||||
|
||||
if (!isValidNode(selectedElement)) {
|
||||
return null;
|
||||
}
|
||||
switch (relatedValueIndex) {
|
||||
// Sometimes getting the exact element we want is not guaranteed by
|
||||
// one specific path builder. It is best to build multiple possible paths
|
||||
// and decide which one is best.
|
||||
default:
|
||||
case 0:
|
||||
return findBestExactSelector(selectedElement, document);
|
||||
// Getting the exact parent element of selected element
|
||||
case 1:
|
||||
if (!isValidNode(selectedElement.parentNode)) {
|
||||
return null;
|
||||
}
|
||||
traverse(selectedElement.parentNode, 0, path);
|
||||
break;
|
||||
// Getting the type of selected element with same exact parent
|
||||
case 2:
|
||||
path.push(getIdentification(selectedElement, 2));
|
||||
if (isValidNode(selectedElement.parentNode)) {
|
||||
path.push(" > ");
|
||||
path.push(getIdentification(selectedElement.parentNode, 0));
|
||||
}
|
||||
break;
|
||||
// Getting the same type of selected element with same parent type
|
||||
case 3:
|
||||
path.push(getIdentification(selectedElement, 2));
|
||||
if (isValidNode(selectedElement.parentNode)) {
|
||||
path.push(" > ");
|
||||
path.push(getIdentification(selectedElement.parentNode, 2));
|
||||
}
|
||||
break;
|
||||
// Getting the same type of selected element
|
||||
case 4:
|
||||
path.push(getIdentification(selectedElement, 2));
|
||||
break;
|
||||
// Getting the same type of the parent
|
||||
case 5:
|
||||
if (!isValidNode(selectedElement.parentNode)) {
|
||||
return null;
|
||||
}
|
||||
path.push(getIdentification(selectedElement.parentNode, 2));
|
||||
break;
|
||||
// Get any element with same parent
|
||||
case 6:
|
||||
if (!isValidNode(selectedElement.parentNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
path.push("*");
|
||||
path.push(" > ");
|
||||
traverse(selectedElement.parentNode, 0, path);
|
||||
break;
|
||||
// Get elements with similar classes
|
||||
case 7:
|
||||
path.push(getIdentification(selectedElement, 1));
|
||||
break;
|
||||
}
|
||||
|
||||
return build(path);
|
||||
}
|
||||
}
|
||||
599
src/zen/boosts/ZenZapDissolve.sys.mjs
Normal file
599
src/zen/boosts/ZenZapDissolve.sys.mjs
Normal file
@@ -0,0 +1,599 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
export class ZapDissolve {
|
||||
FRAG = `
|
||||
precision mediump float;
|
||||
uniform sampler2D u_DissolveTexture;
|
||||
varying vec2 v_PCoord;
|
||||
varying float v_PLifetime;
|
||||
varying float v_POpacity;
|
||||
|
||||
void main() {
|
||||
if(v_PLifetime >= 1.0) discard;
|
||||
|
||||
vec4 sampled = texture2D(u_DissolveTexture, v_PCoord);
|
||||
if(sampled.a == 0.0) discard;
|
||||
|
||||
float distToCenter = distance(vec2(0.5, 0.5), gl_PointCoord);
|
||||
if(distToCenter > 0.5) discard;
|
||||
|
||||
float alpha = sampled.a * v_POpacity;
|
||||
float brightnessFactor = 1.5;
|
||||
gl_FragColor = vec4(sampled.rgb * brightnessFactor, alpha);
|
||||
}
|
||||
`;
|
||||
|
||||
VERT = `
|
||||
precision highp float;
|
||||
|
||||
uniform float u_AnimationDuration;
|
||||
uniform float u_ParticleSize;
|
||||
uniform float u_ElapsedTime;
|
||||
uniform float u_ViewportWidth;
|
||||
uniform float u_ViewportHeight;
|
||||
uniform float u_TextureWidth;
|
||||
uniform float u_TextureHeight;
|
||||
uniform float u_TextureLeft;
|
||||
uniform float u_TextureTop;
|
||||
|
||||
attribute float a_ParticleIndex;
|
||||
|
||||
varying vec2 v_PCoord;
|
||||
varying float v_PLifetime;
|
||||
varying float v_POpacity;
|
||||
|
||||
float hash1(float n) { return fract(sin(n) * 100000.0); }
|
||||
float hash2(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453123); }
|
||||
float hash3(vec2 p, float offset) {
|
||||
return fract(sin(dot(p, vec2(12.9898 + offset, 78.233 - offset))) * 43758.5453123);
|
||||
}
|
||||
|
||||
/* coordinates */
|
||||
vec2 toNDC(vec2 pixel) {
|
||||
vec2 ndc;
|
||||
ndc.x = (pixel.x / u_ViewportWidth) * 2.0 - 1.0;
|
||||
ndc.y = 1.0 - (pixel.y / u_ViewportHeight) * 2.0;
|
||||
return ndc;
|
||||
}
|
||||
|
||||
float mix1(float a, float b, float t) { return a + (b - a) * t; }
|
||||
|
||||
/* particle layout */
|
||||
vec2 particlePixelPos(float index) {
|
||||
float cols = floor(u_TextureWidth / u_ParticleSize);
|
||||
if(cols < 1.0) cols = 1.0;
|
||||
float row = floor(index / cols);
|
||||
float col = index - row * cols;
|
||||
return vec2(
|
||||
(col + 0.5) * u_ParticleSize + u_TextureLeft,
|
||||
(row + 0.5) * u_ParticleSize + u_TextureTop
|
||||
);
|
||||
}
|
||||
|
||||
mat2 rotate2D(float angle) {
|
||||
float s = sin(angle);
|
||||
float c = cos(angle);
|
||||
return mat2(c, -s, s, c);
|
||||
}
|
||||
|
||||
/* motion */
|
||||
vec4 animatedPosition(vec2 pixelPos, float seed, float seed2, float seed3, float t) {
|
||||
vec2 base = toNDC(pixelPos);
|
||||
|
||||
// Random rotation
|
||||
float rotation1 = (seed - 0.5) * 0.52; // 15 * 2 * (π/180)
|
||||
float rotation2 = (seed2 - 0.5) * 0.52;
|
||||
|
||||
// Random translation
|
||||
float randomRadian = 6.28318 * (seed3 - 0.5);
|
||||
float translateX = 60.0 * cos(randomRadian) / u_ViewportWidth * 2.0;
|
||||
float translateY = 30.0 * sin(randomRadian) / u_ViewportHeight * 2.0;
|
||||
vec2 rotated1 = rotate2D(rotation1 * t) * base;
|
||||
|
||||
vec2 translated = vec2(
|
||||
mix1(rotated1.x, rotated1.x + translateX, t),
|
||||
mix1(rotated1.y, rotated1.y + translateY, t)
|
||||
);
|
||||
|
||||
vec2 finalPos = rotate2D(rotation2 * t) * (translated - base) + base;
|
||||
|
||||
return vec4(finalPos, 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 pixelPos = particlePixelPos(a_ParticleIndex);
|
||||
|
||||
// Multiple seeds for different random values
|
||||
float seed = hash2(pixelPos);
|
||||
float seed2 = hash3(pixelPos, 1.0);
|
||||
float seed3 = hash3(pixelPos, 2.0);
|
||||
|
||||
float normalizedX = (pixelPos.x - u_TextureLeft) / u_TextureWidth;
|
||||
float frameAssignment = (seed + 2.0 * normalizedX) / 3.0;
|
||||
float maxDelay = u_AnimationDuration * 0.42;
|
||||
float delay = frameAssignment * maxDelay;
|
||||
float particleDuration = u_AnimationDuration - delay;
|
||||
float lifetime = clamp((u_ElapsedTime - delay) / particleDuration, 0.0, 1.0);
|
||||
float easedT = 1.0 - pow(1.0 - lifetime, 3.0);
|
||||
|
||||
gl_Position = animatedPosition(pixelPos, seed, seed2, seed3, easedT);
|
||||
gl_PointSize = u_ParticleSize;
|
||||
|
||||
v_PLifetime = lifetime;
|
||||
v_POpacity = 1.0 - lifetime;
|
||||
|
||||
// Texture coordinates
|
||||
float cols = floor(u_TextureWidth / u_ParticleSize);
|
||||
if(cols < 1.0) cols = 1.0;
|
||||
|
||||
float row = floor(a_ParticleIndex / cols);
|
||||
float col = a_ParticleIndex - row * cols;
|
||||
|
||||
v_PCoord = vec2(
|
||||
(col + 0.5) / cols,
|
||||
(row + 0.5) / floor(u_TextureHeight / u_ParticleSize)
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
window = null;
|
||||
document = null;
|
||||
#initialized = false;
|
||||
#content = null;
|
||||
|
||||
#webglContext = null;
|
||||
#program;
|
||||
#animationStartTime = -1;
|
||||
#particlesCount = 0;
|
||||
#duration = 1600;
|
||||
#texture = null;
|
||||
#buffer = null;
|
||||
|
||||
#hasTriggered = false;
|
||||
#rafId = null;
|
||||
|
||||
/**
|
||||
* @param {Document} document Webpage document
|
||||
*/
|
||||
constructor(document) {
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
this.document = document;
|
||||
this.window = document.ownerGlobal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the zap mode and inserts anonymous content
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.#initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#content = this.document.insertAnonymousContent();
|
||||
this.#content.root.appendChild(this.fragment);
|
||||
await this.#initializeElements();
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the elements and contexts required for the rendering
|
||||
*/
|
||||
async #initializeElements() {
|
||||
const canvas = this.getElementById("zen-zap-dissolve-canvas");
|
||||
const gl = canvas.getContext("webgl", {
|
||||
alpha: true,
|
||||
antialias: true,
|
||||
premultipliedAlpha: false,
|
||||
});
|
||||
this.#webglContext = gl;
|
||||
|
||||
if (!gl) {
|
||||
console.error("WebGL is not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.#resizeCanvasToClientSize(canvas);
|
||||
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
|
||||
// Ensure viewport is set correctly
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
|
||||
this.#program = await this.#createProgram(gl, this.VERT, this.FRAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the canvas to the correct size
|
||||
*
|
||||
* @param {Element} canvas
|
||||
*/
|
||||
#resizeCanvasToClientSize(canvas) {
|
||||
const width = this.window.innerWidth;
|
||||
const height = this.window.innerHeight;
|
||||
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
// Set the WebGL viewport to match the canvas size
|
||||
if (this.#webglContext) {
|
||||
this.#webglContext.viewport(0, 0, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the canvas program
|
||||
*
|
||||
* @param {object} gl WebGL context
|
||||
* @param {string} vertContent Content of the vertex shader
|
||||
* @param {string} fragContent Content of the fragment shader
|
||||
* @returns {*} The WebGL program
|
||||
*/
|
||||
async #createProgram(gl, vertContent, fragContent) {
|
||||
const vertexShader = this.#compileShader(gl, vertContent, gl.VERTEX_SHADER);
|
||||
const fragmentShader = this.#compileShader(gl, fragContent, gl.FRAGMENT_SHADER);
|
||||
|
||||
if (!vertexShader || !fragmentShader) {
|
||||
console.error("Program creation aborted: One or more shaders failed to compile.");
|
||||
return null;
|
||||
}
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
|
||||
gl.detachShader(program, vertexShader);
|
||||
gl.detachShader(program, fragmentShader);
|
||||
gl.deleteShader(vertexShader);
|
||||
gl.deleteShader(fragmentShader);
|
||||
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
console.error("Shader program initializiation failure:", gl.getProgramInfoLog(program));
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a shader
|
||||
*
|
||||
* @param {object} gl WebGL context
|
||||
* @param {string} source Shader source
|
||||
* @param {object} type Shader type
|
||||
* @returns {*} The compiled shader
|
||||
*/
|
||||
#compileShader(gl, source, type) {
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
console.error("Shader compilation failure:", gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives image data from a given image
|
||||
*
|
||||
* @param {Image} image The image
|
||||
* @returns {ImageData} Image data
|
||||
*/
|
||||
#getImageData(image) {
|
||||
const canvas =
|
||||
this.getElementById("zen-zap-dissolve-canvas").ownerDocument.createElement("canvas");
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
context.drawImage(image, 0, 0);
|
||||
return context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and loads a WebGL texture from an Image
|
||||
*
|
||||
* @param {Image} image The target image
|
||||
*/
|
||||
#loadTexture(image) {
|
||||
if (this.#texture) {
|
||||
this.#webglContext.deleteTexture(this.#texture);
|
||||
}
|
||||
|
||||
const texture = this.#webglContext.createTexture();
|
||||
this.#webglContext.activeTexture(this.#webglContext.TEXTURE0);
|
||||
this.#webglContext.bindTexture(this.#webglContext.TEXTURE_2D, texture);
|
||||
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_WRAP_S,
|
||||
this.#webglContext.CLAMP_TO_EDGE
|
||||
);
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_WRAP_T,
|
||||
this.#webglContext.CLAMP_TO_EDGE
|
||||
);
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_MIN_FILTER,
|
||||
this.#webglContext.NEAREST
|
||||
);
|
||||
this.#webglContext.texParameteri(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
this.#webglContext.TEXTURE_MAG_FILTER,
|
||||
this.#webglContext.NEAREST
|
||||
);
|
||||
|
||||
if (image && image instanceof Ci.nsIImageLoadingContent && image.width && image.height) {
|
||||
this.#webglContext.texImage2D(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
0,
|
||||
this.#webglContext.RGBA,
|
||||
this.#webglContext.RGBA,
|
||||
this.#webglContext.UNSIGNED_BYTE,
|
||||
image
|
||||
);
|
||||
} else {
|
||||
const imageData = this.#getImageData(image);
|
||||
this.#webglContext.texImage2D(
|
||||
this.#webglContext.TEXTURE_2D,
|
||||
0,
|
||||
this.#webglContext.RGBA,
|
||||
imageData.width,
|
||||
imageData.height,
|
||||
0,
|
||||
this.#webglContext.RGBA,
|
||||
this.#webglContext.UNSIGNED_BYTE,
|
||||
imageData.data
|
||||
);
|
||||
}
|
||||
|
||||
const textureLocation = this.#webglContext.getUniformLocation(
|
||||
this.#program,
|
||||
"u_DissolveTexture"
|
||||
);
|
||||
this.#webglContext.uniform1i(textureLocation, 0);
|
||||
this.#texture = texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the parameters to the program for a given Element
|
||||
*
|
||||
* @param {Element} element
|
||||
*/
|
||||
#bindParameters(element) {
|
||||
const gl = this.#webglContext;
|
||||
const rect = element.getBoundingClientRect();
|
||||
const particleSize = 1;
|
||||
|
||||
const textureWidth = rect.width;
|
||||
const textureHeight = rect.height;
|
||||
|
||||
const cols = Math.floor(textureWidth / particleSize);
|
||||
const rows = Math.floor(textureHeight / particleSize);
|
||||
this.#particlesCount = cols * rows;
|
||||
|
||||
gl.useProgram(this.#program);
|
||||
|
||||
this.#setUniform1f("u_AnimationDuration", this.#duration);
|
||||
this.#setUniform1f("u_ParticleSize", particleSize);
|
||||
this.#setUniform1f("u_ViewportWidth", this.window.innerWidth);
|
||||
this.#setUniform1f("u_ViewportHeight", this.window.innerHeight);
|
||||
this.#setUniform1f("u_TextureWidth", textureWidth);
|
||||
this.#setUniform1f("u_TextureHeight", textureHeight);
|
||||
|
||||
this.#setUniform1f("u_TextureLeft", rect.left);
|
||||
this.#setUniform1f("u_TextureTop", rect.top);
|
||||
|
||||
const indices = new Float32Array(this.#particlesCount);
|
||||
for (let i = 0; i < this.#particlesCount; i++) {
|
||||
indices[i] = i;
|
||||
}
|
||||
|
||||
if (this.#buffer) {
|
||||
gl.deleteBuffer(this.#buffer);
|
||||
}
|
||||
|
||||
const buffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, indices, gl.STATIC_DRAW);
|
||||
|
||||
const aLoc = gl.getAttribLocation(this.#program, "a_ParticleIndex");
|
||||
gl.enableVertexAttribArray(aLoc);
|
||||
gl.vertexAttribPointer(aLoc, 1, gl.FLOAT, false, 0, 0);
|
||||
|
||||
this.#buffer = buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an animation frame
|
||||
*/
|
||||
#requestDraw() {
|
||||
this.#rafId = this.window.requestAnimationFrame((t) => this.#draw(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for setting a uniform in the WebGL context
|
||||
*
|
||||
* @param {string} name The property name
|
||||
* @param {*} value The property value
|
||||
*/
|
||||
#setUniform1f(name, value) {
|
||||
const loc = this.#webglContext.getUniformLocation(this.#program, name);
|
||||
this.#webglContext.uniform1f(loc, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the output to a canvas
|
||||
*
|
||||
* @param {number} time The frametime
|
||||
*/
|
||||
#draw(time) {
|
||||
const gl = this.#webglContext;
|
||||
gl.useProgram(this.#program);
|
||||
|
||||
if (this.#animationStartTime === -1) {
|
||||
this.#animationStartTime = time;
|
||||
}
|
||||
|
||||
const elapsed = time - this.#animationStartTime;
|
||||
if (elapsed > this.#duration) {
|
||||
this.#resetForNextRun();
|
||||
return;
|
||||
}
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
this.#setUniform1f("u_ElapsedTime", elapsed);
|
||||
|
||||
gl.drawArrays(gl.POINTS, 0, this.#particlesCount);
|
||||
this.#requestDraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dissolve effect for the element
|
||||
*
|
||||
* @param {Element} element The element to dissolve
|
||||
*/
|
||||
dissolve(element) {
|
||||
if (!this.#initialized || this.#hasTriggered || !element) {
|
||||
return;
|
||||
}
|
||||
this.#hasTriggered = true;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) {
|
||||
console.warn("[ZapDissolve]: element has zero size. Skipping dissolve");
|
||||
return;
|
||||
}
|
||||
|
||||
const captureCanvas = this.document.createElement("canvas");
|
||||
captureCanvas.width = rect.width;
|
||||
captureCanvas.height = rect.height;
|
||||
const ctx = captureCanvas.getContext("2d");
|
||||
|
||||
const canvas = this.getElementById("zen-zap-dissolve-canvas");
|
||||
this.#resizeCanvasToClientSize(canvas);
|
||||
|
||||
ctx.drawWindow(this.window, rect.left, rect.top, rect.width, rect.height, "rgba(0,0,0,0)");
|
||||
|
||||
this.#loadTexture(captureCanvas);
|
||||
this.#bindParameters(element);
|
||||
|
||||
this.#animationStartTime = -1;
|
||||
this.#requestDraw();
|
||||
}
|
||||
|
||||
get content() {
|
||||
if (!this.#content || Cu.isDeadWrapper(this.#content)) {
|
||||
return null;
|
||||
}
|
||||
return this.#content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an anonymous element by id
|
||||
*/
|
||||
getElementById(id) {
|
||||
return this.content.root.getElementById(id);
|
||||
}
|
||||
|
||||
get markup() {
|
||||
return `
|
||||
<template>
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/content/zen-zap.css" />
|
||||
<canvas id="zen-zap-dissolve-canvas"></canvas>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
get fragment() {
|
||||
if (!this.template) {
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(this.markup, "text/html");
|
||||
this.template = this.document.importNode(doc.querySelector("template"), true);
|
||||
}
|
||||
let fragment = this.template.content.cloneNode(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the class ready for reusage
|
||||
*/
|
||||
#resetForNextRun() {
|
||||
const gl = this.#webglContext;
|
||||
if (gl) {
|
||||
if (this.#texture) {
|
||||
gl.deleteTexture(this.#texture);
|
||||
this.#texture = null;
|
||||
}
|
||||
if (this.#buffer) {
|
||||
gl.deleteBuffer(this.#buffer);
|
||||
this.#buffer = null;
|
||||
}
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
this.#hasTriggered = false;
|
||||
this.#animationStartTime = -1;
|
||||
|
||||
if (this.#rafId) {
|
||||
this.window.cancelAnimationFrame(this.#rafId);
|
||||
this.#rafId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners and removes the overlay from the Anonymous Content
|
||||
*/
|
||||
tearDown() {
|
||||
const gl = this.#webglContext;
|
||||
if (gl) {
|
||||
if (this.#texture) {
|
||||
gl.deleteTexture(this.#texture);
|
||||
this.#texture = null;
|
||||
}
|
||||
if (this.#buffer) {
|
||||
gl.deleteBuffer(this.#buffer);
|
||||
this.#buffer = null;
|
||||
}
|
||||
|
||||
if (this.#program) {
|
||||
gl.deleteProgram(this.#program);
|
||||
this.#program = null;
|
||||
}
|
||||
|
||||
const loseCtx = gl.getExtension("WEBGL_lose_context");
|
||||
loseCtx?.loseContext();
|
||||
}
|
||||
|
||||
if (this.window != null) {
|
||||
this.window.cancelAnimationFrame(this.#rafId);
|
||||
}
|
||||
this.#rafId = null;
|
||||
|
||||
if (this.#content) {
|
||||
try {
|
||||
this.document.removeAnonymousContent(this.#content);
|
||||
} catch {
|
||||
/* This might fail but that's not an issue */
|
||||
}
|
||||
}
|
||||
|
||||
this.#content = null;
|
||||
this.#initialized = false;
|
||||
}
|
||||
}
|
||||
391
src/zen/boosts/ZenZapOverlayChild.sys.mjs
Normal file
391
src/zen/boosts/ZenZapOverlayChild.sys.mjs
Normal file
@@ -0,0 +1,391 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ZapDissolve: "resource:///modules/zen/boosts/ZenZapDissolve.sys.mjs",
|
||||
SelectorComponent: "resource:///modules/zen/boosts/ZenSelectorComponent.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "overlayLocalization", () => {
|
||||
return new Localization(["browser/zen-boosts.ftl"], true);
|
||||
});
|
||||
|
||||
export class ZapOverlay {
|
||||
document = null;
|
||||
window = null;
|
||||
#initialized = false;
|
||||
#content = null;
|
||||
|
||||
#zapContentIDs = ["zap-list", "zap-controls-container"];
|
||||
#selectorComponent = null;
|
||||
|
||||
#dissolvePoolSize = 5;
|
||||
#dissolveEffectPool = [];
|
||||
#currentDissolveIndex = 0;
|
||||
|
||||
/**
|
||||
* @param {*} document Webpage document
|
||||
* @param {*} zenBoostsChild Boost JSActor child
|
||||
*/
|
||||
constructor(document, zenBoostsChild) {
|
||||
this.document = document;
|
||||
this.window = document.ownerGlobal;
|
||||
this.zenBoostsChild = zenBoostsChild;
|
||||
|
||||
this.#selectorComponent = new lazy.SelectorComponent(
|
||||
document,
|
||||
zenBoostsChild,
|
||||
this.#zapContentIDs,
|
||||
this.handleSelectComponentSelect.bind(this),
|
||||
[{ id: "zen-zap-this" }, { id: "zen-zap-related" }, { id: "zen-zap-cancel" }]
|
||||
);
|
||||
|
||||
// Remove the bottom unzap bar to the safe area
|
||||
this.#selectorComponent.safeAreaPadding.bottom = 65;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the zap mode and inserts anonymous content
|
||||
*/
|
||||
async initialize() {
|
||||
if (this.#initialized) {
|
||||
console.warn("[ZenZapOverlayChild]: Skipping initialize because initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.#selectorComponent.initialize();
|
||||
|
||||
this.#content = this.document.insertAnonymousContent();
|
||||
this.#content.root.appendChild(this.fragment);
|
||||
this.#initializeElements();
|
||||
|
||||
this.#initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all anonymous content and events
|
||||
*/
|
||||
#initializeElements() {
|
||||
this.zapDoneButton = this.getElementById("zap-done");
|
||||
this.zapDoneButton.addEventListener("click", this.#disableZapMode.bind(this));
|
||||
|
||||
this.#updateZappedList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily loads the next available dissolve effect.
|
||||
* The returned effect might not currently be ready to trigger again.
|
||||
*
|
||||
* @returns {Promise<ZapDissolve>} Dissolve effect
|
||||
*/
|
||||
async #getNextDissolveEffect() {
|
||||
// Effect does not exist yet, create and initialize
|
||||
if (this.#currentDissolveIndex >= this.#dissolveEffectPool.length) {
|
||||
const dissolveEffect = new lazy.ZapDissolve(this.document);
|
||||
await dissolveEffect.initialize();
|
||||
this.#dissolveEffectPool.push(dissolveEffect);
|
||||
}
|
||||
|
||||
// Capture current index and increment for next call
|
||||
const returnIndex = this.#currentDissolveIndex;
|
||||
this.#currentDissolveIndex = (this.#currentDissolveIndex + 1) % this.#dissolvePoolSize;
|
||||
|
||||
return this.#dissolveEffectPool[returnIndex];
|
||||
}
|
||||
|
||||
get content() {
|
||||
if (!this.#content || Cu.isDeadWrapper(this.#content)) {
|
||||
return null;
|
||||
}
|
||||
return this.#content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an anonymous element by id
|
||||
*/
|
||||
getElementById(id) {
|
||||
return this.content.root.getElementById(id);
|
||||
}
|
||||
|
||||
get markup() {
|
||||
// Fetch localizations
|
||||
let [done] = lazy.overlayLocalization.formatMessagesSync([{ id: "zen-zap-done" }]);
|
||||
|
||||
return `
|
||||
<template>
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/content/zen-zap.css" />
|
||||
<div id="zap-controls-container">
|
||||
<div id="zap-list">
|
||||
</div>
|
||||
<input type="button" id="zap-done" value="${done.value}"/>
|
||||
</div>
|
||||
<div id="zap-border"></div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
get fragment() {
|
||||
if (!this.template) {
|
||||
let parser = new DOMParser();
|
||||
let doc = parser.parseFromString(this.markup, "text/html");
|
||||
this.template = this.document.importNode(doc.querySelector("template"), true);
|
||||
}
|
||||
let fragment = this.template.content.cloneNode(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the onSelect callback from the SelectComponent
|
||||
*/
|
||||
handleSelectComponentSelect(cssSelector) {
|
||||
this.#handleZap(cssSelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies listeners for an update in the zap list
|
||||
*/
|
||||
onZapUpdate() {
|
||||
this.#updateZappedList();
|
||||
this.zenBoostsChild.sendNotify("zap-list-update");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the addition of the given zap selector
|
||||
*/
|
||||
#handleZap(cssPath) {
|
||||
const useDissolve = Services.prefs.getBoolPref("zen.boosts.dissolve-on-zap");
|
||||
if (!this.window.gReduceMotion && useDissolve) {
|
||||
const elements = this.document.querySelectorAll(cssPath);
|
||||
|
||||
let counter = 0;
|
||||
elements.forEach(async (element) => {
|
||||
if (counter > this.#dissolvePoolSize) {
|
||||
return;
|
||||
}
|
||||
counter++;
|
||||
|
||||
this.#getNextDissolveEffect().then((dissolve) => {
|
||||
dissolve.dissolve(element);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.zenBoostsChild.addZapSelector(cssPath);
|
||||
this.onZapUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the removal of a zap selector
|
||||
*
|
||||
* @param {string} cssPath The css selector of the zap
|
||||
*/
|
||||
#handleUnzap(cssPath) {
|
||||
this.zenBoostsChild.removeZapSelector(cssPath);
|
||||
this.onZapUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancles the current zap operation
|
||||
*/
|
||||
#cancelZap() {
|
||||
this.#selectorComponent.setState(lazy.SelectorComponent.STATES.SELECTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for leaving the zap mode
|
||||
*/
|
||||
#disableZapMode() {
|
||||
this.zenBoostsChild.disableZapMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the unzap button list at the bottom of the website
|
||||
*/
|
||||
async #updateZappedList() {
|
||||
const zapList = this.getElementById("zap-list");
|
||||
zapList.innerHTML = "";
|
||||
|
||||
const boost = await this.zenBoostsChild.getWebsiteBoost();
|
||||
const { boostData } = boost.boostEntry;
|
||||
|
||||
boostData.zapSelectors.forEach((selector) => {
|
||||
const unzapButton = zapList.ownerDocument.createElement("input");
|
||||
unzapButton.type = "button";
|
||||
unzapButton.id = "zen-zap-unzap";
|
||||
|
||||
const index = boostData.zapSelectors.indexOf(selector) + 1;
|
||||
const zappedElementsCount =
|
||||
selector == "" ? 0 : this.document.querySelectorAll(selector).length;
|
||||
|
||||
const [tooltip] = lazy.overlayLocalization.formatMessagesSync([
|
||||
{
|
||||
id: "zen-unzap-tooltip",
|
||||
args: { elementCount: zappedElementsCount },
|
||||
},
|
||||
]);
|
||||
|
||||
unzapButton.value = index;
|
||||
unzapButton.title = tooltip.value;
|
||||
|
||||
unzapButton.setAttribute("index", index);
|
||||
unzapButton.setAttribute("selector", selector);
|
||||
zapList.appendChild(unzapButton);
|
||||
});
|
||||
|
||||
// Fetch localizations
|
||||
let [addZapHelper, removeZapHelper] = lazy.overlayLocalization.formatMessagesSync([
|
||||
{ id: "zen-add-zap-helper" },
|
||||
{ id: "zen-remove-zap-helper" },
|
||||
]);
|
||||
|
||||
if (!boostData.zapSelectors.length) {
|
||||
const addZapHelperText = zapList.ownerDocument.createElement("p");
|
||||
addZapHelperText.setHTML(addZapHelper.value);
|
||||
addZapHelperText.classList.add("pcenter");
|
||||
zapList.appendChild(addZapHelperText);
|
||||
} else {
|
||||
const removeZapHelperText = zapList.ownerDocument.createElement("p");
|
||||
removeZapHelperText.setHTML(removeZapHelper.value);
|
||||
zapList.appendChild(removeZapHelperText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse enter event for the unzap buttons
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
#unzapButtonHover(event) {
|
||||
const button = event.originalTarget;
|
||||
const selector = button.getAttribute("selector");
|
||||
this.zenBoostsChild.tempShowZappedElement(selector);
|
||||
|
||||
button.value = "×";
|
||||
|
||||
// This has to run with later, as the elements we are trying to highlight do not exist yet.
|
||||
// The css has to load first and calculate the bounding boxes for the elements before we can highlight.
|
||||
this.window.requestAnimationFrame(() => {
|
||||
const selection = this.document.querySelectorAll(selector);
|
||||
if (selection.length) {
|
||||
this.#selectorComponent.showHighlight(selection);
|
||||
}
|
||||
});
|
||||
|
||||
// Cancle an ongoing select action
|
||||
this.#cancelZap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse exit event for the unzap buttons
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
#unzapButtonUnhover(event) {
|
||||
const button = event.originalTarget;
|
||||
button.value = button.getAttribute("index");
|
||||
|
||||
this.zenBoostsChild.tempHideZappedElement();
|
||||
this.#selectorComponent.removeHighlight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles button clicks from the unzap list
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
#unzapButtonClick(event) {
|
||||
const button = event.originalTarget;
|
||||
const selector = button.getAttribute("selector");
|
||||
|
||||
this.zenBoostsChild.tempHideZappedElement();
|
||||
this.#selectorComponent.removeHighlight();
|
||||
this.#cancelZap();
|
||||
|
||||
// In order to avoid the clicked element being null when the
|
||||
// SelectorComponent receives it, push the list re-creation to the next frame
|
||||
this.window.requestAnimationFrame(() => {
|
||||
this.#handleUnzap(selector);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event listeners and removes the overlay from the Anonymous Content
|
||||
*/
|
||||
tearDown() {
|
||||
this.#selectorComponent.tearDown();
|
||||
this.#selectorComponent = null;
|
||||
|
||||
this.#dissolveEffectPool.forEach((dissolve) => {
|
||||
dissolve.tearDown();
|
||||
});
|
||||
|
||||
if (this.#content) {
|
||||
try {
|
||||
this.document.removeAnonymousContent(this.#content);
|
||||
} catch {
|
||||
/* This might fail but that's not an issue */
|
||||
}
|
||||
}
|
||||
|
||||
this.window = null;
|
||||
this.document = null;
|
||||
this.#initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function handles page events while the overlay is active
|
||||
*
|
||||
* @param {Event} event The event which will be handled by the overlay
|
||||
* @param {boolean} prevent True if the event should be prevented
|
||||
*/
|
||||
handleEvent(event, prevent) {
|
||||
switch (event.type) {
|
||||
case "click":
|
||||
this.#handleClick(event);
|
||||
break;
|
||||
case "mouseover":
|
||||
this.#handleHoverDelegation(event);
|
||||
break;
|
||||
case "mouseout":
|
||||
this.#handleUnhoverDelegation(event);
|
||||
}
|
||||
|
||||
this.#selectorComponent.handleEvent(event, prevent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse click event
|
||||
*
|
||||
* @param {Event} event Mouse move event params
|
||||
*/
|
||||
#handleClick(event) {
|
||||
if (event.originalTarget.id == "zen-zap-unzap") {
|
||||
this.#unzapButtonClick(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse enter event
|
||||
*
|
||||
* @param {Event} event Mouse enter event params
|
||||
*/
|
||||
#handleHoverDelegation(event) {
|
||||
if (event.originalTarget.id == "zen-zap-unzap") {
|
||||
this.#unzapButtonHover(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the mouse leave event
|
||||
*
|
||||
* @param {Event} event Mouse leave event params
|
||||
*/
|
||||
#handleUnhoverDelegation(event) {
|
||||
if (event.originalTarget.id == "zen-zap-unzap") {
|
||||
this.#unzapButtonUnhover(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
505
src/zen/boosts/actors/ZenBoostsChild.sys.mjs
Normal file
505
src/zen/boosts/actors/ZenBoostsChild.sys.mjs
Normal file
@@ -0,0 +1,505 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
const AGENT_SHEET = Ci.nsIStyleSheetService.AGENT_SHEET;
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ZapOverlay: "resource:///modules/zen/boosts/ZenZapOverlayChild.sys.mjs",
|
||||
SelectorComponent: "resource:///modules/zen/boosts/ZenSelectorComponent.sys.mjs",
|
||||
});
|
||||
|
||||
export class ZenBoostsChild extends JSWindowActorChild {
|
||||
#currentSheet = null;
|
||||
#currentState = ZenBoostsChild.STATES.NONE;
|
||||
#preventableEventsAdded = false;
|
||||
#zappedElementsTempShown = [];
|
||||
|
||||
#overlay = null;
|
||||
|
||||
static STATES = {
|
||||
NONE: "none",
|
||||
ZAP: "zap",
|
||||
PICKER: "picker",
|
||||
};
|
||||
|
||||
static OVERLAY_EVENTS = ["click", "pointerdown", "pointermove", "pointerup", "scroll", "resize"];
|
||||
|
||||
// A list of events that will be prevented from
|
||||
// reaching the document
|
||||
static PREVENTABLE_EVENTS = [
|
||||
"click",
|
||||
"pointerdown",
|
||||
"pointermove",
|
||||
"pointerup",
|
||||
"mousemove",
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"mouseenter",
|
||||
"mouseover",
|
||||
"mouseout",
|
||||
"mouseleave",
|
||||
"touchstart",
|
||||
"touchmove",
|
||||
"touchend",
|
||||
"dblclick",
|
||||
"auxclick",
|
||||
"keypress",
|
||||
"contextmenu",
|
||||
"pointerenter",
|
||||
"pointerover",
|
||||
"pointerout",
|
||||
"pointerleave",
|
||||
];
|
||||
|
||||
// Caching the events in sets for performance
|
||||
static ALL_EVENTS_SET = new Set([
|
||||
...ZenBoostsChild.OVERLAY_EVENTS,
|
||||
...ZenBoostsChild.PREVENTABLE_EVENTS,
|
||||
]);
|
||||
|
||||
static PREVENTABLE_SET = new Set(ZenBoostsChild.PREVENTABLE_EVENTS);
|
||||
|
||||
/**
|
||||
* Inverse of https://searchfox.org/firefox-main/rev/1a8c62b86277005f907151bc5389cf5c5091e76f/gfx/src/nsColor.h#23-27
|
||||
*
|
||||
* > #define NS_RGBA(_r, _g, _b, _a) \
|
||||
* > ((nscolor)(((_a) << 24) | ((_b) << 16) | ((_g) << 8) | (_r)))
|
||||
*
|
||||
* Converts [r, g, b] array to NSColor
|
||||
* Make a color out of r,g,b,a values. This assumes that the r,g,b,a
|
||||
* values are properly constrained to 0-255.
|
||||
*
|
||||
* @param {Array} rgb - Array of red, green, blue values [0, 255]
|
||||
* @param {number} rgb.0 - Red color value [0, 255]
|
||||
* @param {number} rgb.1 - Green color value [0, 255]
|
||||
* @param {number} rgb.2 - Blue color value [0, 255]
|
||||
* @param {number} contrast - Contrast value (default 255)
|
||||
* @returns {number} NSColor integer representation
|
||||
*/
|
||||
#rgbToNSColor([r, g, b], contrast = 255) {
|
||||
// Note will be using the alpha channel for contrast, since the colors will always
|
||||
// be fully opaque and we need an extra byte to store the contrast value. This allows
|
||||
// us to still use an nscolor as parameter instead of having to deal with WebIDL structs
|
||||
// shenanigans.
|
||||
return (contrast << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
|
||||
/**
|
||||
* From ZenGradientGenerator.mjs
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from https://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*
|
||||
* @param {number} h The hue
|
||||
* @param {number} s The saturation
|
||||
* @param {number} l The lightness
|
||||
* @returns {Array} The RGB representation
|
||||
*/
|
||||
#hslToRgb(h, s, l) {
|
||||
const { round } = Math;
|
||||
let r, g, b;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = this.#hueToRgb(p, q, h + 1 / 3);
|
||||
g = this.#hueToRgb(p, q, h);
|
||||
b = this.#hueToRgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return [round(r * 255), round(g * 255), round(b * 255)];
|
||||
}
|
||||
|
||||
/**
|
||||
* From ZenGradientGenerator.mjs
|
||||
* Inverse of hslToRgb
|
||||
* Converts an RGB color value to HSL. Conversion formula
|
||||
* adapted from https://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h, s, and l in the set [0, 1].
|
||||
*
|
||||
* @param {number} r The red value
|
||||
* @param {number} g The green value
|
||||
* @param {number} b The blue value
|
||||
* @returns {Array} The HSL representation
|
||||
*/
|
||||
#rgbToHsl(r, g, b) {
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
let max = Math.max(r, g, b);
|
||||
let min = Math.min(r, g, b);
|
||||
let d = max - min;
|
||||
let h;
|
||||
if (d === 0) {
|
||||
h = 0;
|
||||
} else if (max === r) {
|
||||
h = ((g - b) / d) % 6;
|
||||
} else if (max === g) {
|
||||
h = (b - r) / d + 2;
|
||||
} else if (max === b) {
|
||||
h = (r - g) / d + 4;
|
||||
}
|
||||
let l = (min + max) / 2;
|
||||
let s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
||||
return [h * 60, s, l];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles DOM events for the actor. Applies boost settings when a document
|
||||
* element is inserted.
|
||||
*
|
||||
* @param {Event} event - The DOM event to handle.
|
||||
*/
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "unload":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
this.disableZapMode();
|
||||
}
|
||||
this.#removeEventListeners();
|
||||
break;
|
||||
case "DOMDocElementInserted":
|
||||
this.#applyBoostForPageIfAvailable();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleZapEvent(event) {
|
||||
if (ZenBoostsChild.ALL_EVENTS_SET.has(event.type)) {
|
||||
this.#overlay.handleEvent(event, ZenBoostsChild.PREVENTABLE_SET.has(event.type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds necessary event listeners to the document
|
||||
* to prevent content interactions
|
||||
*/
|
||||
#addEventListeners() {
|
||||
this._handleZapEvent = this.handleZapEvent.bind(this);
|
||||
this._disableZapMode = this.disableZapMode.bind(this);
|
||||
|
||||
for (let event of ZenBoostsChild.OVERLAY_EVENTS) {
|
||||
this.document.addEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
|
||||
for (let event of ZenBoostsChild.PREVENTABLE_EVENTS) {
|
||||
this.document.addEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
this.#preventableEventsAdded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the event listeners from the document
|
||||
*/
|
||||
#removeEventListeners() {
|
||||
for (let event of ZenBoostsChild.OVERLAY_EVENTS) {
|
||||
this.document.removeEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
|
||||
if (this.#preventableEventsAdded) {
|
||||
for (let event of ZenBoostsChild.PREVENTABLE_EVENTS) {
|
||||
this.document.removeEventListener(event, this._handleZapEvent, true);
|
||||
}
|
||||
}
|
||||
this.#preventableEventsAdded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles messages received from the parent actor.
|
||||
*
|
||||
* @param {object} message - The message object containing name and data.
|
||||
*/
|
||||
async receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "ZenBoost:BoostDataUpdated": {
|
||||
const { unloadStyles = false } = message.data || {};
|
||||
this.#applyBoostForPageIfAvailable(unloadStyles);
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:DisableZapMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
this.disableZapMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:DisablePickerMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.PICKER) {
|
||||
this.disablePickerMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:ToggleZapMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
this.#startZappingOverlay();
|
||||
} else if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
this.disableZapMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:TogglePickerMode":
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
this.#startPickingOverlay();
|
||||
} else if (this.#currentState === ZenBoostsChild.STATES.PICKER) {
|
||||
this.disablePickerMode();
|
||||
}
|
||||
break;
|
||||
case "ZenBoost:ZapModeEnabled":
|
||||
return this.#currentState === ZenBoostsChild.STATES.ZAP;
|
||||
case "ZenBoost:SelectorPickerModeEnabled":
|
||||
return this.#currentState === ZenBoostsChild.STATES.PICKER;
|
||||
case "ZenBoost:OpenInspector":
|
||||
this.sendAsyncMessage("ZenBoost:OpenInspector");
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* From ZenGradientGenerator.mjs
|
||||
* Helper function for hslToRgb conversion
|
||||
*/
|
||||
#hueToRgb(p, q, t) {
|
||||
if (t < 0) {
|
||||
t += 1;
|
||||
}
|
||||
if (t > 1) {
|
||||
t -= 1;
|
||||
}
|
||||
if (t < 1 / 6) {
|
||||
return p + (q - p) * 6 * t;
|
||||
}
|
||||
if (t < 1 / 2) {
|
||||
return q;
|
||||
}
|
||||
if (t < 2 / 3) {
|
||||
return p + (q - p) * (2 / 3 - t) * 6;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aquires the boost data for this website
|
||||
*
|
||||
* @returns {object} Boost data for the current website
|
||||
*/
|
||||
getWebsiteBoost() {
|
||||
const domain = this.browsingContext.topWindow?.location?.host;
|
||||
if (!domain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.sendQuery("ZenBoost:GetBoostForDomain", domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the boost settings for the current page if available.
|
||||
*
|
||||
* @param {boolean} unloadStyles - Indicates whether to unload styles.
|
||||
*/
|
||||
async #applyBoostForPageIfAvailable(unloadStyles = false) {
|
||||
const browsingContext = this.browsingContext;
|
||||
|
||||
// Prevent applying boosts to iframes or non-top-level browsing contexts.
|
||||
// It makes the tab crash if we try to load stylesheets into an iframe's
|
||||
if (!browsingContext || browsingContext.parent !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const boost = await this.getWebsiteBoost();
|
||||
|
||||
if (unloadStyles) {
|
||||
this.#unloadCurrentStyleSheet();
|
||||
}
|
||||
|
||||
if (boost) {
|
||||
const { boostData } = boost.boostEntry;
|
||||
if (boost.styleSheet) {
|
||||
this.#loadStyleSheet(boost.styleSheet);
|
||||
}
|
||||
|
||||
browsingContext.isZenBoostsInverted = boostData.smartInvert;
|
||||
if (boostData.enableColorBoost) {
|
||||
if (boostData.autoTheme) {
|
||||
// Workspace color is converted to the HSL color space
|
||||
let primaryGradientColor = boost.workspaceGradient[0]?.c ?? [0, 0, 0.6];
|
||||
boost.workspaceGradient.forEach((color) => {
|
||||
if (color.isPrimary) {
|
||||
primaryGradientColor = this.#rgbToHsl(color.c[0], color.c[1], color.c[2]);
|
||||
}
|
||||
});
|
||||
|
||||
// Workspace color is converted back to rgb
|
||||
// using the same modifiers as the color above
|
||||
primaryGradientColor = this.#hslToRgb(
|
||||
primaryGradientColor[0] / 360,
|
||||
primaryGradientColor[1] * (1 - boostData.saturation),
|
||||
0.1 + primaryGradientColor[2] * 0.6 * boostData.brightness
|
||||
);
|
||||
|
||||
const rgbColor = primaryGradientColor;
|
||||
const nsColor = this.#rgbToNSColor(rgbColor, (1 - boostData.contrast) * 255);
|
||||
browsingContext.zenBoostsData = nsColor;
|
||||
} else {
|
||||
let colorWheelColor = this.#hslToRgb(
|
||||
boostData.dotAngleDeg / 360,
|
||||
/* already is [0, 1] */
|
||||
boostData.dotDistance * (1 - boostData.saturation),
|
||||
/* lightness range from [0.1, 0.7] */
|
||||
0.1 + boostData.dotDistance * 0.6 * boostData.brightness
|
||||
);
|
||||
|
||||
const rgbColor = colorWheelColor;
|
||||
const nsColor = this.#rgbToNSColor(rgbColor, (1 - boostData.contrast) * 255);
|
||||
browsingContext.zenBoostsData = nsColor;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
browsingContext.zenBoostsData = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given stylesheet into the website
|
||||
*
|
||||
* @param {object} styleSheet The stylesheet
|
||||
*/
|
||||
#loadStyleSheet(styleSheet) {
|
||||
const browsingContext = this.browsingContext;
|
||||
styleSheet.uri = Services.io.newURI(styleSheet.uri);
|
||||
|
||||
if (this.#currentSheet?.uuid !== styleSheet.uuid) {
|
||||
if (this.#currentSheet) {
|
||||
this.#unloadCurrentStyleSheet();
|
||||
}
|
||||
browsingContext.window.windowGlobalChild.browsingContext.window.windowUtils.loadSheet(
|
||||
styleSheet.uri,
|
||||
AGENT_SHEET
|
||||
);
|
||||
this.#currentSheet = styleSheet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the currently loaded stylesheet
|
||||
*/
|
||||
#unloadCurrentStyleSheet() {
|
||||
const browsingContext = this.browsingContext;
|
||||
if (this.#currentSheet && browsingContext) {
|
||||
browsingContext.window.windowGlobalChild.browsingContext.window.windowUtils.removeSheet(
|
||||
this.#currentSheet.uri,
|
||||
AGENT_SHEET
|
||||
);
|
||||
this.#currentSheet = null;
|
||||
}
|
||||
}
|
||||
|
||||
async #startZappingOverlay() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.ZAP) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.ZAP;
|
||||
|
||||
this.#overlay = new lazy.ZapOverlay(this.document, this);
|
||||
this.#overlay.initialize();
|
||||
|
||||
this.#addEventListeners();
|
||||
this.sendNotify("zap-state-update");
|
||||
}
|
||||
|
||||
async #startPickingOverlay() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.PICKER) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.PICKER;
|
||||
|
||||
this.#overlay = new lazy.SelectorComponent(
|
||||
this.document,
|
||||
this,
|
||||
[], // No additional IDs needed
|
||||
this.onPickerSelection.bind(this)
|
||||
);
|
||||
|
||||
this.#overlay.initialize();
|
||||
|
||||
this.#addEventListeners();
|
||||
this.sendNotify("selector-picker-state-update", "onenable");
|
||||
}
|
||||
|
||||
onPickerSelection(cssSelector) {
|
||||
this.sendNotify("selector-picker-picked", cssSelector);
|
||||
}
|
||||
|
||||
addZapSelector(selector) {
|
||||
const domain = this.browsingContext.topWindow?.location?.host;
|
||||
this.sendQuery("ZenBoost:ZapSelector", {
|
||||
action: "add",
|
||||
selector,
|
||||
domain,
|
||||
});
|
||||
}
|
||||
|
||||
removeZapSelector(selector) {
|
||||
const domain = this.browsingContext.topWindow?.location?.host;
|
||||
this.sendQuery("ZenBoost:ZapSelector", {
|
||||
action: "remove",
|
||||
selector,
|
||||
domain,
|
||||
});
|
||||
}
|
||||
|
||||
async tempShowZappedElement(selector) {
|
||||
this.document.querySelectorAll(selector).forEach((element) => {
|
||||
element.setAttribute("zen-zap-unhide", "true");
|
||||
});
|
||||
|
||||
if (!this.#zappedElementsTempShown.includes(selector)) {
|
||||
this.#zappedElementsTempShown.push(selector);
|
||||
}
|
||||
}
|
||||
|
||||
async tempHideZappedElement() {
|
||||
this.#zappedElementsTempShown.forEach((selector) => {
|
||||
this.document.querySelectorAll(selector).forEach((element) => {
|
||||
element.removeAttribute("zen-zap-unhide");
|
||||
});
|
||||
});
|
||||
|
||||
this.#zappedElementsTempShown = [];
|
||||
}
|
||||
|
||||
disableZapMode() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.NONE;
|
||||
|
||||
this.#overlay?.tearDown();
|
||||
this.#overlay = null;
|
||||
|
||||
this.#removeEventListeners();
|
||||
this.sendNotify("zap-state-update");
|
||||
}
|
||||
|
||||
disablePickerMode() {
|
||||
if (this.#currentState === ZenBoostsChild.STATES.NONE) {
|
||||
return;
|
||||
}
|
||||
this.#currentState = ZenBoostsChild.STATES.NONE;
|
||||
|
||||
this.#overlay?.tearDown();
|
||||
this.#overlay = null;
|
||||
|
||||
this.#removeEventListeners();
|
||||
this.sendNotify("selector-picker-state-update", "ondisable");
|
||||
}
|
||||
|
||||
sendNotify(topic, msg = null) {
|
||||
this.sendAsyncMessage("ZenBoost:Notify", { topic, msg });
|
||||
}
|
||||
}
|
||||
159
src/zen/boosts/actors/ZenBoostsParent.sys.mjs
Normal file
159
src/zen/boosts/actors/ZenBoostsParent.sys.mjs
Normal file
@@ -0,0 +1,159 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
gZenBoostsManager: "resource:///modules/zen/boosts/ZenBoostsManager.sys.mjs",
|
||||
});
|
||||
|
||||
export class ZenBoostsParent extends JSWindowActorParent {
|
||||
static OBSERVERS = [
|
||||
"zen-boosts-update",
|
||||
"zen-space-gradient-update",
|
||||
"zen-boosts-disable-zap",
|
||||
"zen-boosts-disable-picker",
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates a new ZenBoostsParent actor instance and sets up an observer
|
||||
* for boost update notifications.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._observe = this.observe.bind(this);
|
||||
ZenBoostsParent.OBSERVERS.forEach((observe) => {
|
||||
Services.obs.addObserver(this._observe, observe);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the actor is destroyed. Cleans up the observer.
|
||||
*/
|
||||
didDestroy() {
|
||||
ZenBoostsParent.OBSERVERS.forEach((observe) => {
|
||||
Services.obs.removeObserver(this._observe, observe);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer callback that handles boost update notifications.
|
||||
* Sends a message to child actors when boosts are updated.
|
||||
*
|
||||
* @param {object} subject - The subject of the notification.
|
||||
* @param {string} topic - The topic of the notification.
|
||||
*/
|
||||
observe(subject, topic) {
|
||||
switch (topic) {
|
||||
case "zen-boosts-update":
|
||||
case "zen-space-gradient-update":
|
||||
this.sendAsyncMessage("ZenBoost:BoostDataUpdated", { unloadStyles: true });
|
||||
break;
|
||||
case "zen-boosts-disable-zap":
|
||||
this.sendAsyncMessage("ZenBoost:DisableZapMode");
|
||||
break;
|
||||
case "zen-boosts-disable-picker":
|
||||
this.sendAsyncMessage("ZenBoost:DisablePickerMode");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles messages received from child actors.
|
||||
* Retrieves boost data for a domain when requested.
|
||||
*
|
||||
* @param {object} message - The message object containing name and data.
|
||||
* @returns {Promise<object | null>} A promise that resolves to the boost data or null.
|
||||
*/
|
||||
async receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "ZenBoost:OpenInspector": {
|
||||
const { require } = ChromeUtils.importESModule(
|
||||
"resource://devtools/shared/loader/Loader.sys.mjs"
|
||||
);
|
||||
|
||||
const { gDevTools } = require("devtools/client/framework/devtools");
|
||||
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let tab = win.gBrowser.selectedTab;
|
||||
|
||||
let toolbox = gDevTools.getToolboxForTab(tab);
|
||||
|
||||
if (toolbox) {
|
||||
await gDevTools.closeToolboxForTab(tab);
|
||||
} else {
|
||||
await gDevTools.showToolboxForTab(tab, "inspector");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:Notify": {
|
||||
Services.obs.notifyObservers(null, message.data.topic, message.data.msg);
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:ZapSelector": {
|
||||
const data = message.data;
|
||||
|
||||
if (!data.action) {
|
||||
break;
|
||||
}
|
||||
if (!data.selector) {
|
||||
break;
|
||||
}
|
||||
if (!data.domain) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.action == "add") {
|
||||
lazy.gZenBoostsManager.addZapSelectorToActive(data.selector, data.domain);
|
||||
} else if (data.action == "remove") {
|
||||
lazy.gZenBoostsManager.removeZapSelectorToActive(data.selector, data.domain);
|
||||
} else if (data.action == "clear") {
|
||||
lazy.gZenBoostsManager.clearZapSelectorsForActive(data.domain);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "ZenBoost:GetBoostForDomain": {
|
||||
const domain = message.data;
|
||||
const embedder = this.browsingContext.top.embedderElement;
|
||||
|
||||
if (!embedder || !domain) {
|
||||
break;
|
||||
}
|
||||
|
||||
const exists = lazy.gZenBoostsManager.registeredBoostForDomain(domain);
|
||||
if (!exists) {
|
||||
break;
|
||||
}
|
||||
|
||||
const topWindowIsDarkMode =
|
||||
embedder.ownerGlobal.getComputedStyle(embedder).colorScheme === "dark";
|
||||
|
||||
const boost = lazy.gZenBoostsManager.loadActiveBoostFromStore(domain);
|
||||
const currentWorkspace =
|
||||
await this.browsingContext.topChromeWindow.gZenWorkspaces.getActiveWorkspace();
|
||||
|
||||
const styleData = await lazy.gZenBoostsManager.getStyleSheetForBoost(domain);
|
||||
|
||||
return {
|
||||
...boost,
|
||||
topWindowIsDarkMode,
|
||||
workspaceGradient: currentWorkspace.theme.gradientColors,
|
||||
styleSheet: styleData
|
||||
? {
|
||||
uuid: styleData.uuid,
|
||||
uri: styleData.uri.spec,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
console.warn(`[ZenBoostsParent]: Unknown message: ${message.name}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
15
src/zen/boosts/components.conf
Normal file
15
src/zen/boosts/components.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Classes = [
|
||||
{
|
||||
'cid': '{7d46aa4e-7486-4f77-ab47-81125f1a5723}',
|
||||
'interfaces': ['nsIZenBoostsBackend'],
|
||||
'contract_ids': ['@mozilla.org/zen/boosts-backend;1'],
|
||||
'type': 'zen::nsZenBoostsBackend',
|
||||
'headers': ['mozilla/nsZenBoostsBackend.h'],
|
||||
'js_name': 'boosts',
|
||||
'processes': ProcessSelector.CONTENT_PROCESS_ONLY,
|
||||
},
|
||||
]
|
||||
14
src/zen/boosts/jar.inc.mn
Normal file
14
src/zen/boosts/jar.inc.mn
Normal 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/.
|
||||
|
||||
content/browser/zen-styles/zen-boosts.css (../../zen/boosts/zen-boosts.css)
|
||||
content/browser/zen-styles/content/zen-zap.css (../../zen/boosts/zen-zap.css)
|
||||
content/browser/zen-styles/content/zen-selector.css (../../zen/boosts/zen-selector.css)
|
||||
content/browser/zen-styles/zen-advanced-color-options.css (../../zen/boosts/zen-advanced-color-options.css)
|
||||
|
||||
# Windows
|
||||
* content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.xhtml)
|
||||
|
||||
# Images
|
||||
content/browser/zen-images/boost-indicator.svg (../../zen/images/boost-indicator.svg)
|
||||
36
src/zen/boosts/moz.build
Normal file
36
src/zen/boosts/moz.build
Normal file
@@ -0,0 +1,36 @@
|
||||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES.zen.boosts += [
|
||||
"ZenBoostsEditor.mjs",
|
||||
"ZenBoostsManager.sys.mjs",
|
||||
"ZenBoostStyles.sys.mjs",
|
||||
"ZenSelectorComponent.sys.mjs",
|
||||
"ZenZapDissolve.sys.mjs",
|
||||
"ZenZapOverlayChild.sys.mjs",
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.actors += [
|
||||
"actors/ZenBoostsChild.sys.mjs",
|
||||
"actors/ZenBoostsParent.sys.mjs",
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
"nsIZenBoostsBackend.idl",
|
||||
]
|
||||
|
||||
EXPORTS.mozilla += [
|
||||
"nsZenBoostsBackend.h",
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
"nsZenBoostsBackend.cpp",
|
||||
]
|
||||
|
||||
XPCOM_MANIFESTS += [
|
||||
"components.conf",
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
||||
XPIDL_MODULE = "zen_boosts"
|
||||
19
src/zen/boosts/nsIZenBoostsBackend.idl
Normal file
19
src/zen/boosts/nsIZenBoostsBackend.idl
Normal file
@@ -0,0 +1,19 @@
|
||||
/* 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 "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* @brief Interface for Zen boosts backend.
|
||||
*/
|
||||
[scriptable, uuid(7d46aa4e-7486-4f77-ab47-81125f1a5723)]
|
||||
interface nsIZenBoostsBackend : nsISupports {
|
||||
%{C++
|
||||
/*
|
||||
* @brief Called when the presshell is entered. See nsDisplayListBuilder::EnterPresShell
|
||||
* for context.
|
||||
*/
|
||||
auto onPresShellEntered(nsPresContext* aPresContext) -> void;
|
||||
%}
|
||||
};
|
||||
297
src/zen/boosts/nsZenBoostsBackend.cpp
Normal file
297
src/zen/boosts/nsZenBoostsBackend.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
/* 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 "nsZenBoostsBackend.h"
|
||||
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
#include "mozilla/ServoStyleConsts.h"
|
||||
#include "mozilla/ServoStyleConstsInlines.h"
|
||||
#include "mozilla/MediaFeatureChange.h"
|
||||
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
|
||||
#define COLOR_CHANNEL_MIDPOINT 128
|
||||
|
||||
// It's a bit of a hacky solution, but instead of using alpha as what it is
|
||||
// (opacity), we use it to store contrast information for now.
|
||||
// We do this primarily to avoid having to deal with WebIDL structs and
|
||||
// serialization/deserialization between parent and content processes.
|
||||
#define NS_GET_CONTRAST(_c) NS_GET_A(_c)
|
||||
|
||||
#define MARK_MEDIA_FEATURE_CHANGED(_pc) \
|
||||
(_pc)->MediaFeatureValuesChanged( \
|
||||
{mozilla::RestyleHint::RecascadeSubtree(), NS_STYLE_HINT_VISUAL, \
|
||||
mozilla::MediaFeatureChangeReason::PreferenceChange}, \
|
||||
mozilla::MediaFeatureChangePropagation::All);
|
||||
|
||||
#define TRIGGER_PRES_CONTEXT_RESTYLE() \
|
||||
WalkPresContexts([&](nsPresContext* aPc) { \
|
||||
MARK_MEDIA_FEATURE_CHANGED(aPc); \
|
||||
});
|
||||
|
||||
using BrowsingContext = mozilla::dom::BrowsingContext;
|
||||
|
||||
template <typename Callback>
|
||||
void BrowsingContext::WalkPresContexts(Callback&& aCallback) {
|
||||
PreOrderWalk([&](BrowsingContext* aContext) {
|
||||
if (nsIDocShell* shell = aContext->GetDocShell()) {
|
||||
if (RefPtr pc = shell->GetPresContext()) {
|
||||
aCallback(pc.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called when the ZenBoostsData field is set on a browsing context.
|
||||
* Triggers a restyle if the boost data has changed.
|
||||
* @param aOldValue The previous value of the boost data.
|
||||
*/
|
||||
void BrowsingContext::DidSet(FieldIndex<IDX_ZenBoostsData>,
|
||||
ZenBoostData aOldValue) {
|
||||
MOZ_ASSERT(IsTop());
|
||||
if (ZenBoostsData() == aOldValue) {
|
||||
return;
|
||||
}
|
||||
PresContextAffectingFieldChanged();
|
||||
TRIGGER_PRES_CONTEXT_RESTYLE();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called when the IsZenBoostsInverted field is set on a browsing
|
||||
* context. Triggers a restyle if the value has changed.
|
||||
* @param aOldValue The previous value of the IsZenBoostsInverted flag.
|
||||
*/
|
||||
void BrowsingContext::DidSet(FieldIndex<IDX_IsZenBoostsInverted>,
|
||||
bool aOldValue) {
|
||||
MOZ_ASSERT(IsTop());
|
||||
if (IsZenBoostsInverted() == aOldValue) {
|
||||
return;
|
||||
}
|
||||
PresContextAffectingFieldChanged();
|
||||
TRIGGER_PRES_CONTEXT_RESTYLE();
|
||||
}
|
||||
|
||||
namespace zen {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief Clamps a value to the range [0, 255] using branchless operations.
|
||||
* @param v The value to clamp.
|
||||
* @return The clamped value in the range [0, 255].
|
||||
*/
|
||||
static __inline int32_t clamp255(int32_t v) {
|
||||
// llvm x86 is poor at ternary operator, so use branchless min/max.
|
||||
v = v & ~(v >> 31);
|
||||
return (v | ((255 - v) >> 31)) & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a color filter to transform an original color toward an accent
|
||||
* color. Preserves the original color's perceived luminance while shifting
|
||||
* hue/chroma toward the accent. Uses the alpha channel of the accent color to
|
||||
* store contrast information.
|
||||
* @param aOriginalColor The original color to filter.
|
||||
* @param aAccentColor The accent color to filter toward (alpha channel contains
|
||||
* contrast value).
|
||||
* @return The filtered color with transformations applied.
|
||||
*/
|
||||
static nscolor zenFilterColorChannel(nscolor aOriginalColor,
|
||||
nscolor aAccentColor) {
|
||||
const auto r1 = NS_GET_R(aOriginalColor);
|
||||
const auto g1 = NS_GET_G(aOriginalColor);
|
||||
const auto b1 = NS_GET_B(aOriginalColor);
|
||||
const auto a1 = NS_GET_A(aOriginalColor);
|
||||
if (a1 == 0) {
|
||||
// Skip processing fully transparent colors since they won't be visible and
|
||||
// we want to avoid unnecessary computations with the accent color's alpha
|
||||
// channel used for contrast information.
|
||||
return aOriginalColor;
|
||||
}
|
||||
|
||||
const auto r2 = NS_GET_R(aAccentColor);
|
||||
const auto g2 = NS_GET_G(aAccentColor);
|
||||
const auto b2 = NS_GET_B(aAccentColor);
|
||||
|
||||
// It's a bit of a hacky solution, but instead of using alpha as what it is
|
||||
// (opacity), we use it to store contrast information for now.
|
||||
// We do this primarily to avoid having to deal with WebIDL structs and
|
||||
// serialization/deserialization between parent and content processes.
|
||||
const auto contrast = NS_GET_CONTRAST(aAccentColor);
|
||||
|
||||
// Approximate perceived luminance in sRGB space
|
||||
// Coefficients per Rec.709; gamma correction ignored for speed
|
||||
const double origLum = 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1;
|
||||
const double accentLum = 0.2126 * r2 + 0.7152 * g2 + 0.0722 * b2;
|
||||
|
||||
double scale = 1.0;
|
||||
// The scale explodes for very small values of the luminance
|
||||
// so to counteract that we simply don't calculate it
|
||||
if (accentLum > 1e-5) {
|
||||
scale = origLum / accentLum;
|
||||
// Limit the scale factor
|
||||
scale = std::clamp(scale, 0.0, 4.0);
|
||||
}
|
||||
|
||||
double fr = r2 * scale;
|
||||
double fg = g2 * scale;
|
||||
double fb = b2 * scale;
|
||||
|
||||
// Apply contrast adjustment: map contrast from 0–255 to -1.0–+1.0
|
||||
// contrast = 0: maximum darkening (mix toward black)
|
||||
// contrast = 127.5: no change
|
||||
// contrast = 255: maximum lightening (mix toward white)
|
||||
const double contrastFactor = (contrast - 128.0) / 128.0;
|
||||
|
||||
// Compute perceived luminance for the filtered color
|
||||
const double lum = 0.2126 * fr + 0.7152 * fg + 0.0722 * fb;
|
||||
|
||||
// If it's bright, mix toward white; if dark, mix toward black
|
||||
if (lum >= COLOR_CHANNEL_MIDPOINT) {
|
||||
const double mix = (lum - COLOR_CHANNEL_MIDPOINT) / COLOR_CHANNEL_MIDPOINT;
|
||||
const double amount = contrastFactor * mix;
|
||||
fr = fr + (255.0 - fr) * amount;
|
||||
fg = fg + (255.0 - fg) * amount;
|
||||
fb = fb + (255.0 - fb) * amount;
|
||||
} else {
|
||||
const double mix = (COLOR_CHANNEL_MIDPOINT - lum) / COLOR_CHANNEL_MIDPOINT;
|
||||
const double amount = -contrastFactor * mix;
|
||||
fr = fr * (1.0 - amount);
|
||||
fg = fg * (1.0 - amount);
|
||||
fb = fb * (1.0 - amount);
|
||||
}
|
||||
|
||||
const uint8_t fr8 = clamp255(fr);
|
||||
const uint8_t fg8 = clamp255(fg);
|
||||
const uint8_t fb8 = clamp255(fb);
|
||||
|
||||
return NS_RGBA(fr8, fg8, fb8, a1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inverts a color by inverting each RGB channel while preserving
|
||||
* perceived luminance. This is done by inverting the color and then shifting it
|
||||
* based on the sum of the inverted channels.
|
||||
* @param aColor The color to invert.
|
||||
* @return The inverted color with luminance preservation.
|
||||
*/
|
||||
inline static nscolor zenInvertColorChannel(nscolor aColor) {
|
||||
const auto r = NS_GET_R(aColor);
|
||||
const auto g = NS_GET_G(aColor);
|
||||
const auto b = NS_GET_B(aColor);
|
||||
const auto a = NS_GET_A(aColor);
|
||||
if (a == 0) {
|
||||
// Skip processing fully transparent colors since they won't be visible and
|
||||
// we want to avoid unnecessary computations.
|
||||
return aColor;
|
||||
}
|
||||
|
||||
const auto rInv = 255 - r;
|
||||
const auto gInv = 255 - g;
|
||||
const auto bInv = 255 - b;
|
||||
|
||||
const auto max = std::max({rInv, gInv, bInv});
|
||||
const auto min = std::min({rInv, gInv, bInv});
|
||||
const auto sum = max + min;
|
||||
|
||||
const auto rShifted = sum - rInv;
|
||||
const auto gShifted = sum - gInv;
|
||||
const auto bShifted = sum - bInv;
|
||||
|
||||
return NS_RGBA(rShifted, gShifted, bShifted, a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves the current boost data from the browsing context.
|
||||
*/
|
||||
inline static void GetZenBoostsDataFromBrowsingContext(ZenBoostData* aData,
|
||||
bool* aIsInverted,
|
||||
nsPresContext* aPresContext = nullptr) {
|
||||
auto zenBoosts = nsZenBoostsBackend::GetInstance();
|
||||
if (!zenBoosts || (zenBoosts->mCurrentFrameIsAnonymousContent)) {
|
||||
return;
|
||||
}
|
||||
if (aPresContext) {
|
||||
if (auto document = aPresContext->Document()) {
|
||||
if (auto browsingContext = document->GetBrowsingContext()) {
|
||||
*aData = browsingContext->ZenBoostsData();
|
||||
*aIsInverted = browsingContext->IsZenBoostsInverted();
|
||||
}
|
||||
}
|
||||
} else if (auto currentBrowsingContext = zenBoosts->GetCurrentBrowsingContext()) {
|
||||
*aData = currentBrowsingContext->ZenBoostsData();
|
||||
*aIsInverted = currentBrowsingContext->IsZenBoostsInverted();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Use the macro to inject all of the definitions for nsISupports.
|
||||
NS_IMPL_ISUPPORTS(nsZenBoostsBackend, nsIZenBoostsBackend)
|
||||
nsZenBoostsBackend::nsZenBoostsBackend() {};
|
||||
|
||||
auto nsZenBoostsBackend::GetInstance() -> nsCOMPtr<nsZenBoostsBackend> {
|
||||
static nsCOMPtr<zen::nsZenBoostsBackend> zenBoosts(
|
||||
do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID));
|
||||
return zenBoosts;
|
||||
}
|
||||
|
||||
auto nsZenBoostsBackend::onPresShellEntered(mozilla::dom::Document* aDocument)
|
||||
-> void {
|
||||
// Note that aDocument can be null when entering anonymous content frames.
|
||||
// We explicitly do this to prevent applying boosts to anonymous content, such
|
||||
// as devtools or screenshots.
|
||||
auto browsingContext = aDocument ? aDocument->GetBrowsingContext() : nullptr;
|
||||
if (!browsingContext) {
|
||||
return;
|
||||
}
|
||||
mCurrentBrowsingContext = browsingContext;
|
||||
}
|
||||
|
||||
auto nsZenBoostsBackend::FilterColorFromPresContext(nscolor aColor,
|
||||
nsPresContext* aPresContext) -> nscolor {
|
||||
if (!XRE_IsContentProcess()) {
|
||||
// Zen boosts are only supported in content, so if we somehow end up here
|
||||
// without a prescontext or in the parent process, just return the original
|
||||
// color.
|
||||
return aColor;
|
||||
}
|
||||
ZenBoostData accentNS = 0;
|
||||
bool invertColors = false;
|
||||
GetZenBoostsDataFromBrowsingContext(&accentNS, &invertColors, aPresContext);
|
||||
if (accentNS) {
|
||||
// Apply a filter-like tint:
|
||||
// - Preserve the original color's perceived luminance
|
||||
// - Map hue/chroma toward the accent by scaling the accent's RGB
|
||||
// to match the original luminance
|
||||
// - Keep the original alpha
|
||||
// Convert both colors to nscolor to access channels
|
||||
aColor = zenFilterColorChannel(aColor, (nscolor)accentNS);
|
||||
}
|
||||
if (invertColors) {
|
||||
aColor = zenInvertColorChannel(aColor);
|
||||
}
|
||||
return aColor;
|
||||
}
|
||||
|
||||
auto nsZenBoostsBackend::ResolveStyleColor(mozilla::StyleAbsoluteColor aColor)
|
||||
-> mozilla::StyleAbsoluteColor {
|
||||
if (aColor.alpha == 0) {
|
||||
// Skip processing fully transparent colors since they won't be visible and
|
||||
// we want to avoid unnecessary computations. This also prevents issues with
|
||||
// using the alpha channel for contrast information in the accent color.
|
||||
return aColor;
|
||||
}
|
||||
const auto resultColor = FilterColorFromPresContext(aColor.ToColor());
|
||||
aColor = mozilla::StyleAbsoluteColor::FromColor(resultColor);
|
||||
return aColor;
|
||||
}
|
||||
|
||||
} // namespace zen
|
||||
81
src/zen/boosts/nsZenBoostsBackend.h
Normal file
81
src/zen/boosts/nsZenBoostsBackend.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_ZenBoostsBackend_h__
|
||||
#define mozilla_ZenBoostsBackend_h__
|
||||
|
||||
#include "nsColor.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "nsIZenBoostsBackend.h"
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
||||
#define ZEN_BOOSTS_BACKEND_CONTRACTID "@mozilla.org/zen/boosts-backend;1"
|
||||
|
||||
using ZenBoostData = nscolor; // For now, Zen boosts data is just a color.
|
||||
|
||||
namespace zen {
|
||||
|
||||
class nsZenBoostsBackend final : public nsIZenBoostsBackend {
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
public:
|
||||
explicit nsZenBoostsBackend();
|
||||
|
||||
/**
|
||||
* Indicates whether the current frame being rendered is for anonymous
|
||||
* content.
|
||||
*/
|
||||
bool mCurrentFrameIsAnonymousContent = false;
|
||||
|
||||
/**
|
||||
* @brief Resolve a StyleAbsoluteColor to take into account Zen boosts.
|
||||
* @param aColor The color to resolve.
|
||||
* @return The resolved color with Zen boost filters applied, or the original
|
||||
* color if no boost is active.
|
||||
* @see StyleColor::ResolveColor for reference.
|
||||
*/
|
||||
static auto ResolveStyleColor(mozilla::StyleAbsoluteColor aColor)
|
||||
-> mozilla::StyleAbsoluteColor;
|
||||
|
||||
/**
|
||||
* @brief Filter a color based on the current Zen boost settings.
|
||||
* @param aColor The color to filter.
|
||||
* @param aPresContext The presentation context to use for filtering.
|
||||
* @return The filtered color.
|
||||
*/
|
||||
static auto FilterColorFromPresContext(nscolor aColor,
|
||||
nsPresContext* aPresContext = nullptr) -> nscolor;
|
||||
|
||||
/**
|
||||
* @brief Called when a presshell is entered during rendering.
|
||||
* @param aPresContext The presentation context that was entered.
|
||||
*/
|
||||
auto onPresShellEntered(mozilla::dom::Document* aDocument) -> void;
|
||||
|
||||
[[nodiscard]]
|
||||
inline auto GetCurrentBrowsingContext() const {
|
||||
return mCurrentBrowsingContext;
|
||||
}
|
||||
|
||||
NS_DECL_NSIZENBOOSTSBACKEND
|
||||
private:
|
||||
~nsZenBoostsBackend() = default;
|
||||
|
||||
/**
|
||||
* The presshell of the current document being rendered.
|
||||
*/
|
||||
RefPtr<mozilla::dom::BrowsingContext> mCurrentBrowsingContext;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the singleton instance of the ZenBoostsBackend.
|
||||
* @return The singleton instance.
|
||||
*/
|
||||
static auto GetInstance() -> nsCOMPtr<nsZenBoostsBackend>;
|
||||
};
|
||||
|
||||
} // namespace zen
|
||||
|
||||
#endif
|
||||
52
src/zen/boosts/zen-advanced-color-options.css
Normal file
52
src/zen/boosts/zen-advanced-color-options.css
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#zen-boost-advanced-color-options-panel {
|
||||
color-scheme: light;
|
||||
-zen-window-transform-origin: 50% 0%;
|
||||
padding: 8px 8px;
|
||||
|
||||
& p {
|
||||
color: #3a3a3b;
|
||||
}
|
||||
|
||||
& input {
|
||||
width: 185px;
|
||||
appearance: none !important;
|
||||
height: 4px;
|
||||
|
||||
background-color: #b5b5b9;
|
||||
border-radius: 2px;
|
||||
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
& separator {
|
||||
height: 24px !important;
|
||||
}
|
||||
|
||||
@media (-moz-platform: macos) {
|
||||
&[animate="open"] {
|
||||
animation: 0.4 zen-color-options-panel-animation-macos cubic-bezier(0.14, 1.43, 0.56, 1.01) forwards !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-advanced-color-options-panel input::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #e6e5ea;
|
||||
border: solid 1px #d1d0d5;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
#zen-boost-advanced-color-options-panel input::-moz-range-thumb:active {
|
||||
background-color: #cbcad0;
|
||||
}
|
||||
|
||||
#zen-boost-advanced-color-options-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
151
src/zen/boosts/zen-boost-editor.xhtml
Normal file
151
src/zen/boosts/zen-boost-editor.xhtml
Normal file
@@ -0,0 +1,151 @@
|
||||
#filter substitution
|
||||
<?xml version="1.0"?>
|
||||
# -*- Mode: 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/.
|
||||
|
||||
<!DOCTYPE window>
|
||||
|
||||
<html
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="zenBoostWindow"
|
||||
windowtype="Zen:BoostEditor"
|
||||
alwaysontop="true"
|
||||
customtitlebar="true"
|
||||
sizemode="normal"
|
||||
scrolling="false"
|
||||
macanimationtype="document"
|
||||
windowsmica="true"
|
||||
data-l10n-sync="true">
|
||||
<head>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/" />
|
||||
<link rel="stylesheet" href="chrome://global/skin/global.css" />
|
||||
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-boosts.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-buttons.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-theme.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-animations.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-panel-ui.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/skin/zen-icons/icons.css" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/zen-styles/zen-advanced-color-options.css" />
|
||||
|
||||
<link rel="localization" href="browser/zen-boosts.ftl"/>
|
||||
|
||||
<!-- Loading in the window module -->
|
||||
<script>
|
||||
const { nsZenBoostEditor } = ChromeUtils.importESModule( "resource:///modules/zen/boosts/ZenBoostsEditor.mjs" );
|
||||
window.addEventListener("load", () => {
|
||||
window.boostEditor = new nsZenBoostEditor(document, window.domain, window, window.openerWindow);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<vbox flex="1" id="zen-boost-editor-root">
|
||||
<hbox id="zen-boost-head-wrapper">
|
||||
<button id="zen-boost-close" class="subviewbutton mod-button title-button"></button>
|
||||
<hbox id="zen-boost-name" flex="1">
|
||||
<hbox id="zen-boost-name-container">
|
||||
<html:p id="zen-boost-name-text"></html:p>
|
||||
</hbox>
|
||||
</hbox>
|
||||
<button data-l10n-id="zen-boost-shuffle" id="zen-boost-shuffle" class="subviewbutton mod-button title-button"></button>
|
||||
</hbox>
|
||||
<vbox flex="1" id="zen-boost-filter-wrapper">
|
||||
<hbox class="zen-boost-color-picker-gradient zen-boost-panel-disabled">
|
||||
<button data-l10n-id="zen-boost-magic-theme" id="zen-boost-magic-theme" class="subviewbutton mod-button"></button>
|
||||
<html:div class="zen-boost-color-picker-dot"></html:div>
|
||||
<html:div class="zen-boost-color-picker-circle"></html:div>
|
||||
</hbox>
|
||||
|
||||
<hbox flex="1" id="zen-boost-toolbar-wrapper-colors">
|
||||
<button data-l10n-id="zen-boost-invert" id="zen-boost-invert" class="subviewbutton mod-button small"></button>
|
||||
<button data-l10n-id="zen-boost-controls" id="zen-boost-controls" class="subviewbutton mod-button small"></button>
|
||||
<button data-l10n-id="zen-boost-disable" id="zen-boost-disable" class="subviewbutton mod-button small"></button>
|
||||
</hbox>
|
||||
|
||||
<html:div id="zen-boost-font-wrapper">
|
||||
<vbox id="zen-boost-font-grid">
|
||||
<!-- Font buttons will be injected here -->
|
||||
</vbox>
|
||||
<html:div class="visible-separator"></html:div>
|
||||
<hbox flex="1" id="zen-boost-font-toolbar">
|
||||
<html:select name="font" id="zen-boost-font-select" class="mod-button">
|
||||
<!-- Additional font options will be injected here -->
|
||||
</html:select>
|
||||
<button data-l10n-id="zen-boost-text-case-toggle" id="zen-boost-text-case-toggle" class="subviewbutton mod-button"></button>
|
||||
</hbox>
|
||||
</html:div>
|
||||
|
||||
<button id="zen-boost-zap" class="subviewbutton mod-button big-button toggleable-button">
|
||||
<html:p data-l10n-id="zen-boost-zap" id="zen-boost-zap-text"></html:p>
|
||||
<html:p id="zen-boost-zap-value"></html:p>
|
||||
</button>
|
||||
|
||||
<button id="zen-boost-code" class="subviewbutton mod-button big-button">
|
||||
<html:p data-l10n-id="zen-boost-code" id="zen-boost-code-text"></html:p>
|
||||
</button>
|
||||
|
||||
<hbox flex="1" id="zen-boost-toolbar-wrapper">
|
||||
<button data-l10n-id="zen-boost-save" id="zen-boost-save" class="subviewbutton mod-button med"></button>
|
||||
<button data-l10n-id="zen-boost-load" id="zen-boost-load" class="subviewbutton mod-button med"></button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox flex="1" id="zen-boost-code-editor-root">
|
||||
<hbox id="zen-boost-code-top-bar">
|
||||
<button id="zen-boost-back" class="subviewbutton mod-button big-button">
|
||||
<html:p data-l10n-id="zen-boost-back" id="zen-boost-back-text"></html:p>
|
||||
</button>
|
||||
</hbox>
|
||||
|
||||
<vbox flex="1" id="zen-boost-code-editor">
|
||||
</vbox>
|
||||
|
||||
<hbox id="zen-boost-code-bottom-bar">
|
||||
<button data-l10n-id="zen-boost-css-picker" id="zen-boost-css-picker" class="subviewbutton mod-button big-button toggleable-button"></button>
|
||||
<button data-l10n-id="zen-boost-css-inspector" id="zen-boost-css-inspector" class="subviewbutton mod-button big-button"></button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<popupset id="mainPopupSet">
|
||||
<panel type="arrow" popupalign="topmiddle" id="zen-boost-advanced-color-options-panel">
|
||||
<html:div flex="1" id="zen-boost-advanced-color-options-container">
|
||||
<p data-l10n-id="zen-bootst-color-contrast"></p>
|
||||
<html:input id="zen-boost-color-contrast" type="range" min="0" max="1" value="0.5" step="0.01"/>
|
||||
<separator />
|
||||
<p data-l10n-id="zen-bootst-color-brightness"></p>
|
||||
<html:input id="zen-boost-color-brightness" type="range" min="0" max="1" value="0.5" step="0.01"/>
|
||||
<separator />
|
||||
<p data-l10n-id="zen-bootst-color-original-saturation"></p>
|
||||
<html:input id="zen-boost-color-saturation" type="range" min="0" max="1" value="0.5" step="0.01"/>
|
||||
</html:div>
|
||||
</panel>
|
||||
</popupset>
|
||||
|
||||
<menupopup id="zenBoostContextMenu">
|
||||
<menuitem data-l10n-id="zen-boost-edit-rename" id="zen-boost-edit-rename" command="cmd_zenBoostEditName" />
|
||||
<menuitem data-l10n-id="zen-boost-edit-shuffle" id="zen-boost-edit-shuffle" command="cmd_zenBoostShuffle" />
|
||||
<menuitem data-l10n-id="zen-boost-edit-reset" id="zen-boost-edit-reset" command="cmd_zenBoostReset" />
|
||||
<menuseparator/>
|
||||
<menuitem data-l10n-id="zen-boost-edit-delete" id="zen-boost-edit-delete" command="cmd_zenBoostDelete" />
|
||||
</menupopup>
|
||||
|
||||
<commandset id="zenBoostCommandSet">
|
||||
<command id="cmd_zenBoostEditName"
|
||||
oncommand="window.boostEditor.editBoostName();" />
|
||||
|
||||
<command id="cmd_zenBoostShuffle"
|
||||
oncommand="window.boostEditor.shuffleBoost();" />
|
||||
|
||||
<command id="cmd_zenBoostReset"
|
||||
oncommand="window.boostEditor.resetBoost();" />
|
||||
|
||||
<command id="cmd_zenBoostDelete"
|
||||
oncommand="window.boostEditor.deleteBoost();" />
|
||||
</commandset>
|
||||
</html:body>
|
||||
</html>
|
||||
789
src/zen/boosts/zen-boosts.css
Normal file
789
src/zen/boosts/zen-boosts.css
Normal file
@@ -0,0 +1,789 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#zenBoostWindow {
|
||||
/* For the mica effect we want a white tint */
|
||||
@media not (-moz-platform: linux) {
|
||||
color-scheme: light;
|
||||
}
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.source-editor-frame {
|
||||
max-width: 1000px;
|
||||
max-height: 1000px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#zen-boost-code-top-bar {
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: #f6f6f8;
|
||||
@media (-moz-windows-mica) {
|
||||
background-color: #f6f6f8c0;
|
||||
}
|
||||
|
||||
border: solid 0px #ededef;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
#zen-boost-name-container {
|
||||
background-image: url("chrome://global/skin/icons/arrow-down.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: calc(100% - 2px) calc(50% + 2px);
|
||||
background-size: 12px 12px;
|
||||
padding: 4px 18px 4px 2px;
|
||||
margin-left: 4px;
|
||||
border-radius: 8px;
|
||||
|
||||
opacity: 0.75;
|
||||
background-color: transparent;
|
||||
|
||||
transition:
|
||||
0.25s opacity cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.25s background-color cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
|
||||
-moz-window-dragging: no-drag;
|
||||
}
|
||||
|
||||
#zen-boost-name-container:hover {
|
||||
opacity: 0.8;
|
||||
background-color: #aaaaaa22;
|
||||
}
|
||||
|
||||
#zen-boost-code-bottom-bar {
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: #f6f6f8;
|
||||
@media (-moz-windows-mica) {
|
||||
background-color: #f6f6f8c0;
|
||||
}
|
||||
|
||||
border: solid 0px #ededef;
|
||||
border-top-width: 1px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#zen-boost-code-editor {
|
||||
width: 100%;
|
||||
height: calc(100% - 100px);
|
||||
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: clip;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#zen-boost-editor-root,
|
||||
#zen-boost-code-editor-root {
|
||||
user-select: none;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
& button {
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
padding: 0 !important;
|
||||
min-width: fit-content !important;
|
||||
|
||||
border: solid 6px transparent;
|
||||
|
||||
transition: background 0.1s cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
appearance: none;
|
||||
|
||||
&:active {
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
& .button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.subviewbutton {
|
||||
color: #3a3a3b;
|
||||
}
|
||||
|
||||
#zen-boost-editor-view {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#zen-boost-code-top-bar {
|
||||
-moz-window-dragging: drag; /* Allow dragging the window with the custom titlebar for the code view */
|
||||
}
|
||||
|
||||
#zen-boost-head-wrapper {
|
||||
-moz-window-dragging: drag; /* Allow dragging the window with the custom titlebar */
|
||||
|
||||
margin-bottom: -10px;
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
max-height: 40px;
|
||||
align-items: center;
|
||||
background-color: #f6f6f8;
|
||||
border: solid 1px #e7e7e7ab;
|
||||
|
||||
@media (-moz-windows-mica) {
|
||||
background-color: #f6f6f8c0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: row-reverse;
|
||||
@media (-moz-platform: macos) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
& button {
|
||||
height: 24px !important;
|
||||
width: 24px !important;
|
||||
aspect-ratio: 1/1 !important;
|
||||
background-color: transparent;
|
||||
opacity: 0.45;
|
||||
|
||||
transition: 0.25s opacity cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
& #zen-boost-name {
|
||||
transition: color 0.2s ease-in-out;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-name-text {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
font-size: 100%;
|
||||
font-weight: 600;
|
||||
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#zen-boost-name-text {
|
||||
text-align: center;
|
||||
margin: auto !important;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
#zen-boost-font-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 0px;
|
||||
width: 100%;
|
||||
|
||||
padding: 2px;
|
||||
|
||||
& button {
|
||||
padding: auto;
|
||||
margin: auto;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-filter-wrapper {
|
||||
padding: 20px;
|
||||
gap: 14px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
|
||||
background-color: #fcfcfe;
|
||||
|
||||
-moz-window-dragging: drag;
|
||||
& > * {
|
||||
-moz-window-dragging: no-drag;
|
||||
}
|
||||
}
|
||||
|
||||
.big-button {
|
||||
width: 100% !important;
|
||||
margin: auto;
|
||||
text-indent: 6px;
|
||||
vertical-align: middle;
|
||||
font-size: 10pt;
|
||||
|
||||
list-style-position: right;
|
||||
|
||||
& p {
|
||||
width: 75%;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-back {
|
||||
width: 60px !important;
|
||||
margin: 5px;
|
||||
left: 5px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
|
||||
& hbox {
|
||||
scale: 0.75;
|
||||
margin-right: -7px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-code-bottom-bar button {
|
||||
content: "";
|
||||
width: auto !important;
|
||||
aspect-ratio: 1/1 !important;
|
||||
|
||||
margin: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
#zen-boost-zap {
|
||||
& #zen-boost-zap-value {
|
||||
text-align: right;
|
||||
right: 8px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& hbox {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-zap[hideicon="true"] {
|
||||
list-style: none;
|
||||
list-style-type: none;
|
||||
|
||||
& hbox {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-size {
|
||||
list-style-type: none;
|
||||
|
||||
& #zen-boost-size-value {
|
||||
text-align: right;
|
||||
right: 8px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-panel-disabled {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.title-button {
|
||||
scale: 0.9;
|
||||
}
|
||||
|
||||
.mod-button {
|
||||
height: 38px !important;
|
||||
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
background-color: #ebebed;
|
||||
|
||||
transition:
|
||||
0.4s background-color cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s opacity cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s filter cubic-bezier(0.075, 0.82, 0.165, 1) !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: none !important;
|
||||
|
||||
opacity: 0.9;
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="none"] {
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
opacity: 0.6 !important;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-code-top-bar .mod-button {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
#zen-boost-code-bottom-bar button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#zen-boost-name {
|
||||
height: 26px;
|
||||
font-size: 8pt;
|
||||
text-indent: 2px;
|
||||
vertical-align: middle;
|
||||
color: #3a3a3b;
|
||||
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#zen-boost-close {
|
||||
margin-left: 0px;
|
||||
margin-right: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
margin-left: 6px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-shuffle {
|
||||
margin-right: 0px;
|
||||
margin-left: 4px;
|
||||
@media (-moz-platform: macos) {
|
||||
margin-right: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-button-active {
|
||||
background-color: #3a3a3a;
|
||||
color: #fcfcfe;
|
||||
}
|
||||
|
||||
.zen-boost-button-active:hover {
|
||||
background-color: #5b5b5c;
|
||||
}
|
||||
|
||||
.zen-boost-button-active-transparent {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.zen-boost-button-active-transparent:hover {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
#zen-boost-magic-theme {
|
||||
width: 18px;
|
||||
height: 24px !important;
|
||||
margin-top: 12px;
|
||||
|
||||
z-index: 4;
|
||||
box-shadow: 0px 2px 4px #00000010;
|
||||
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
& image {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
&:not(.zen-boost-button-active) {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-toolbar-wrapper,
|
||||
#zen-boost-toolbar-wrapper-colors {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& .small {
|
||||
margin: 0;
|
||||
flex: 1 1 30%;
|
||||
}
|
||||
|
||||
& .med {
|
||||
margin: 0;
|
||||
flex: 1 1 50%;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-font-select {
|
||||
width: 95px;
|
||||
height: 20px !important;
|
||||
|
||||
transition: 0.4s opacity cubic-bezier(0.075, 0.82, 0.165, 1) !important;
|
||||
|
||||
color: #727272;
|
||||
background-color: transparent;
|
||||
background: none;
|
||||
|
||||
font-size: 9pt;
|
||||
|
||||
padding: 0px;
|
||||
text-indent: 2px;
|
||||
|
||||
margin-left: 8px;
|
||||
margin-right: 3px;
|
||||
margin-top: 8px;
|
||||
opacity: 0.75;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-font-toolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
& button {
|
||||
border-radius: 6px;
|
||||
min-width: fit-content;
|
||||
height: 20px !important;
|
||||
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
margin-top: 8px;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-font-wrapper {
|
||||
box-shadow: 0px 2px 8px #00000010;
|
||||
background-color: #ffffff;
|
||||
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .visible-separator {
|
||||
content: "";
|
||||
height: 1px;
|
||||
width: auto;
|
||||
|
||||
margin-right: 12px;
|
||||
margin-left: 12px;
|
||||
margin-top: 2px;
|
||||
|
||||
opacity: 0.25;
|
||||
|
||||
border-top: solid 1px #9a9a9a;
|
||||
}
|
||||
|
||||
& button {
|
||||
filter: none;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 50px;
|
||||
transition: 0.1s background-color ease-in-out !important;
|
||||
font-size: 12px;
|
||||
padding: 2px;
|
||||
|
||||
&:hover {
|
||||
background-color: #9a9a9a30;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-font-button-active {
|
||||
background-color: #5b5b5c22 !important;
|
||||
}
|
||||
|
||||
.zen-boost-font-button-active:hover {
|
||||
background-color: #5b5b5c32 !important;
|
||||
}
|
||||
|
||||
@property --mod-button-c1 {
|
||||
syntax: "<color>";
|
||||
inherits: false;
|
||||
initial-value: #ebebed;
|
||||
}
|
||||
|
||||
@property --mod-button-c2 {
|
||||
syntax: "<color>";
|
||||
inherits: false;
|
||||
initial-value: #ebebed;
|
||||
}
|
||||
|
||||
.mod-button[mode="orange"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #ffbb5d;
|
||||
--mod-button-c2: #ffa01d;
|
||||
}
|
||||
.mod-button[mode="orange-red"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #ff8758;
|
||||
--mod-button-c2: #ff5b1b;
|
||||
}
|
||||
.mod-button[mode="red"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #ff595f;
|
||||
--mod-button-c2: #ff121b;
|
||||
}
|
||||
.mod-button[mode="blue"] {
|
||||
color: #e3e9e4;
|
||||
--mod-button-c1: #6650fc;
|
||||
--mod-button-c2: #4125ff;
|
||||
}
|
||||
.mod-button[mode] {
|
||||
background: linear-gradient(180deg, var(--mod-button-c1) 0%, var(--mod-button-c2) 100%) border-box;
|
||||
transition:
|
||||
0.4s background-color cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s opacity cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.4s filter cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.75s --mod-button-c1 cubic-bezier(0.075, 0.82, 0.165, 1),
|
||||
0.55s --mod-button-c2 cubic-bezier(0.075, 0.82, 0.165, 1) !important;
|
||||
}
|
||||
|
||||
#zen-boost-back {
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: #e3e3e6;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-code-editor-root button {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.toggleable-button[enabled="true"] {
|
||||
filter: invert();
|
||||
opacity: 0.9;
|
||||
|
||||
&:active {
|
||||
filter: invert();
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-color-picker-gradient::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 8px;
|
||||
margin: 8px;
|
||||
|
||||
z-index: 2;
|
||||
|
||||
background: #fbfbfdea;
|
||||
|
||||
background-position: -23px -23px;
|
||||
backdrop-filter: saturate(2) blur(15px);
|
||||
|
||||
background-size: 6px 6px;
|
||||
background-image: radial-gradient(#e3e9e4, 1px, transparent 0);
|
||||
|
||||
@media (-moz-platform: macos) {
|
||||
background-size: 4px 4px;
|
||||
background-image: radial-gradient(#e3e9e4 0.5px, transparent 0);
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-color-picker-gradient::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
background: conic-gradient(
|
||||
rgba(255, 0, 0, 1) 0%,
|
||||
rgba(255, 162, 0, 1) 10%,
|
||||
rgba(255, 242, 0, 1) 20%,
|
||||
rgba(89, 255, 0, 1) 30%,
|
||||
rgba(0, 255, 128, 1) 40%,
|
||||
rgba(0, 255, 247, 1) 50%,
|
||||
rgba(0, 89, 255, 1) 60%,
|
||||
rgba(8, 0, 255, 1) 70%,
|
||||
rgba(204, 0, 255, 1) 80%,
|
||||
rgba(255, 0, 144, 1) 90%,
|
||||
rgba(255, 0, 8, 1) 100%
|
||||
)
|
||||
border-box;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.zen-boost-color-picker-gradient {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 4px 12px #00000021;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 /1;
|
||||
margin: 10px 0px 4px 0px;
|
||||
|
||||
min-height: calc(var(--panel-width) - var(--panel-padding) * 2 - 2px);
|
||||
|
||||
& [animated="true"] {
|
||||
transition: all 0.4s cubic-bezier(0.14, 1.43, 0.56, 1.01) !important;
|
||||
}
|
||||
|
||||
& .zen-boost-color-picker-circle {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
transform-origin: center center;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
|
||||
outline: solid 1px #9a9a9a88;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
& .zen-boost-color-picker-dot {
|
||||
box-shadow: 0px 2px 4px #00000022;
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: var(--zen-theme-picker-dot-color);
|
||||
@media (-prefers-color-scheme: dark) {
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
cursor: pointer;
|
||||
border: 3px solid #ffffff;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
transform-origin: top left;
|
||||
|
||||
&:first-of-type {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-width: 3px;
|
||||
pointer-events: all;
|
||||
transition: transform 0.2s;
|
||||
z-index: 999;
|
||||
&:hover {
|
||||
transform: scale(1.05) translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
&[dragging="true"] {
|
||||
transform: scale(1.2) translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-editor-root:hover {
|
||||
& .zen-boost-color-picker-circle {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/** Import animation */
|
||||
/** From zen-single-components.css:211 */
|
||||
|
||||
@property --background-top {
|
||||
syntax: "<percentage>";
|
||||
inherits: false;
|
||||
initial-value: -50%;
|
||||
}
|
||||
|
||||
#import-animation {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.5) 40%, rgba(255, 255, 255, 0.6) 60%, transparent 100%);
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#import-animation-shadow {
|
||||
position: fixed;
|
||||
mix-blend-mode: multiply;
|
||||
background: #00000040;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 998;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#import-animation-border {
|
||||
position: absolute;
|
||||
border-radius: var(--zen-border-radius);
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#import-animation-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 4px; /* thickness of border */
|
||||
z-index: 1000;
|
||||
|
||||
background: radial-gradient(ellipse 100% 50% at center var(--background-top), rgba(255, 255, 255, 0.9) 80%, transparent);
|
||||
|
||||
/* Punch out the inside once (not animated) */
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
}
|
||||
200
src/zen/boosts/zen-selector.css
Normal file
200
src/zen/boosts/zen-selector.css
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
@keyframes select-component-in {
|
||||
0% {
|
||||
scale: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
scale: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#select-component {
|
||||
width: min-content;
|
||||
font-family: system-ui !important;
|
||||
|
||||
height: 98px;
|
||||
|
||||
pointer-events: auto !important;
|
||||
|
||||
transform-origin: top;
|
||||
overflow: hidden;
|
||||
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
|
||||
background-color: #f5f7fb;
|
||||
margin: 4px;
|
||||
|
||||
border-radius: 12px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
box-shadow: 0px 0px 20px#00000050;
|
||||
|
||||
scale: 1;
|
||||
opacity: 1;
|
||||
|
||||
@media not (-moz-platform: windows) {
|
||||
&[is-appearing="true"] {
|
||||
scale: 0;
|
||||
opacity: 0;
|
||||
|
||||
animation: 0.4s select-component-in cubic-bezier(0.14, 1.43, 0.56, 1.01) forwards;
|
||||
}
|
||||
}
|
||||
|
||||
& input {
|
||||
width: fit-content;
|
||||
min-width: 75px;
|
||||
|
||||
height: 40px;
|
||||
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
font-size: 11pt;
|
||||
transition:
|
||||
0.15s filter ease,
|
||||
0.1s box-shadow ease-out,
|
||||
scale 0.35s cubic-bezier(0.11, 1.6, 0.63, 1);
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
scale: 0.95;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#highlight-shadow {
|
||||
z-index: 999;
|
||||
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
content: "";
|
||||
background-color: #00000044;
|
||||
}
|
||||
|
||||
#highlight-container {
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
|
||||
& .highlight {
|
||||
position: fixed;
|
||||
|
||||
border: 1px solid #e9dd38;
|
||||
background-color: #c2b72046;
|
||||
border-radius: 6px;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
#select-controls {
|
||||
display: flex;
|
||||
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
@media not (-moz-platform: windows) {
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
#select-related {
|
||||
appearance: none;
|
||||
text-align: left;
|
||||
|
||||
width: 250px !important;
|
||||
--related-elements-value: 100%;
|
||||
background: linear-gradient(to top, rgb(247, 66, 0), rgb(245, 134, 86));
|
||||
|
||||
border: none;
|
||||
color: #dadada;
|
||||
box-shadow: 0px 0px 15px #00000052;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 14px #00000066;
|
||||
background:
|
||||
linear-gradient(to right, transparent var(--related-elements-value), gray var(--related-elements-value)),
|
||||
linear-gradient(to top, rgb(247, 66, 0), rgb(245, 134, 86));
|
||||
|
||||
@media not (-moz-platform: windows) {
|
||||
box-shadow: 0px 0px 20px #00000077;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#select-this {
|
||||
appearance: none;
|
||||
|
||||
background: linear-gradient(0deg, rgba(246, 27, 25, 1) 0%, rgba(254, 67, 59, 1) 100%);
|
||||
border: none;
|
||||
color: #dadada;
|
||||
|
||||
box-shadow: 0px 0px 15px #00000052;
|
||||
}
|
||||
|
||||
#select-cancel {
|
||||
appearance: none;
|
||||
|
||||
background: rgb(90, 94, 100);
|
||||
@media (-moz-platform: macos) {
|
||||
background: linear-gradient(0deg, rgba(81, 83, 85, 1) 0%, rgba(108, 110, 112, 1) 100%);
|
||||
}
|
||||
|
||||
border: none;
|
||||
color: #dadada;
|
||||
|
||||
box-shadow: 0px 0px 15px #00000052;
|
||||
}
|
||||
|
||||
#selector-preview {
|
||||
background-color: #e0e2e63d;
|
||||
outline: 1px solid #e0e2e6ae;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
text-indent: 18px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#selector-element-preview-text {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #797b7f;
|
||||
}
|
||||
|
||||
#hover-div {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
|
||||
content: "";
|
||||
outline: 1px solid #e9dd38;
|
||||
border-radius: 6px;
|
||||
|
||||
background-color: #c2b72046;
|
||||
|
||||
transition: 0.1s all ease-out;
|
||||
will-change: left, top, width, height;
|
||||
}
|
||||
158
src/zen/boosts/zen-zap.css
Normal file
158
src/zen/boosts/zen-zap.css
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
@keyframes select-controls-container-in {
|
||||
0% {
|
||||
bottom: -54px;
|
||||
}
|
||||
|
||||
100% {
|
||||
bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zap-border-in {
|
||||
0% {
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
#zap-border {
|
||||
z-index: 9999;
|
||||
content: "";
|
||||
position: fixed;
|
||||
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
|
||||
pointer-events: none;
|
||||
background-color: transparent;
|
||||
|
||||
outline: 4px solid #dfd43a;
|
||||
animation: 0.15s zap-border-in ease-out;
|
||||
|
||||
border-radius: 4px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
#zap-controls-container {
|
||||
font-family: system-ui !important;
|
||||
|
||||
display: flex;
|
||||
pointer-events: auto !important;
|
||||
z-index: 999999;
|
||||
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 10px 0 10px;
|
||||
height: 54px;
|
||||
position: fixed;
|
||||
|
||||
border-radius: 6px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
color: #d6d6d6;
|
||||
background-color: #1d1b1b;
|
||||
border: 1px solid #2f2b2b;
|
||||
|
||||
animation: 0.15s select-controls-container-in cubic-bezier(0.07, 1.32, 0.41, 1);
|
||||
|
||||
& input {
|
||||
width: min-content;
|
||||
height: 36px;
|
||||
|
||||
border-radius: 4px;
|
||||
@media (-moz-platform: macos) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
border: none;
|
||||
background: linear-gradient(0deg, #afafaf 0%, #e5e5e5 100%) !important;
|
||||
|
||||
color: #1d1b1b;
|
||||
font-weight: bold;
|
||||
|
||||
transition:
|
||||
0.1s filter ease-in-out,
|
||||
scale 0.35s cubic-bezier(0.11, 1.6, 0.63, 1);
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
&:active {
|
||||
filter: brightness(0.8);
|
||||
scale: 0.95;
|
||||
}
|
||||
}
|
||||
|
||||
& #zap-list {
|
||||
display: flex;
|
||||
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
gap: 8px;
|
||||
|
||||
& input {
|
||||
width: 36px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
& p {
|
||||
padding-left: 12px;
|
||||
font-size: 10pt;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
& .pcenter {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
& #zap-done {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 12px 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-zap-dissolve-canvas {
|
||||
position: absolute !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
pointer-events: none !important;
|
||||
z-index: 2147483647 !important;
|
||||
}
|
||||
@@ -221,6 +221,7 @@ panel {
|
||||
}
|
||||
|
||||
.permission-popup-permission-item,
|
||||
.permission-popup-boost-item,
|
||||
#permission-popup-storage-access-permission-list-header {
|
||||
padding-block: 4px;
|
||||
margin-block: 0px;
|
||||
|
||||
@@ -404,17 +404,59 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.permission-popup-permission-item {
|
||||
.zen-permission-popup-boost-editor-button {
|
||||
transition:
|
||||
0.2s opacity ease,
|
||||
0.2s background-color ease,
|
||||
0.2s transform ease;
|
||||
opacity: 0;
|
||||
|
||||
width: 30px;
|
||||
height: 26px;
|
||||
min-width: 30px;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
border-radius: 8px;
|
||||
transform: scale(1);
|
||||
|
||||
-moz-context-properties: fill;
|
||||
fill: white;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
& image {
|
||||
opacity: 0.6;
|
||||
height: 14px;
|
||||
width: calc(100% - 2px);
|
||||
}
|
||||
}
|
||||
|
||||
.permission-popup-boost-item:hover .zen-permission-popup-boost-editor-button {
|
||||
opacity: 1;
|
||||
|
||||
&.zen-permission-popup-boost-editor-button:hover {
|
||||
background-color: #aaaaaa22;
|
||||
}
|
||||
}
|
||||
|
||||
.permission-popup-permission-item,
|
||||
.permission-popup-boost-item {
|
||||
gap: 8px;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.permission-popup-permission-label-container {
|
||||
.permission-popup-permission-label-container,
|
||||
.permission-popup-boost-label-container {
|
||||
min-width: 1px;
|
||||
}
|
||||
|
||||
.permission-popup-permission-label {
|
||||
.permission-popup-permission-label,
|
||||
.permission-popup-boost-label {
|
||||
margin: 0px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
@@ -423,7 +465,8 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.permission-popup-permission-icon {
|
||||
.permission-popup-permission-icon,
|
||||
.permission-popup-boost-icon {
|
||||
fill: var(--button-primary-color);
|
||||
padding: 8px;
|
||||
width: 34px;
|
||||
@@ -455,20 +498,24 @@
|
||||
opacity 0.12s ease-in-out;
|
||||
}
|
||||
|
||||
.permission-popup-permission-item:hover &::before {
|
||||
.permission-popup-permission-item:hover &::before,
|
||||
.permission-popup-boost-item:hover:not(:has(.zen-permission-popup-boost-editor-button:hover)) &::before {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.permission-popup-permission-item:active:hover &::before {
|
||||
.permission-popup-permission-item:active:hover &::before,
|
||||
.permission-popup-boost-item:active:hover:not(:has(.zen-permission-popup-boost-editor-button:hover)) &::before {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.permission-popup-permission-item[state="allow"] &::before {
|
||||
.permission-popup-permission-item[state="allow"] &::before,
|
||||
.permission-popup-boost-item[state="enabled"] &::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-permission-popup-permission-state-label {
|
||||
.zen-permission-popup-permission-state-label,
|
||||
.zen-permission-popup-boost-state-label {
|
||||
opacity: 0.6;
|
||||
font-size: smaller;
|
||||
font-weight: 400;
|
||||
@@ -485,6 +532,8 @@
|
||||
padding-top: 8px;
|
||||
margin: 2px 8px 8px 8px;
|
||||
|
||||
gap: 8px;
|
||||
|
||||
& toolbarbutton {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -581,7 +630,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
#zen-site-data-settings-list toolbarseparator {
|
||||
#zen-site-data-settings-list toolbarseparator,
|
||||
#zen-site-data-boost-list toolbarseparator {
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,24 @@ let JSWINDOWACTORS = {
|
||||
},
|
||||
};
|
||||
|
||||
if (!Services.appinfo.inSafeMode) {
|
||||
JSWINDOWACTORS.ZenBoosts = {
|
||||
parent: {
|
||||
esModuleURI: "resource:///actors/ZenBoostsParent.sys.mjs",
|
||||
},
|
||||
child: {
|
||||
esModuleURI: "resource:///actors/ZenBoostsChild.sys.mjs",
|
||||
events: {
|
||||
DOMDocElementInserted: { capture: true },
|
||||
unload: {},
|
||||
},
|
||||
},
|
||||
allFrames: true,
|
||||
matches: ["*://*/*"],
|
||||
enablePreference: "zen.boosts.enabled",
|
||||
};
|
||||
}
|
||||
|
||||
export let gZenActorsManager = {
|
||||
init() {
|
||||
ActorManagerParent.addJSProcessActors(JSPROCESSACTORS);
|
||||
|
||||
@@ -30,7 +30,7 @@ nsZenDragAndDrop::nsZenDragAndDrop() {
|
||||
}
|
||||
|
||||
auto nsZenDragAndDrop::GetZenDragAndDropInstance() -> nsCOMPtr<nsZenDragAndDrop> {
|
||||
return do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID);
|
||||
return do_GetService(ZEN_DND_MANAGER_CONTRACTID);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#include "nsIZenDragAndDrop.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
#define ZEN_BOOSTS_BACKEND_CONTRACTID "@mozilla.org/zen/drag-and-drop;1"
|
||||
#define ZEN_DND_MANAGER_CONTRACTID \
|
||||
"@mozilla.org/zen/drag-and-drop;1"
|
||||
|
||||
namespace zen {
|
||||
|
||||
|
||||
50
src/zen/images/boost-indicator.svg
Normal file
50
src/zen/images/boost-indicator.svg
Normal file
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
- 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/.
|
||||
-->
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100%" height="100%" fill="none"/>
|
||||
|
||||
<style type="text/css"><![CDATA[
|
||||
.sparkle {
|
||||
stroke: currentColor;
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
|
||||
animation: sparkle 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes sparkle {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(90deg);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0) rotate(180deg);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
]]></style>
|
||||
|
||||
<g class="sparkle-group">
|
||||
<path class="sparkle" style="animation-delay: 0.5s;" d="M10 2v2"/>
|
||||
<path class="sparkle" style="animation-delay: 0.5s;" d="M11 3H9"/>
|
||||
|
||||
<path class="sparkle" d="M5 6v4"/>
|
||||
<path class="sparkle" d="M7 8H3"/>
|
||||
|
||||
<path class="sparkle" style="animation-delay: 1.8s;" d="M21 20h-4"/>
|
||||
<path class="sparkle" style="animation-delay: 1.8s;" d="M19 18v4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -34,10 +34,8 @@ nsZenModsBackend::nsZenModsBackend() {
|
||||
|
||||
auto nsZenModsBackend::CheckEnabled() -> void {
|
||||
// Check if the mods backend is enabled based on the preference.
|
||||
nsCOMPtr<nsIXULRuntime> appInfo =
|
||||
do_GetService("@mozilla.org/xre/app-info;1");
|
||||
bool inSafeMode = false;
|
||||
if (appInfo) {
|
||||
if (nsCOMPtr<nsIXULRuntime> appInfo = do_GetService("@mozilla.org/xre/app-info;1")) {
|
||||
appInfo->GetInSafeMode(&inSafeMode);
|
||||
}
|
||||
mEnabled = !inSafeMode &&
|
||||
|
||||
@@ -7,6 +7,7 @@ EXTRA_PP_COMPONENTS += [
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
"boosts",
|
||||
"common",
|
||||
"drag-and-drop",
|
||||
"glance",
|
||||
|
||||
12
src/zen/tests/boosts/browser.toml
Normal file
12
src/zen/tests/boosts/browser.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
# 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/.
|
||||
|
||||
[DEFAULT]
|
||||
support-files = [
|
||||
"head.js",
|
||||
]
|
||||
|
||||
["browser_boost_selector_basic.js"]
|
||||
["browser_boost_selector_invalid.js"]
|
||||
["browser_boost_selector_nthchild.js"]
|
||||
38
src/zen/tests/boosts/browser_boost_selector_basic.js
Normal file
38
src/zen/tests/boosts/browser_boost_selector_basic.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_getSelectionPath_basic() {
|
||||
const doc = document.implementation.createHTMLDocument("TestDoc");
|
||||
|
||||
const container = doc.createElement("div");
|
||||
container.id = "container";
|
||||
const child1 = doc.createElement("p");
|
||||
child1.className = "one";
|
||||
child1.textContent = "Text 1";
|
||||
const child2 = doc.createElement("span");
|
||||
child2.className = "two";
|
||||
child2.textContent = "Text 2";
|
||||
|
||||
container.appendChild(child1);
|
||||
container.appendChild(child2);
|
||||
doc.body.appendChild(container);
|
||||
|
||||
const component = new SelectorComponent(doc, null, [], () => {});
|
||||
|
||||
for (let i = 0; i <= 7; i++) {
|
||||
const path = component.getSelectionPath(doc, i, child2);
|
||||
ok(path, `getSelectionPath should return a path for relatedValueIndex=${i}`);
|
||||
|
||||
const selectedElements = doc.querySelectorAll(path);
|
||||
|
||||
if (i === 0)
|
||||
ok(
|
||||
selectedElements.length === 1,
|
||||
"For relatedValueIndex=1 there should be exactly one queried element"
|
||||
);
|
||||
|
||||
ok(selectedElements.length >= 1, "CSS path should select at least one element");
|
||||
}
|
||||
});
|
||||
26
src/zen/tests/boosts/browser_boost_selector_invalid.js
Normal file
26
src/zen/tests/boosts/browser_boost_selector_invalid.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_getSelectionPath_invalidNode() {
|
||||
const doc = document.implementation.createHTMLDocument("TestInvalid");
|
||||
const component = new SelectorComponent(doc, null, [], () => {});
|
||||
|
||||
// Null element
|
||||
Assert.equal(component.getSelectionPath(doc, 0, null), null, "Null element should return null");
|
||||
|
||||
// Body element
|
||||
Assert.equal(
|
||||
component.getSelectionPath(doc, 0, doc.body),
|
||||
null,
|
||||
"Body element should return null"
|
||||
);
|
||||
|
||||
// Html element
|
||||
Assert.equal(
|
||||
component.getSelectionPath(doc, 0, doc.documentElement),
|
||||
null,
|
||||
"HTML element should return null"
|
||||
);
|
||||
});
|
||||
39
src/zen/tests/boosts/browser_boost_selector_nthchild.js
Normal file
39
src/zen/tests/boosts/browser_boost_selector_nthchild.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_getSelectionPath_nthchild() {
|
||||
const doc = document.implementation.createHTMLDocument("TestDoc");
|
||||
const childCount = 10;
|
||||
|
||||
const container = doc.createElement("div");
|
||||
container.id = "container";
|
||||
|
||||
for (let i = 0; i < childCount; i++) {
|
||||
const child = doc.createElement("p");
|
||||
child.className = "child";
|
||||
child.textContent = `Child ${i}`;
|
||||
container.appendChild(child);
|
||||
}
|
||||
|
||||
doc.body.appendChild(container);
|
||||
|
||||
const component = new SelectorComponent(doc, null, [], () => {});
|
||||
|
||||
for (let i = 0; i < container.children.length; i++) {
|
||||
const currentNode = container.children[i];
|
||||
// Get exact element
|
||||
const path = component.getSelectionPath(doc, 0, currentNode);
|
||||
ok(path, "Path should be generated");
|
||||
|
||||
const selectedElements = doc.querySelectorAll(path);
|
||||
|
||||
ok(
|
||||
Array.from(selectedElements).includes(currentNode),
|
||||
"Selector must include the selected node"
|
||||
);
|
||||
|
||||
Assert.equal(selectedElements.length, 1, "Selector should uniquely identify the element");
|
||||
}
|
||||
});
|
||||
7
src/zen/tests/boosts/head.js
Normal file
7
src/zen/tests/boosts/head.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { SelectorComponent } = ChromeUtils.importESModule(
|
||||
"resource:///modules/zen/boosts/ZenSelectorComponent.sys.mjs"
|
||||
);
|
||||
@@ -3,6 +3,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
"boosts/browser.toml",
|
||||
"compact_mode/browser.toml",
|
||||
"container_essentials/browser.toml",
|
||||
"folders/browser.toml",
|
||||
|
||||
@@ -13,6 +13,7 @@ const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
FeatureCallout: "resource:///modules/asrouter/FeatureCallout.sys.mjs",
|
||||
gZenBoostsManager: "resource:///modules/zen/boosts/ZenBoostsManager.sys.mjs",
|
||||
});
|
||||
|
||||
export class nsZenSiteDataPanel {
|
||||
@@ -63,6 +64,7 @@ export class nsZenSiteDataPanel {
|
||||
|
||||
this.#initCopyUrlButton();
|
||||
this.#initEventListeners();
|
||||
this.#initBrowserListeners();
|
||||
this.#initUnifiedExtensionsManageHook();
|
||||
this.#maybeShowFeatureCallout();
|
||||
}
|
||||
@@ -76,6 +78,7 @@ export class nsZenSiteDataPanel {
|
||||
"zen-site-data-header-share",
|
||||
"zen-site-data-header-bookmark",
|
||||
"zen-site-data-security-info",
|
||||
"zen-site-data-boost",
|
||||
"zen-site-data-actions",
|
||||
"zen-site-data-new-addon-button",
|
||||
];
|
||||
@@ -87,6 +90,50 @@ export class nsZenSiteDataPanel {
|
||||
this.#initContextMenuEventListener();
|
||||
}
|
||||
|
||||
#initBrowserListeners() {
|
||||
Services.obs.addObserver(this, "zen-boosts-update");
|
||||
this.window.gBrowser.addProgressListener({
|
||||
onLocationChange: (aWebProgress) => {
|
||||
if (aWebProgress.isTopLevel) {
|
||||
this.checkIfTabIsBoosted();
|
||||
}
|
||||
},
|
||||
});
|
||||
this.window.addEventListener(
|
||||
"unload",
|
||||
() => {
|
||||
Services.obs.removeObserver(this, "zen-boosts-update");
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
|
||||
observe(subject, topic) {
|
||||
switch (topic) {
|
||||
case "zen-boosts-update":
|
||||
this.checkIfTabIsBoosted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#getCurrentDomain() {
|
||||
try {
|
||||
return this.window.gBrowser.currentURI.host;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
checkIfTabIsBoosted() {
|
||||
const domain = this.#getCurrentDomain();
|
||||
const isBoosted = lazy.gZenBoostsManager.registeredBoostForDomain(domain);
|
||||
if (isBoosted) {
|
||||
this.anchor.setAttribute("boosting", "true");
|
||||
} else {
|
||||
this.anchor.removeAttribute("boosting");
|
||||
}
|
||||
}
|
||||
|
||||
#initCopyUrlButton() {
|
||||
// This function is a bit out of place, but it's related enough to the panel
|
||||
// that it's easier to do it here than in a separate module.
|
||||
@@ -172,12 +219,131 @@ export class nsZenSiteDataPanel {
|
||||
}
|
||||
|
||||
#preparePanel() {
|
||||
this.#resetSiteOptionsList();
|
||||
this.#setSiteBoost();
|
||||
this.#setSitePermissions();
|
||||
this.#setSiteSecurityInfo();
|
||||
this.#setSiteHeader();
|
||||
this.#setAddonsOverflow();
|
||||
}
|
||||
|
||||
#setSiteBoost() {
|
||||
const domain = this.#getCurrentDomain();
|
||||
const uri = this.window.gBrowser.currentURI;
|
||||
const canBoostSite = lazy.gZenBoostsManager.canBoostSite(uri);
|
||||
|
||||
const list = this.document.getElementById("zen-site-data-boost-list");
|
||||
const section = list.closest(".zen-site-data-section");
|
||||
section.hidden = true;
|
||||
|
||||
const boostButton = this.document.getElementById("zen-site-data-boost");
|
||||
if (!canBoostSite) {
|
||||
boostButton.removeAttribute("boosting");
|
||||
}
|
||||
|
||||
if (!canBoostSite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lazy.gZenBoostsManager.registeredBoostForDomain(domain)) {
|
||||
boostButton.setAttribute("boosting", "true");
|
||||
} else {
|
||||
boostButton.removeAttribute("boosting");
|
||||
}
|
||||
|
||||
/* Boosts panel */
|
||||
|
||||
const boosts = lazy.gZenBoostsManager.loadBoostsFromStore(domain);
|
||||
let validBoostCount = 0;
|
||||
|
||||
if (boosts) {
|
||||
const activeBoostId = lazy.gZenBoostsManager.getActiveBoostId(domain);
|
||||
boosts.forEach((boost) => {
|
||||
const boostData = boost.boostEntry.boostData;
|
||||
if (!boostData.changeWasMade) {
|
||||
return;
|
||||
}
|
||||
validBoostCount++;
|
||||
|
||||
const enabled = boost.id === activeBoostId;
|
||||
list.appendChild(
|
||||
this.#createBoostPanelItem(
|
||||
"boost-brush",
|
||||
boostData.boostName,
|
||||
enabled ? "Enabled" : "Disabled",
|
||||
"zen-site-data-toggle-boost",
|
||||
boost,
|
||||
enabled
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
section.hidden = validBoostCount === 0;
|
||||
}
|
||||
|
||||
#updateSiteBoost() {
|
||||
const boostList = this.document.getElementById("zen-site-data-boost-list");
|
||||
boostList.innerHTML = "";
|
||||
|
||||
this.#setSiteBoost();
|
||||
}
|
||||
|
||||
#createBoostPanelItem(iconClass, title, description, actionId, boost = null, enabled = false) {
|
||||
const container = this.document.createXULElement("hbox");
|
||||
container.classList.add("permission-popup-boost-item");
|
||||
|
||||
container.setAttribute("align", "center");
|
||||
container.setAttribute("role", "group");
|
||||
container.setAttribute("data-action-id", actionId);
|
||||
container.setAttribute("state", "enabled");
|
||||
|
||||
if (boost) {
|
||||
container.setAttribute("data-boost-id", boost.id);
|
||||
container.setAttribute("state", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
const img = this.document.createXULElement("toolbarbutton");
|
||||
img.classList.add("permission-popup-boost-icon", "zen-site-data-boost-icon");
|
||||
img.setAttribute("closemenu", "none");
|
||||
img.classList.add(iconClass);
|
||||
|
||||
const labelContainer = this.document.createXULElement("vbox");
|
||||
labelContainer.setAttribute("flex", "1");
|
||||
labelContainer.setAttribute("align", "start");
|
||||
labelContainer.classList.add("permission-popup-boost-label-container");
|
||||
|
||||
const nameLabel = this.document.createXULElement("label");
|
||||
nameLabel.setAttribute("flex", "1");
|
||||
nameLabel.setAttribute("class", "permission-popup-boost-label");
|
||||
nameLabel.textContent = title || "";
|
||||
labelContainer.appendChild(nameLabel);
|
||||
|
||||
const stateLabel = this.document.createXULElement("label");
|
||||
stateLabel.setAttribute("class", "zen-permission-popup-boost-state-label");
|
||||
stateLabel.textContent = description || "";
|
||||
labelContainer.appendChild(stateLabel);
|
||||
|
||||
container.appendChild(img);
|
||||
container.appendChild(labelContainer);
|
||||
|
||||
if (boost) {
|
||||
const editorButton = this.document.createXULElement("toolbarbutton");
|
||||
editorButton.setAttribute("data-action-id", "zen-site-data-edit-boost");
|
||||
editorButton.setAttribute("data-boost-id", boost.id);
|
||||
editorButton.classList.add("zen-permission-popup-boost-editor-button");
|
||||
container.appendChild(editorButton);
|
||||
|
||||
editorButton.addEventListener("click", (event) => {
|
||||
event.stopPropagation(); // Prevents the container event
|
||||
this.#onBoostClick(event);
|
||||
});
|
||||
}
|
||||
|
||||
container.addEventListener("click", this.#onBoostClick.bind(this));
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
#setAddonsOverflow() {
|
||||
const addons = this.document.getElementById("zen-site-data-addons");
|
||||
if (addons.getBoundingClientRect().height > 420) {
|
||||
@@ -242,6 +408,13 @@ export class nsZenSiteDataPanel {
|
||||
return uri.scheme.startsWith("http");
|
||||
}
|
||||
|
||||
#resetSiteOptionsList() {
|
||||
const settingsList = this.document.getElementById("zen-site-data-settings-list");
|
||||
settingsList.innerHTML = "";
|
||||
const boostList = this.document.getElementById("zen-site-data-boost-list");
|
||||
boostList.innerHTML = "";
|
||||
}
|
||||
|
||||
#setSiteSecurityInfo() {
|
||||
const { gIdentityHandler } = this.window;
|
||||
const button = this.document.getElementById("zen-site-data-security-info");
|
||||
@@ -380,7 +553,6 @@ export class nsZenSiteDataPanel {
|
||||
}
|
||||
|
||||
const separator = this.document.createXULElement("toolbarseparator");
|
||||
list.innerHTML = "";
|
||||
list.appendChild(separator);
|
||||
const settingElements = [];
|
||||
const crossSiteCookieElements = [];
|
||||
@@ -517,6 +689,13 @@ export class nsZenSiteDataPanel {
|
||||
this.window.gIdentityHandler._openPopup(event);
|
||||
break;
|
||||
}
|
||||
case "zen-site-data-boost": {
|
||||
const domain = this.#getCurrentDomain();
|
||||
const uri = this.window.gBrowser.currentURI;
|
||||
const boost = lazy.gZenBoostsManager.createNewBoost(domain);
|
||||
lazy.gZenBoostsManager.openBoostWindow(this.window, boost, uri);
|
||||
break;
|
||||
}
|
||||
case "zen-site-data-actions": {
|
||||
const button = this.document.getElementById("zen-site-data-actions");
|
||||
const popup = this.document.getElementById("zenSiteDataActions");
|
||||
@@ -598,6 +777,34 @@ export class nsZenSiteDataPanel {
|
||||
}
|
||||
}
|
||||
|
||||
#onBoostClick(event) {
|
||||
const target = event.target.closest("[data-action-id]");
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionId = target.getAttribute("data-action-id");
|
||||
const domain = this.#getCurrentDomain();
|
||||
|
||||
switch (actionId) {
|
||||
case "zen-site-data-toggle-boost": {
|
||||
const boostId = target.getAttribute("data-boost-id");
|
||||
|
||||
lazy.gZenBoostsManager.toggleBoostActiveForDomain(domain, boostId);
|
||||
this.#updateSiteBoost();
|
||||
break;
|
||||
}
|
||||
case "zen-site-data-edit-boost": {
|
||||
const boostId = target.getAttribute("data-boost-id");
|
||||
const uri = this.window.gBrowser.currentURI;
|
||||
const boost = lazy.gZenBoostsManager.loadBoostFromStore(domain, boostId);
|
||||
lazy.gZenBoostsManager.openBoostWindow(this.window, boost, uri);
|
||||
this.unifiedPanel.hidePopup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onClickEvent(event) {
|
||||
const id = event.target.id;
|
||||
switch (id) {
|
||||
|
||||
@@ -1574,6 +1574,9 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
||||
browser.gZenThemePicker.recalculateDots(workspaceTheme.gradientColors);
|
||||
}
|
||||
});
|
||||
|
||||
// Notify observers that gradient updated
|
||||
Services.obs.notifyObservers(null, "zen-space-gradient-update");
|
||||
}
|
||||
|
||||
fixTheme(theme) {
|
||||
@@ -1676,8 +1679,8 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
||||
const gradient = nsZenThemePicker.getTheme(colors, this.currentOpacity, this.currentTexture);
|
||||
let currentWorkspace = gZenWorkspaces.getActiveWorkspace();
|
||||
|
||||
currentWorkspace.theme = gradient;
|
||||
if (!skipSave) {
|
||||
currentWorkspace.theme = gradient;
|
||||
gZenWorkspaces.saveWorkspace(currentWorkspace);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user