summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/theme-switching.js
blob: bc4d481a2f70266c951a9430282298c2f1bc836f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/* 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/. */

/* eslint-env browser */
"use strict";
(function () {
  const { require } = ChromeUtils.importESModule(
    "resource://devtools/shared/loader/Loader.sys.mjs"
  );
  const {
    gDevTools,
  } = require("resource://devtools/client/framework/devtools.js");
  const {
    appendStyleSheet,
  } = require("resource://devtools/client/shared/stylesheet-utils.js");

  const {
    getTheme,
    getAutoTheme,
    addThemeObserver,
    removeThemeObserver,
  } = require("resource://devtools/client/shared/theme.js");

  const documentElement = document.documentElement;

  let os;
  const platform = navigator.platform;
  if (platform.startsWith("Win")) {
    os = "win";
  } else if (platform.startsWith("Mac")) {
    os = "mac";
  } else {
    os = "linux";
  }

  documentElement.setAttribute("platform", os);

  // no-theme attributes allows to just est the platform attribute
  // to have per-platform CSS working correctly.
  if (documentElement.getAttribute("no-theme") === "true") {
    return;
  }

  const devtoolsStyleSheets = new WeakMap();
  let gOldTheme = "";

  /*
   * Notify the window that a theme switch finished so tests can check the DOM
   */
  function notifyWindow() {
    window.dispatchEvent(new CustomEvent("theme-switch-complete", {}));
  }

  /*
   * Apply all the sheets from `newTheme` and remove all of the sheets
   * from `oldTheme`
   */
  function switchTheme(newTheme) {
    if (newTheme === gOldTheme) {
      return;
    }
    const oldTheme = gOldTheme;
    gOldTheme = newTheme;

    const oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
    let newThemeDef = gDevTools.getThemeDefinition(newTheme);

    // The theme might not be available anymore (e.g. uninstalled)
    // Use the default one.
    if (!newThemeDef) {
      newThemeDef = gDevTools.getThemeDefinition(getAutoTheme());
    }

    // Store the sheets in a WeakMap for access later when the theme gets
    // unapplied.  It's hard to query for processing instructions so this
    // is an easy way to access them later without storing a property on
    // the window
    devtoolsStyleSheets.set(newThemeDef, []);

    const loadEvents = [];
    for (const url of newThemeDef.stylesheets) {
      const { styleSheet, loadPromise } = appendStyleSheet(document, url);
      devtoolsStyleSheets.get(newThemeDef).push(styleSheet);
      loadEvents.push(loadPromise);
    }

    Promise.all(loadEvents).then(() => {
      // Unload all stylesheets and classes from the old theme.
      if (oldThemeDef) {
        for (const name of oldThemeDef.classList) {
          documentElement.classList.remove(name);
        }

        for (const sheet of devtoolsStyleSheets.get(oldThemeDef) || []) {
          sheet.remove();
        }

        if (oldThemeDef.onUnapply) {
          oldThemeDef.onUnapply(window, newTheme);
        }
      }

      // Load all stylesheets and classes from the new theme.
      for (const name of newThemeDef.classList) {
        documentElement.classList.add(name);
      }

      if (newThemeDef.onApply) {
        newThemeDef.onApply(window, oldTheme);
      }

      // Final notification for further theme-switching related logic.
      gDevTools.emit("theme-switched", window, newTheme, oldTheme);
      notifyWindow();
    }, console.error);
  }

  function handleThemeChange() {
    switchTheme(getTheme());
  }

  // Check if the current document or the embedder of the document enforces a
  // theme.
  const forcedTheme =
    documentElement.getAttribute("force-theme") ||
    window.top.document.documentElement.getAttribute("force-theme");

  if (forcedTheme) {
    switchTheme(forcedTheme);
  } else {
    switchTheme(getTheme());

    addThemeObserver(handleThemeChange);
    window.addEventListener(
      "unload",
      function () {
        removeThemeObserver(handleThemeChange);
      },
      { once: true }
    );
  }
})();