tor-browser

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

viewpage.mjs (7438B)


      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 import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
      6 
      7 // eslint-disable-next-line import/no-unassigned-import
      8 import "chrome://browser/content/firefoxview/card-container.mjs";
      9 // eslint-disable-next-line import/no-unassigned-import
     10 import "chrome://browser/content/firefoxview/fxview-empty-state.mjs";
     11 // eslint-disable-next-line import/no-unassigned-import
     12 import "chrome://browser/content/firefoxview/fxview-tab-list.mjs";
     13 
     14 const lazy = {};
     15 
     16 ChromeUtils.defineESModuleGetters(lazy, {
     17  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
     18  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
     19 });
     20 
     21 const WIN_RESIZE_DEBOUNCE_RATE_MS = 500;
     22 const WIN_RESIZE_DEBOUNCE_TIMEOUT_MS = 1000;
     23 
     24 /**
     25 * A base class for content container views displayed on firefox-view.
     26 *
     27 * @property {boolean} recentBrowsing
     28 *   Is part of the recentbrowsing page view
     29 * @property {boolean} paused
     30 *   No content will be updated and rendered while paused
     31 */
     32 export class ViewPageContent extends MozLitElement {
     33  static get properties() {
     34    return {
     35      recentBrowsing: { type: Boolean },
     36      paused: { type: Boolean },
     37    };
     38  }
     39  constructor() {
     40    super();
     41    // don't update or render until explicitly un-paused
     42    this.paused = true;
     43  }
     44 
     45  get ownerViewPage() {
     46    return this.closest("[type='page']") || this;
     47  }
     48 
     49  get isVisible() {
     50    if (!this.isConnected || this.ownerDocument.visibilityState != "visible") {
     51      return false;
     52    }
     53    return this.ownerViewPage.selectedTab;
     54  }
     55 
     56  /**
     57   * Override this function to run a callback whenever this content is visible.
     58   */
     59  viewVisibleCallback() {}
     60 
     61  /**
     62   * Override this function to run a callback whenever this content is hidden.
     63   */
     64  viewHiddenCallback() {}
     65 
     66  getWindow() {
     67    return window.browsingContext.embedderWindowGlobal.browsingContext.window;
     68  }
     69 
     70  get isSelectedBrowserTab() {
     71    const { gBrowser } = this.getWindow();
     72    return gBrowser.selectedBrowser.browsingContext == window.browsingContext;
     73  }
     74 
     75  copyLink(e) {
     76    lazy.BrowserUtils.copyLink(this.triggerNode.url, this.triggerNode.title);
     77    this.recordContextMenuTelemetry("copy-link", e);
     78  }
     79 
     80  openInNewWindow(e) {
     81    this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
     82      private: false,
     83    });
     84    this.recordContextMenuTelemetry("open-in-new-window", e);
     85  }
     86 
     87  openInNewPrivateWindow(e) {
     88    this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
     89      private: true,
     90    });
     91    this.recordContextMenuTelemetry("open-in-private-window", e);
     92  }
     93 
     94  recordContextMenuTelemetry(menuAction, event) {
     95    Glean.firefoxviewNext.contextMenuTabs.record({
     96      menu_action: menuAction,
     97      data_type: event.target.panel.dataset.tabType,
     98    });
     99  }
    100 
    101  shouldUpdate(changedProperties) {
    102    return !this.paused && super.shouldUpdate(changedProperties);
    103  }
    104 }
    105 
    106 /**
    107 * A "page" in firefox view, which may be hidden or shown by the named-deck container or
    108 * via the owner document's visibility
    109 *
    110 * @property {boolean} selectedTab
    111 *   Is this page the selected view in the named-deck container
    112 */
    113 export class ViewPage extends ViewPageContent {
    114  static get properties() {
    115    return {
    116      selectedTab: { type: Boolean },
    117      searchTextboxSize: { type: Number },
    118    };
    119  }
    120 
    121  constructor() {
    122    super();
    123    this.selectedTab = false;
    124    this.recentBrowsing = Boolean(this.recentBrowsingElement);
    125    this.onTabSelect = this.onTabSelect.bind(this);
    126    this.onResize = this.onResize.bind(this);
    127  }
    128 
    129  get recentBrowsingElement() {
    130    return this.closest("VIEW-RECENTBROWSING");
    131  }
    132 
    133  onResize() {
    134    this.windowResizeTask = new lazy.DeferredTask(
    135      () => this.updateAllVirtualLists(),
    136      WIN_RESIZE_DEBOUNCE_RATE_MS,
    137      WIN_RESIZE_DEBOUNCE_TIMEOUT_MS
    138    );
    139    this.windowResizeTask?.arm();
    140  }
    141 
    142  onTabSelect({ target }) {
    143    const win = target.ownerGlobal;
    144 
    145    let selfBrowser = window.docShell?.chromeEventHandler;
    146    const { gBrowser } = this.getWindow();
    147    let isForegroundTab = gBrowser.selectedBrowser == selfBrowser;
    148 
    149    if (win.FirefoxViewHandler.tab?.selected && isForegroundTab) {
    150      this.paused = false;
    151      this.viewVisibleCallback();
    152    } else {
    153      this.paused = true;
    154      this.viewHiddenCallback();
    155    }
    156  }
    157 
    158  connectedCallback() {
    159    super.connectedCallback();
    160  }
    161 
    162  disconnectedCallback() {
    163    super.disconnectedCallback();
    164    this.getWindow().removeEventListener("resize", this.onResize);
    165    this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
    166  }
    167 
    168  updateAllVirtualLists() {
    169    if (!this.paused) {
    170      let tabLists = [];
    171      if (this.recentBrowsing) {
    172        let viewComponents = this.querySelectorAll("[slot]");
    173        viewComponents.forEach(viewComponent => {
    174          let currentTabLists = [];
    175          if (viewComponent.nodeName.includes("OPENTABS")) {
    176            viewComponent.viewCards.forEach(viewCard => {
    177              currentTabLists.push(viewCard.tabList);
    178            });
    179          } else {
    180            currentTabLists =
    181              viewComponent.shadowRoot.querySelectorAll("fxview-tab-list");
    182          }
    183          tabLists.push(...currentTabLists);
    184        });
    185      } else {
    186        tabLists = this.shadowRoot.querySelectorAll("fxview-tab-list");
    187      }
    188      tabLists.forEach(tabList => {
    189        if (!tabList.updatesPaused && tabList.rootVirtualListEl?.isVisible) {
    190          tabList.rootVirtualListEl.recalculateAfterWindowResize();
    191        }
    192      });
    193    }
    194  }
    195 
    196  toggleVisibilityInCardContainer(isOpenTabs) {
    197    let cards = [];
    198    let tabLists = [];
    199    if (!isOpenTabs) {
    200      cards = this.shadowRoot.querySelectorAll("card-container");
    201      tabLists = this.shadowRoot.querySelectorAll(
    202        "fxview-tab-list, syncedtabs-tab-list"
    203      );
    204    } else {
    205      this.viewCards.forEach(viewCard => {
    206        if (viewCard.cardEl) {
    207          cards.push(viewCard.cardEl);
    208          tabLists.push(viewCard.tabList);
    209        }
    210      });
    211    }
    212    if (tabLists.length && cards.length) {
    213      cards.forEach(cardEl => {
    214        if (cardEl.visible !== !this.paused) {
    215          cardEl.visible = !this.paused;
    216        } else if (
    217          cardEl.isExpanded &&
    218          Array.from(tabLists).some(
    219            tabList => tabList.updatesPaused !== this.paused
    220          )
    221        ) {
    222          // If card is already visible and expanded but tab-list has updatesPaused,
    223          // update the tab-list updatesPaused prop from here instead of card-container
    224          tabLists.forEach(tabList => {
    225            tabList.updatesPaused = this.paused;
    226          });
    227        }
    228      });
    229    }
    230  }
    231 
    232  enter() {
    233    this.selectedTab = true;
    234    if (this.isVisible) {
    235      this.paused = false;
    236      this.viewVisibleCallback();
    237      this.getWindow().addEventListener("resize", this.onResize);
    238      this.getWindow().addEventListener("TabSelect", this.onTabSelect);
    239    }
    240  }
    241 
    242  exit() {
    243    this.selectedTab = false;
    244    this.paused = true;
    245    this.viewHiddenCallback();
    246    if (!this.windowResizeTask?.isFinalized) {
    247      this.windowResizeTask?.finalize();
    248    }
    249    this.getWindow().removeEventListener("resize", this.onResize);
    250    this.getWindow().removeEventListener("TabSelect", this.onTabSelect);
    251  }
    252 }