Skip to content

Latest commit

 

History

History
385 lines (321 loc) · 14 KB

Switch_Selected_Tab.md

File metadata and controls

385 lines (321 loc) · 14 KB
  • Assign selected tab to <tabbrowser>

    // gBrowser is `<tabbrowser>`
    gBrowser.selectedTab = tab
  • selectedTab of <tabbrowser> @tabbrowser.xml#tabbrowser

  • selectedTab of <tabbox> @tabbox.xml#tabbox

  • selectedItem of <tabs>

  • selectedIndex of <tabs>

    • Update tabs' selected states

      var alreadySelected = tab.selected;
      Array.forEach(this.childNodes, function(aTab) {
        if (aTab.selected && aTab != tab)
          aTab._selected = false;
      });
      tab._selected = true;
    • Update selected panel

      // linkedPanel is `<notificationbox>`(holding browser) under `<tabpanels>` under `<tabbox>` under `<tabbrowser>`
      let linkedPanel = this.getRelatedElement(tab);
      if (linkedPanel) {
        this.tabbox.setAttribute("selectedIndex", val);
      
        // tabpanels is `<tabpanels>`
        // This will cause an onselect event to fire for the tabpanel element.
        this.tabbox.tabpanels.selectedPanel = linkedPanel;
      }
  • selectedPanel of <tabpanels> @tabbrowser.xml#tabbrowser-tabpanels > tabbox.xml#tabpanels

    • Find the index of selected panel in the DOM(<tabpanels>)
      var selectedIndex = -1;
      for (var panel = val; panel != null; panel = panel.previousSibling)
        ++selectedIndex;
      this.selectedIndex = selectedIndex;
      return val;
  • selectedIndex of <tabpanels> @tabbrowser.xml#tabbrowser-tabpanels

    • Request the tab switcher to switch tab

      // ... ...
      gBrowser._getSwitcher().requestTab(toTab);
      // ... ...
    • P2: Important! Fire the select event so that the tab switcher later could know time to adjust tab focus

      // ... ...
      this._selectedPanel = newPanel;
      if (this._selectedPanel != panel) {
        var event = document.createEvent("Events");
        event.initEvent("select", true, true);
        this.dispatchEvent(event);
        this._selectedIndex = val;
      }
  • requestTab of the tab switcher(_getSwitcher) @tabbrowser.xml#tabbrowser

    • Warm the tab (disabled by Bug 1394455)

    • Suppress displayport of the requested tab and queue unload job

      this.requestedTab = tab;
      this.suppressDisplayPortAndQueueUnload(this.requestedTab, this.UNLOAD_DELAY);
  • suppressDisplayPortAndQueueUnload of the tab switcher

    • We don't want to paint the requested tab temporarily during siwtching tab

      // tab is the requested tab
      let browser = tab.linkedBrowser;
      let fl = browser.frameLoader;
      if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
        // `suppressDisplayport` is nsITabParent::suppressDisplayport
        fl.tabParent.suppressDisplayport(true);
        this.activeSuppressDisplayport.add(fl.tabParent);
      }
    • P1: Queue to unload unused tabs. See Section: onUnloadTimeout

      // This won't run immediately so loading the requested tab should go first
      this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), unloadTimeout);
  • postAction of the tab switcher

    • Load the requested tab if required

      // If we're not loading anything, try loading the requested tab.
      let requestedState = this.getTabState(this.requestedTab);
      if (!this.loadTimer && !this.minimizedOrFullyOccluded &&
          (requestedState == this.STATE_UNLOADED ||
           requestedState == this.STATE_UNLOADING)) {
        this.loadRequestedTab();
      }
    • Decide which tab to display, such as a blank tab, spinner tab, or requested tab, then switch to it

      this.updateDisplay();
      • updateDisplay of the tab switcher
        • Switch to the requested tab visible. This makes other tabs' frames not being rendered. If wanted to render multiple tabs' frames at the same time, would need display: -moz-stack. See Section: -moz-deck and -moz-stack.
          // If the display of `<tabpanels>` is `-moz-deck`(by default),
          // updating the "selectedIndex" attirbute would switch to and make the requested tab visible
          tabPanel.setAttribute("selectedIndex", index);
    • Maybe finish

      // ... ...
      
      // Unload the redundant warming tabs
      if (numWarming > this.tabbrowser.tabWarmingMax) {
        this.logState("Hit tabWarmingMax");
        if (this.unloadTimer) {
          this.clearTimer(this.unloadTimer);
        }
        this.unloadNonRequiredTabs();
      }
      
      // There still might be some not-yet-unloaded out there.
      // The `onUnloadTimeout1 scheduled in the P1 position would help us to unload them, then finish.
      if (numPending == 0) {
        this.finish();
      }

Section: onUnloadTimeout

  • onUnloadTimeout of the tab switcher

  • unloadNonRequiredTabs of the tab switcher

    If there are any non-visible and non-requested tabs in STATE_LOADED, sets them to STATE_UNLOADING.

    Also queues up the unloadTimer to run onUnloadTimeout if there are still tabs in the process of unloading.

  • postActions of the tab switcher

    This call would finish the tab switch if no more pending unloaded tabs out there

Section: docShellIsActive

  • IDL: nsITabParent::docShellIsActive

  • Implementation:

    • Getter: TabParent::GetDocShellIsActive
    • Setter: TabParent::SetDocShellIsActive
  • In JS, call one <browser>'s docShellIsActive

    // This would have the browser visible and being painted.
    gBrowser.selectedBrowser.docShellIsActive = true;
  • TabParent::SetDocShellIsActive would receive the call from JS

    • UPDATE: Bug 1397426 - Make tab browser warming API not set the docshell to be active changes the behavior ofSetDocShellIsActive .
    • Send Browser::Msg_SetDocShellIsActive__ID msg to TabChild
      // `mPreserveLayers` defaults to false and if false and deactiving, then tab would be hidden in the end.
      // `mLayerTreeEpoch` is used to rule out the old request.
      Unused << SendSetDocShellIsActive(isActive, mPreserveLayers, mLayerTreeEpoch);
  • TabChild::RecvSetDocShellIsActive

    • Receive the ipc call from TabParent
      // Since requests to change the active docshell come in from both the hang
      // monitor channel and the PContent channel, we have an ordering problem. This
      // code ensures that we respect the order in which the requests were made and
      // ignore stale requests.
      if (mLayerObserverEpoch >= aLayerObserverEpoch) {
        return IPC_OK();
      }
      mLayerObserverEpoch = aLayerObserverEpoch;
      
      // ... ...
  • TabChild::InternalSetDocShellIsActive

    // ... ...
    
    nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
    
    // ... ...
    
    if (aIsActive) {
      MakeVisible(); // Make tab visible
    
      if (!docShell) {
        return;
      }
    
      // We don't use TabChildBase::GetPresShell() here because that would create
      // a content viewer if one doesn't exist yet. Creating a content viewer can
      // cause JS to run, which we want to avoid. nsIDocShell::GetPresShell
      // returns null if no content viewer exists yet.
      if (nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell()) {
        if (nsIFrame* root = presShell->FrameConstructor()->GetRootFrame()) {
          FrameLayerBuilder::InvalidateAllLayersForFrame(
            nsLayoutUtils::GetDisplayRootFrame(root));
          root->SchedulePaint();
        }
    
        Telemetry::AutoTimer<Telemetry::TABCHILD_PAINT_TIME> timer;
        // If we need to repaint, let's do that right away. No sense waiting until
        // we get back to the event loop again. We suppress the display port so that
        // we only paint what's visible. This ensures that the tab we're switching
        // to paints as quickly as possible.
        APZCCallbackHelper::SuppressDisplayport(true, presShell);
        if (nsContentUtils::IsSafeToRunScript()) {
          WebWidget()->PaintNowIfNeeded();
        } else {
          RefPtr<nsViewManager> vm = presShell->GetViewManager();
          if (nsView* view = vm->GetRootView()) {
            presShell->Paint(view, view->GetBounds(), nsIPresShell::PAINT_LAYERS);
          }
        }
        APZCCallbackHelper::SuppressDisplayport(false, presShell);
      }
    } else if (!aPreserveLayers) {
      MakeHidden();
    }
  • TabParent::SetDocShellIsActive

    • Force painting after sending Browser::Msg_SetDocShellIsActive__ID ipc msg
      // Ask the child to repaint using the PHangMonitor channel/thread (which may
      // be less congested).
      if (isActive) {
        ContentParent* cp = Manager()->AsContentParent();
        cp->ForceTabPaint(this, mLayerTreeEpoch);
      }

Section: -moz-deck and -moz-stack

-moz-deck

  • The representing frame class is nsDeckFrame @ nsDeckFrame.cpp

  • Only could draw one frame at one time

  • While xul element's "selectedIndex" changed, it would observer and then update the displayed frame

    • nsDeckFrame::AttributeChanged

       // if the index changed hide the old element and make the new element visible
      if (aAttribute == nsGkAtoms::selectedIndex) {
        IndexChanged();
      }
    • nsDeckFrame::IndexChanged

      //did the index change?
      int32_t index = GetSelectedIndex();
      if (index == mIndex)
        return;
      
      // redraw
      InvalidateFrame();
      
      // ... ...
    • nsDeckFrame::GetSelectedIndex

      // get the index attribute
      nsAutoString value;
      if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::selectedIndex, value))
      {
        nsresult error;
        // convert it to an integer
        index = value.ToInteger(&error);
      }

-moz-stack

  • The representing frame class is nsStackFrame @ nsStackFrame.cpp

  • Basically frames are on top of each other one by one and could draw multiple frames at the same time.

  • To render 2 or more tab's frames at the same time, we need

    • Set 2 tab's browsers' docShell as active

    • Reveal the below tab from the top tab, for example

      <!--  The total width is 1000px -->
      <tabpanels style="display: -moz-stack">
        <!-- 500px right margin makes this panel take 500px left side -->
        <notificationbox id="panel-1" style="margin-right: 500px">
          <!--  This broswer's docShell has to be active -->
          <browser id="browser-1" ></browser>
        </notificationbox>
        <!-- 500px left margin makes this panel take 500px right side
             so that could reveal the #panel-1 beath it on the half left side. -->
        <notificationbox id="panel-2" style="margin-left: 500px">
          <!--  This broswer's docShell has to be active -->
          <browser id="browser-2></browser>
        </notificationbox>
      </tabpanels>