Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

Popup live toggle #439

Merged
merged 17 commits into from
Apr 16, 2020
Merged

Popup live toggle #439

merged 17 commits into from
Apr 16, 2020

Conversation

siikamiika
Copy link
Collaborator

Fixes #431

@siikamiika
Copy link
Collaborator Author

There should probably be a forced settings update when using the root frame popup after using it from an iframe, but I haven't investigated it yet

let popupNestedInitialized = false;

async function popupNestedInitialize(id, depth, parentFrameId, url) {
if (popupNestedInitialized) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think popupNestedInitialized is necessary anymore. Please remove.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's called from float.js, but I can't think of cases when it would actually be called multiple times except maybe reloading the extension, and this check wouldn't help with that. Maybe there should be a check similar to the one in search-frontend.js

        const node = document.querySelector(`script[src='${src}']`);
        if (node !== null) { continue; }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that prepare should only be called once due to the _prepareInvoked guard, so that should handle it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const isIframe = !proxy && (window !== window.parent);

const initEventDispatcher = new EventDispatcher();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of initEventDispatcher can probably be removed by some simplification.

initEventDispatcher is only used by frontend, which is only instantiated once. Add a setPopup method on it for popupChange, and setDisabledOverride can probably be handled in a similar way, or by setting the popup to null.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

44ff4d8

I'd rather not set popup to null because then I'd have to potentially handle nulls all the places where popup is used.

@@ -23,11 +23,13 @@
*/

class TextScanner {
constructor(node, ignoreElements, ignorePoints) {
constructor(node, ignoreElements, ignorePoints, canEnable=null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than adding canEnable, might be better to add it as an argument to setEnabled. setEnabled is only called from TextScanner.setOptions, which is called from the subclasses of TextScanner (i.e. Frontend).

Meaning you can just pass this.popup.depth <= this.options.scanning.popupNestingMaxDepth && !this._disabledOverride here:

this.setOptions(await apiOptionsGet(this.getOptionsContext()));

And true here (or default value):

super.setOptions(options);

This should give better control over when the object is enabled/disabled.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@siikamiika
Copy link
Collaborator Author

Seems like something's up with the CI, none of the tests go through

@toasted-nutbread
Copy link
Collaborator

Seems like something's up with the CI, none of the tests go through

Re-running it manually seems to have worked. ¯\_(ツ)_/¯

@siikamiika
Copy link
Collaborator Author

siikamiika commented Apr 11, 2020

There should probably be a forced settings update when using the root frame popup after using it from an iframe, but I haven't investigated it yet

Actually, the settings should be the same regardless of the iframe proxy because the context is the same there with the current profile conditions. If a refresh is needed, something like this could be done:

diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 20bfc638..df038e24 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -247,13 +247,20 @@ class Frontend extends TextScanner {
     }
 
     _showPopupContent(textSource, type=null, details=null) {
-        this._lastShowPromise = this.popup.showContent(
-            textSource.getRect(),
-            textSource.getWritingMode(),
-            type,
-            details
+        let promise = this._lastShowPromise;
+        if (!this.popup.isProxy() && this.popup.usedByProxy()) {
+            this.popup.setUsedByProxy(false);
+            promise = promise.then(() => this.popup.setOptions(this.options));
+        }
+
+        this._lastShowPromise = promise.then(
+            () => this.popup.showContent(
+                textSource.getRect(),
+                textSource.getWritingMode(),
+                type,
+                details
+            )
         );
-        return this._lastShowPromise;
     }
 
     _updateContentScale() {
diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js
index 958462ff..2aa0e523 100644
--- a/ext/fg/js/popup-proxy-host.js
+++ b/ext/fg/js/popup-proxy-host.js
@@ -158,6 +158,7 @@ class PopupProxyHost {
         if (typeof popup === 'undefined') {
             throw new Error(`Invalid popup ID ${id}`);
         }
+        popup.setUsedByProxy(true);
         return popup;
     }
 
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 42f08afa..4118368d 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -36,6 +36,7 @@ class Popup {
         this._containerSizeContentScale = null;
         this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, '');
         this._messageToken = null;
+        this._usedByProxy = false;
 
         this._container = document.createElement('iframe');
         this._container.className = 'yomichan-float';
@@ -81,6 +82,14 @@ class Popup {
         return false;
     }
 
+    usedByProxy() {
+        return this._usedByProxy;
+    }
+
+    setUsedByProxy(used) {
+        this._usedByProxy = used;
+    }
+
     async setOptions(options) {
         this._options = options;
         this.updateTheme();

@siikamiika
Copy link
Collaborator Author

It's a bit ambiguous what should be done with the url of the optionsContext for the content script. When a content script is injected into an iframe, it could be either the URL of the iframe or the root frame. When iframe popups are set to show in the root frame, it could make sense to abstract iframes away from the user. To make things more complicated, the decision whether to show iframe popups in the root frame could be affected by the current URL.

This was broken when merging #417, but I've addressed the issue in b0d94e8. I can change it to window.location.href if that makes more sense. In that case a new change similar to the diff above is needed to keep the popup options in sync.

if (action === 'rootPopupInformation') {
resolve(params);
}
const {id, depth=0, parentFrameId, url=window.top.location.href, proxy=false, isSearchPage=false} = data;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window.top.location.href can have an exception. I tested manually on https://dic.nicovideo.jp/'s twitter widget and get the following error when trying to read it from its context:

image

Blocked a frame with origin "https://platform.twitter.com" from accessing a cross-origin frame.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what's going to happen with the line below:

    const isIframe = !proxy && (window !== window.parent);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested and that doesn't throw an error

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that there's still an issue with DisplayFloat's optionsContext. Fixing that would be quite complicated, and not necessarily something I want to do in this PR. Should I revert b80fd00 and b0d94e8 and create a new commit making the default value window.location.href?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me.

}

async function getOrCreatePopup(depth) {
const frameOffsetForwarder = new FrameOffsetForwarder();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two FrameOffsetForwarder instances can exist at the same time this way; one from createIframePopupProxy and one from getOrCreatePopup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toasted-nutbread
Copy link
Collaborator

toasted-nutbread commented Apr 12, 2020

Noticed an issue:

  1. Open Chrome.
  2. Open test-document2.html in a new tab.
  3. Open a popup by scanning content in test 1.
  4. Open the options tab.
  5. Change the value of Maximum number of additional popups or Show iframe popups in root frame.
  6. test-document2.html hijacks the tab focus from the options tab.

Appears to have something to do with the iframes, since it doesn't occur on documents without iframes.

@toasted-nutbread
Copy link
Collaborator

Another issue I noticed, which may be a timing issue. Not as critical, but not sure how complex this would be to fix.

  1. Change Maximum number of additional popups to some large value like 10.
  2. Open a document, scan text, open all popups.
  3. Change value of Maximum number of additional popups to 1. All popups will remain open.
  4. Change value of Maximum number of additional popups to 2. Most of the popups will now close.

The options will use the old value of popupNestingMaxDepth.

For testing, on line 290 I added:

console.log('_canEnable', this.options, this.popup.depth, '<=', this.options !== null ? this.options.scanning.popupNestingMaxDepth : null, this._disabledOverride);

Logged this:

// Initially opening all popups
frontend.js:290 _canEnable null 0 <= null false
frontend.js:290 _canEnable null 1 <= null false
frontend.js:290 _canEnable null 2 <= null false
frontend.js:290 _canEnable null 3 <= null false
frontend.js:290 _canEnable null 4 <= null false
frontend.js:290 _canEnable null 5 <= null false
frontend.js:290 _canEnable null 6 <= null false
frontend.js:290 _canEnable null 7 <= null false
frontend.js:290 _canEnable null 8 <= null false
frontend.js:290 _canEnable null 9 <= null false
frontend.js:290 _canEnable null 10 <= null false
// Changed value of popupNestingMaxDepth to 1
frontend.js:290 _canEnable {…} 0 <= 10 false
frontend.js:290 _canEnable {…} 1 <= 10 false
frontend.js:290 _canEnable {…} 7 <= 10 false
frontend.js:290 _canEnable {…} 6 <= 10 false
frontend.js:290 _canEnable {…} 4 <= 10 false
frontend.js:290 _canEnable {…} 2 <= 10 false
frontend.js:290 _canEnable {…} 5 <= 10 false
frontend.js:290 _canEnable {…} 3 <= 10 false
frontend.js:290 _canEnable {…} 10 <= 10 false
frontend.js:290 _canEnable {…} 8 <= 10 false
frontend.js:290 _canEnable {…} 9 <= 10 false
// Changed value of popupNestingMaxDepth to 2
frontend.js:290 _canEnable {…} 0 <= 1 false
frontend.js:290 _canEnable {…} 1 <= 1 false
frontend.js:290 _canEnable {…} 7 <= 1 false
frontend.js:290 _canEnable {…} 6 <= 1 false
frontend.js:290 _canEnable {…} 4 <= 1 false
frontend.js:290 _canEnable {…} 2 <= 1 false
frontend.js:290 _canEnable {…} 5 <= 1 false
frontend.js:290 _canEnable {…} 3 <= 1 false
frontend.js:290 _canEnable {…} 10 <= 1 false
frontend.js:290 _canEnable {…} 8 <= 1 false
frontend.js:290 _canEnable {…} 9 <= 1 false

@siikamiika
Copy link
Collaborator Author

Noticed an issue:

1. Open Chrome.

2. Open [test-document2.html](https://github.com/FooSoft/yomichan/blob/master/test/data/html/test-document2.html) in a new tab.

3. Open a popup by scanning content in test 1.

4. Open the options tab.

5. Change the value of **Maximum number of additional popups** or **Show iframe popups in root frame**.

6. test-document2.html hijacks the tab focus from the options tab.

Appears to have something to do with the iframes, since it doesn't occur on documents without iframes.

This happens when https://github.com/FooSoft/yomichan/pull/439/files#diff-c0a35793e5728d9d3e4b1301d23a4c3bR113-R115 is run because Frontend.setPopup calls this.onSearchClear(true);. I can change it to false, but it could lead to hiding a popup so that it's still focused. I guess forcefully focusing the tab is worse. I remember we had a similar issue in the past, but it was fixed somehow.

@siikamiika
Copy link
Collaborator Author

Another issue I noticed, which may be a timing issue. Not as critical, but not sure how complex this would be to fix.

1. Change **Maximum number of additional popups** to some large value like 10.

2. Open a document, scan text, open all popups.

3. Change value of **Maximum number of additional popups** to 1. All popups will remain open.

4. Change value of **Maximum number of additional popups**  to 2. Most of the popups will now close.

The options will use the old value of popupNestingMaxDepth.

For testing, on line 290 I added:

console.log('_canEnable', this.options, this.popup.depth, '<=', this.options !== null ? this.options.scanning.popupNestingMaxDepth : null, this._disabledOverride);

Logged this:

// Initially opening all popups
frontend.js:290 _canEnable null 0 <= null false
frontend.js:290 _canEnable null 1 <= null false
frontend.js:290 _canEnable null 2 <= null false
frontend.js:290 _canEnable null 3 <= null false
frontend.js:290 _canEnable null 4 <= null false
frontend.js:290 _canEnable null 5 <= null false
frontend.js:290 _canEnable null 6 <= null false
frontend.js:290 _canEnable null 7 <= null false
frontend.js:290 _canEnable null 8 <= null false
frontend.js:290 _canEnable null 9 <= null false
frontend.js:290 _canEnable null 10 <= null false
// Changed value of popupNestingMaxDepth to 1
frontend.js:290 _canEnable {…} 0 <= 10 false
frontend.js:290 _canEnable {…} 1 <= 10 false
frontend.js:290 _canEnable {…} 7 <= 10 false
frontend.js:290 _canEnable {…} 6 <= 10 false
frontend.js:290 _canEnable {…} 4 <= 10 false
frontend.js:290 _canEnable {…} 2 <= 10 false
frontend.js:290 _canEnable {…} 5 <= 10 false
frontend.js:290 _canEnable {…} 3 <= 10 false
frontend.js:290 _canEnable {…} 10 <= 10 false
frontend.js:290 _canEnable {…} 8 <= 10 false
frontend.js:290 _canEnable {…} 9 <= 10 false
// Changed value of popupNestingMaxDepth to 2
frontend.js:290 _canEnable {…} 0 <= 1 false
frontend.js:290 _canEnable {…} 1 <= 1 false
frontend.js:290 _canEnable {…} 7 <= 1 false
frontend.js:290 _canEnable {…} 6 <= 1 false
frontend.js:290 _canEnable {…} 4 <= 1 false
frontend.js:290 _canEnable {…} 2 <= 1 false
frontend.js:290 _canEnable {…} 5 <= 1 false
frontend.js:290 _canEnable {…} 3 <= 1 false
frontend.js:290 _canEnable {…} 10 <= 1 false
frontend.js:290 _canEnable {…} 8 <= 1 false
frontend.js:290 _canEnable {…} 9 <= 1 false

Caused by calling _canEnable before it got the newest options. Fixed in b6f7f8c

@siikamiika siikamiika merged commit e6053ee into FooSoft:master Apr 16, 2020
@toasted-nutbread toasted-nutbread mentioned this pull request Apr 18, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Apply options related to popup creation without reloading the page
2 participants