diff options
Diffstat (limited to 'ext/fg/js/float.js')
| -rw-r--r-- | ext/fg/js/float.js | 135 | 
1 files changed, 72 insertions, 63 deletions
diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 77e8edd8..845bf7f6 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -18,7 +18,7 @@  /* global   * Display   * apiBroadcastTab - * apiGetMessageToken + * apiSendMessageToFrame   * popupNestedInitialize   */ @@ -27,12 +27,11 @@ class DisplayFloat extends Display {          super(document.querySelector('#spinner'), document.querySelector('#definitions'));          this.autoPlayAudioTimer = null; -        this._popupId = null; +        this._secret = yomichan.generateId(16); +        this._token = null;          this._orphaned = false; -        this._prepareInvoked = false; -        this._messageToken = null; -        this._messageTokenPromise = null; +        this._initializedNestedPopups = false;          this._onKeyDownHandlers = new Map([              ['C', (e) => { @@ -46,38 +45,23 @@ class DisplayFloat extends Display {          ]);          this._windowMessageHandlers = new Map([ -            ['setOptionsContext', ({optionsContext}) => this.setOptionsContext(optionsContext)], -            ['setContent', ({type, details}) => this.setContent(type, details)], -            ['clearAutoPlayTimer', () => this.clearAutoPlayTimer()], -            ['setCustomCss', ({css}) => this.setCustomCss(css)], -            ['prepare', ({popupInfo, optionsContext, childrenSupported, scale}) => this.prepare(popupInfo, optionsContext, childrenSupported, scale)], -            ['setContentScale', ({scale}) => this.setContentScale(scale)] +            ['initialize', {handler: this._initialize.bind(this), authenticate: false}], +            ['configure', {handler: this._configure.bind(this)}], +            ['setOptionsContext', {handler: ({optionsContext}) => this.setOptionsContext(optionsContext)}], +            ['setContent', {handler: ({type, details}) => this.setContent(type, details)}], +            ['clearAutoPlayTimer', {handler: () => this.clearAutoPlayTimer()}], +            ['setCustomCss', {handler: ({css}) => this.setCustomCss(css)}], +            ['setContentScale', {handler: ({scale}) => this.setContentScale(scale)}]          ]); - -        yomichan.on('orphaned', this.onOrphaned.bind(this)); -        window.addEventListener('message', this.onMessage.bind(this), false);      } -    async prepare(popupInfo, optionsContext, childrenSupported, scale) { -        if (this._prepareInvoked) { return; } -        this._prepareInvoked = true; - -        const {id, parentFrameId} = popupInfo; -        this._popupId = id; - -        this.optionsContext = optionsContext; - +    async prepare() {          await super.prepare(); -        await this.updateOptions(); - -        if (childrenSupported) { -            const {depth, url} = optionsContext; -            popupNestedInitialize(id, depth, parentFrameId, url); -        } -        this.setContentScale(scale); +        yomichan.on('orphaned', this.onOrphaned.bind(this)); +        window.addEventListener('message', this.onMessage.bind(this), false); -        apiBroadcastTab('popupPrepareCompleted', {targetPopupId: this._popupId}); +        apiBroadcastTab('popupPrepared', {secret: this._secret});      }      onError(error) { @@ -102,46 +86,30 @@ class DisplayFloat extends Display {      onMessage(e) {          const data = e.data; -        if (typeof data !== 'object' || data === null) { return; } // Invalid data - -        const token = data.token; -        if (typeof token !== 'string') { return; } // Invalid data - -        if (this._messageToken === null) { -            // Async -            this.getMessageToken() -                .then( -                    () => { this.handleAction(token, data); }, -                    () => {} -                ); -        } else { -            // Sync -            this.handleAction(token, data); +        if (typeof data !== 'object' || data === null) { +            this._logMessageError(e, 'Invalid data'); +            return;          } -    } -    async getMessageToken() { -        // this._messageTokenPromise is used to ensure that only one call to apiGetMessageToken is made. -        if (this._messageTokenPromise === null) { -            this._messageTokenPromise = apiGetMessageToken(); -        } -        const messageToken = await this._messageTokenPromise; -        if (this._messageToken === null) { -            this._messageToken = messageToken; +        const action = data.action; +        if (typeof action !== 'string') { +            this._logMessageError(e, 'Invalid data'); +            return;          } -        this._messageTokenPromise = null; -    } -    handleAction(token, {action, params}) { -        if (token !== this._messageToken) { -            // Invalid token +        const handlerInfo = this._windowMessageHandlers.get(action); +        if (typeof handlerInfo === 'undefined') { +            this._logMessageError(e, `Invalid action: ${JSON.stringify(action)}`);              return;          } -        const handler = this._windowMessageHandlers.get(action); -        if (typeof handler !== 'function') { return; } +        if (handlerInfo.authenticate !== false && !this._isMessageAuthenticated(data)) { +            this._logMessageError(e, 'Invalid authentication'); +            return; +        } -        handler(params); +        const handler = handlerInfo.handler; +        handler(data.params);      }      autoPlayAudio() { @@ -193,4 +161,45 @@ class DisplayFloat extends Display {              return '';          }      } + +    _logMessageError(event, type) { +        yomichan.logWarning(new Error(`Popup received invalid message from origin ${JSON.stringify(event.origin)}: ${type}`)); +    } + +    _initialize(params) { +        if (this._token !== null) { return; } // Already initialized +        if (!isObject(params)) { return; } // Invalid data + +        const secret = params.secret; +        if (secret !== this._secret) { return; } // Invalid authentication + +        const {token, frameId} = params; +        this._token = token; + +        apiSendMessageToFrame(frameId, 'popupInitialized', {secret, token}); +    } + +    async _configure({messageId, frameId, popupId, optionsContext, childrenSupported, scale}) { +        this.optionsContext = optionsContext; + +        await this.updateOptions(); + +        if (childrenSupported && !this._initializedNestedPopups) { +            const {depth, url} = optionsContext; +            popupNestedInitialize(popupId, depth, frameId, url); +            this._initializedNestedPopups = true; +        } + +        this.setContentScale(scale); + +        apiSendMessageToFrame(frameId, 'popupConfigured', {messageId}); +    } + +    _isMessageAuthenticated(message) { +        return ( +            this._token !== null && +            this._token === message.token && +            this._secret === message.secret +        ); +    }  }  |