tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ToolbarIconColor.sys.mjs (4474B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* Module is used to set or remove the "brighttext" attribute based on
      6 * calculating a luminance value from the current toolbar color.
      7 * This causes items like icons on the toolbar to contrast in brightness
      8 * enough to be visible, depending on the current theme/coloring of the browser
      9 * window. Calculated luminance values are cached in `state.toolbarLuminanceCache`. */
     10 
     11 // Track individual windowstates using WeakMap
     12 const _windowStateMap = new WeakMap();
     13 
     14 export const ToolbarIconColor = {
     15  init(window) {
     16    if (_windowStateMap.has(window)) {
     17      return;
     18    }
     19 
     20    const state = {
     21      active: false,
     22      fullscreen: false,
     23      customtitlebar: false,
     24      toolbarLuminanceCache: new Map(),
     25    };
     26 
     27    _windowStateMap.set(window, state);
     28 
     29    window.addEventListener("nativethemechange", this);
     30    window.addEventListener("activate", this);
     31    window.addEventListener("deactivate", this);
     32    window.addEventListener("toolbarvisibilitychange", this);
     33    window.addEventListener("windowlwthemeupdate", this);
     34 
     35    // If the window isn't active now, we assume that it has never been active
     36    // before and will soon become active such that inferFromText will be
     37    // called from the initial activate event.
     38    if (Services.focus.activeWindow == window) {
     39      this.inferFromText("activate", window);
     40    }
     41  },
     42 
     43  uninit(window) {
     44    const state = _windowStateMap.get(window);
     45    if (!state) {
     46      return;
     47    }
     48 
     49    window.removeEventListener("nativethemechange", this);
     50    window.removeEventListener("activate", this);
     51    window.removeEventListener("deactivate", this);
     52    window.removeEventListener("toolbarvisibilitychange", this);
     53    window.removeEventListener("windowlwthemeupdate", this);
     54 
     55    _windowStateMap.delete(window);
     56  },
     57 
     58  handleEvent(event) {
     59    const window = event.target.ownerGlobal;
     60    switch (event.type) {
     61      case "activate":
     62      case "deactivate":
     63      case "nativethemechange":
     64      case "windowlwthemeupdate":
     65        this.inferFromText(event.type, window);
     66        break;
     67      case "toolbarvisibilitychange":
     68        this.inferFromText(event.type, window, event.visible);
     69        break;
     70    }
     71  },
     72 
     73  inferFromText(reason, window, reasonValue) {
     74    const state = _windowStateMap.get(window);
     75 
     76    if (!state) {
     77      return;
     78    }
     79 
     80    switch (reason) {
     81      case "activate": // falls through
     82      case "deactivate":
     83        state.active = reason === "activate";
     84        break;
     85      case "fullscreen":
     86        state.fullscreen = reasonValue;
     87        break;
     88      case "nativethemechange":
     89      case "windowlwthemeupdate":
     90        // theme change, we'll need to recalculate all color values
     91        state.toolbarLuminanceCache.clear();
     92        break;
     93      case "toolbarvisibilitychange":
     94        // toolbar changes dont require reset of the cached color values
     95        break;
     96      case "customtitlebar":
     97        state.customtitlebar = reasonValue;
     98        break;
     99    }
    100 
    101    let toolbarSelector = ".browser-toolbar:not([collapsed])";
    102    if (Services.appinfo.nativeMenubar) {
    103      toolbarSelector += ":not([type=menubar])";
    104    }
    105 
    106    // The getComputedStyle calls and setting the brighttext are separated in
    107    // two loops to avoid flushing layout and making it dirty repeatedly.
    108    let cachedLuminances = state.toolbarLuminanceCache;
    109    let luminances = new Map();
    110    for (let toolbar of window.document.querySelectorAll(toolbarSelector)) {
    111      // toolbars *should* all have ids, but guard anyway to avoid blowing up
    112      let cacheKey = toolbar.id && toolbar.id + JSON.stringify(state);
    113      // lookup cached luminance value for this toolbar in this window state
    114      let luminance = cacheKey && cachedLuminances.get(cacheKey);
    115      if (isNaN(luminance)) {
    116        let { r, g, b } = InspectorUtils.colorToRGBA(
    117          window.getComputedStyle(toolbar).color
    118        );
    119        luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
    120        if (cacheKey) {
    121          cachedLuminances.set(cacheKey, luminance);
    122        }
    123      }
    124      luminances.set(toolbar, luminance);
    125    }
    126 
    127    const luminanceThreshold = 127; // In between 0 and 255
    128    for (let [toolbar, luminance] of luminances) {
    129      toolbar.toggleAttribute("brighttext", luminance > luminanceThreshold);
    130    }
    131  },
    132 };