diff options
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/bg/data/options-schema.json | 7 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 8 | ||||
| -rw-r--r-- | ext/bg/js/options.js | 3 | ||||
| -rw-r--r-- | ext/bg/settings.html | 4 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 2 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 62 | ||||
| -rw-r--r-- | ext/mixed/js/api.js | 4 | ||||
| -rw-r--r-- | ext/mixed/js/dynamic-loader.js | 49 | 
8 files changed, 119 insertions, 20 deletions
| diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index f8791433..b56017bc 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -110,7 +110,8 @@                                      "showPitchAccentPositionNotation",                                      "showPitchAccentGraph",                                      "showIframePopupsInRootFrame", -                                    "useSecurePopupFrameUrl" +                                    "useSecurePopupFrameUrl", +                                    "usePopupShadowDom"                                  ],                                  "properties": {                                      "enable": { @@ -252,6 +253,10 @@                                      "useSecurePopupFrameUrl": {                                          "type": "boolean",                                          "default": true +                                    }, +                                    "usePopupShadowDom": { +                                        "type": "boolean", +                                        "default": true                                      }                                  }                              }, diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index b89cb641..344706d1 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -108,6 +108,7 @@ class Backend {              ['broadcastTab',                 {async: false, contentScript: true,  handler: this._onApiBroadcastTab.bind(this)}],              ['frameInformationGet',          {async: true,  contentScript: true,  handler: this._onApiFrameInformationGet.bind(this)}],              ['injectStylesheet',             {async: true,  contentScript: true,  handler: this._onApiInjectStylesheet.bind(this)}], +            ['getStylesheetContent',         {async: true,  contentScript: true,  handler: this._onApiGetStylesheetContent.bind(this)}],              ['getEnvironmentInfo',           {async: false, contentScript: true,  handler: this._onApiGetEnvironmentInfo.bind(this)}],              ['clipboardGet',                 {async: true,  contentScript: true,  handler: this._onApiClipboardGet.bind(this)}],              ['getDisplayTemplatesHtml',      {async: true,  contentScript: true,  handler: this._onApiGetDisplayTemplatesHtml.bind(this)}], @@ -719,6 +720,13 @@ class Backend {          });      } +    async _onApiGetStylesheetContent({url}) { +        if (!url.startsWith('/') || url.startsWith('//') || !url.endsWith('.css')) { +            throw new Error('Invalid URL'); +        } +        return await requestText(url, 'GET'); +    } +      _onApiGetEnvironmentInfo() {          return this.environment.getInfo();      } diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 151c945b..ccc56848 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -177,7 +177,8 @@ function profileOptionsCreateDefaults() {              showPitchAccentPositionNotation: true,              showPitchAccentGraph: false,              showIframePopupsInRootFrame: false, -            useSecurePopupFrameUrl: true +            useSecurePopupFrameUrl: true, +            usePopupShadowDom: true          },          audio: { diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 4de70b7e..51cb14e7 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -187,6 +187,10 @@                  </div>                  <div class="checkbox options-advanced"> +                    <label><input type="checkbox" data-setting="general.usePopupShadowDom"> Use shadow DOM container for popup</label> +                </div> + +                <div class="checkbox options-advanced">                      <label><input type="checkbox" id="show-debug-info" data-setting="general.debugInfo" data-transform-pre="setDocumentAttribute" data-transform-post="setDocumentAttribute" data-document-attribute="data-options-general-debug-info"> Show debug information</label>                  </div> diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index ae0953f9..f6b0d236 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -345,7 +345,7 @@ class Frontend {      }      _ignoreElements() { -        return this._popup === null || this._popup.isProxy() ? [] : [this._popup.getFrame()]; +        return this._popup === null || this._popup.isProxy() ? [] : [this._popup.getContainer()];      }      _ignorePoint(x, y) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 3b14d3d0..5ee62c9b 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -47,6 +47,9 @@ class Popup {          this._frame.style.width = '0';          this._frame.style.height = '0'; +        this._container = this._frame; +        this._shadow = null; +          this._fullscreenEventListeners = new EventListenerCollection();      } @@ -180,7 +183,12 @@ class Popup {      }      async setCustomOuterCss(css, useWebExtensionApi) { -        return await dynamicLoader.loadStyle('yomichan-popup-outer-user-stylesheet', 'code', css, useWebExtensionApi); +        let parentNode = null; +        if (this._shadow !== null) { +            useWebExtensionApi = false; +            parentNode = this._shadow; +        } +        return await dynamicLoader.loadStyle('yomichan-popup-outer-user-stylesheet', 'code', css, useWebExtensionApi, parentNode);      }      setChildrenSupported(value) { @@ -195,6 +203,10 @@ class Popup {          return this._frame.getBoundingClientRect();      } +    getContainer() { +        return this._container; +    } +      // Private functions      _inject() { @@ -330,9 +342,9 @@ class Popup {              throw new Error('Options not initialized');          } -        const {useSecurePopupFrameUrl} = this._options.general; +        const {useSecurePopupFrameUrl, usePopupShadowDom} = this._options.general; -        this._injectStyles(); +        await this._setUpContainer(usePopupShadowDom);          const {secret, token} = await this._initializeFrame(this._frame, this._targetOrigin, this._frameId, (frame) => {              frame.removeAttribute('src'); @@ -382,9 +394,9 @@ class Popup {      }      _resetFrame() { -        const parent = this._frame.parentNode; +        const parent = this._container.parentNode;          if (parent !== null) { -            parent.removeChild(this._frame); +            parent.removeChild(this._container);          }          this._frame.removeAttribute('src');          this._frame.removeAttribute('srcdoc'); @@ -395,9 +407,31 @@ class Popup {          this._injectPromiseComplete = false;      } +    async _setUpContainer(usePopupShadowDom) { +        if (usePopupShadowDom && typeof this._frame.attachShadow === 'function') { +            const container = document.createElement('div'); +            container.style.setProperty('all', 'initial', 'important'); +            const shadow = container.attachShadow({mode: 'closed', delegatesFocus: true}); +            shadow.appendChild(this._frame); + +            this._container = container; +            this._shadow = shadow; +        } else { +            const frameParentNode = this._frame.parentNode; +            if (frameParentNode !== null) { +                frameParentNode.removeChild(this._frame); +            } + +            this._container = this._frame; +            this._shadow = null; +        } + +        await this._injectStyles(); +    } +      async _injectStyles() {          try { -            await dynamicLoader.loadStyle('yomichan-popup-outer-stylesheet', 'file', '/fg/css/client.css', true); +            await this._injectPopupOuterStylesheet();          } catch (e) {              // NOP          } @@ -409,6 +443,18 @@ class Popup {          }      } +    async _injectPopupOuterStylesheet() { +        let fileType = 'file'; +        let useWebExtensionApi = true; +        let parentNode = null; +        if (this._shadow !== null) { +            fileType = 'file-content'; +            useWebExtensionApi = false; +            parentNode = this._shadow; +        } +        await dynamicLoader.loadStyle('yomichan-popup-outer-stylesheet', fileType, '/fg/css/client.css', useWebExtensionApi, parentNode); +    } +      _observeFullscreen(observe) {          if (!observe) {              this._fullscreenEventListeners.removeAllEventListeners(); @@ -425,8 +471,8 @@ class Popup {      _onFullscreenChanged() {          const parent = this._getFrameParentElement(); -        if (parent !== null && this._frame.parentNode !== parent) { -            parent.appendChild(this._frame); +        if (parent !== null && this._container.parentNode !== parent) { +            parent.appendChild(this._container);          }      } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index c54196e2..5c17d50e 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -121,6 +121,10 @@ const api = (() => {              return this._invoke('injectStylesheet', {type, value});          } +        getStylesheetContent(url) { +            return this._invoke('getStylesheetContent', {url}); +        } +          getEnvironmentInfo() {              return this._invoke('getEnvironmentInfo');          } diff --git a/ext/mixed/js/dynamic-loader.js b/ext/mixed/js/dynamic-loader.js index 37f85112..981d1ee5 100644 --- a/ext/mixed/js/dynamic-loader.js +++ b/ext/mixed/js/dynamic-loader.js @@ -21,14 +21,36 @@  const dynamicLoader = (() => {      const injectedStylesheets = new Map(); +    const injectedStylesheetsWithParent = new WeakMap(); -    async function loadStyle(id, type, value, useWebExtensionApi=false) { +    function getInjectedStylesheet(id, parentNode) { +        if (parentNode === null) { +            return injectedStylesheets.get(id); +        } +        const map = injectedStylesheetsWithParent.get(parentNode); +        return typeof map !== 'undefined' ? map.get(id) : void 0; +    } + +    function setInjectedStylesheet(id, parentNode, value) { +        if (parentNode === null) { +            injectedStylesheets.set(id, value); +            return; +        } +        let map = injectedStylesheetsWithParent.get(parentNode); +        if (typeof map === 'undefined') { +            map = new Map(); +            injectedStylesheetsWithParent.set(parentNode, map); +        } +        map.set(id, value); +    } + +    async function loadStyle(id, type, value, useWebExtensionApi=false, parentNode=null) {          if (useWebExtensionApi && yomichan.isExtensionUrl(window.location.href)) {              // Permissions error will occur if trying to use the WebExtension API to inject into an extension page              useWebExtensionApi = false;          } -        let styleNode = injectedStylesheets.get(id); +        let styleNode = getInjectedStylesheet(id, parentNode);          if (typeof styleNode !== 'undefined') {              if (styleNode === null) {                  // Previously injected via WebExtension API @@ -38,21 +60,30 @@ const dynamicLoader = (() => {              styleNode = null;          } +        if (type === 'file-content') { +            value = await api.getStylesheetContent(value); +            type = 'code'; +            useWebExtensionApi = false; +        } +          if (useWebExtensionApi) {              // Inject via WebExtension API              if (styleNode !== null && styleNode.parentNode !== null) {                  styleNode.parentNode.removeChild(styleNode);              } -            injectedStylesheets.set(id, null); +            setInjectedStylesheet(id, parentNode, null);              await api.injectStylesheet(type, value);              return null;          }          // Create node in document -        const parentNode = document.head; -        if (parentNode === null) { -            throw new Error('No parent node'); +        let parentNode2 = parentNode; +        if (parentNode2 === null) { +            parentNode2 = document.head; +            if (parentNode2 === null) { +                throw new Error('No parent node'); +            }          }          // Create or reuse node @@ -74,12 +105,12 @@ const dynamicLoader = (() => {          }          // Update parent -        if (styleNode.parentNode !== parentNode) { -            parentNode.appendChild(styleNode); +        if (styleNode.parentNode !== parentNode2) { +            parentNode2.appendChild(styleNode);          }          // Add to map -        injectedStylesheets.set(id, styleNode); +        setInjectedStylesheet(id, parentNode, styleNode);          return styleNode;      } |