ブラウザプラグインの開発中にスクリプトとページコンテンツを注入する通信について


ブラウザプラグインの開発中にスクリプトとページコンテンツを注入する通信について
説明
最近ではsafariブラウザ拡張および360ブラウザ拡張の開発を行っているが,従来のchrome拡張とは異なり,低バージョンのsafari(12)と低バージョンの360ブラウザではwindow.postMessage()を用いて注入スクリプトとページ間の通信が成功していないことが分かった.
通信機能を実現するためには、Chromeと360の公式ドキュメントで推奨されている方法でもあるhackを比較する方法も必要です.
  • はdom要素を作成し、このdom要素のinnerTextをデータを格納媒体
  • とする.
  • その後、このdom要素にカスタムイベント(document.createEvent(‘Event’))
  • を作成します.
  • がデータを渡すときは、まずinnerTextを更新し、キッチンというカスタムイベント(dom.dispatchEvent(event))
  • は、dom.addEventListener()によってイベントを傍受後、後続の動作
  • を行う.
    インプリメンテーション
    挿入スクリプトのwindowとページコンテンツのwindowは、一部のブラウザで一定の分離があり、属性やメソッドを共有することはできないため、両者の相互通信を実現するには、両側にカスタムイベントを登録し、両者の間で所定のイベント名でイベントリスニングを行う必要がある.
    以上の論理に従って、挿入スクリプトとページコンテンツの間の通信プラットフォーム(flowをタイプ宣言し、不要な削除を行う)を構築するためのクラスをカプセル化し、window.postMessageのapiを模倣し、window.postMessageが利用可能であることを優先し、使用できない場合はdomノードを使用して中継する方法を実現した.
    // @flow
    
    /*
     *               
     * */
    
    class CommunicationPlatform {
      communicationPointId: string;
      communicationPoint: HTMLDivElement;
      fromEventName: string;
      toEventName: string;
      communicationEvent: CustomEvent;
      isPostMessageUseful: boolean;
    
      constructor(communicationPointId: string, event: {from: string, to: string}) {
        this.communicationPointId = communicationPointId;
        this.communicationPoint = document.getElementById(communicationPointId);
        this.isPostMessageUseful = this.judgeHasPostMessage();
        // postMessage       
        if (!this.isPostMessageUseful) {
          this.fromEventName = event.from;
          this.toEventName = event.to;
          this.communicationPoint && this.registerExtensionEvent();
        }
      }
    
      registerExtensionEvent() {
        this.communicationEvent = document.createEvent('Event');
        this.communicationEvent.initEvent(this.toEventName, true, true);
      }
    
      addEventListener = (listener: (result: {data: Object}) => void) => {
        if (typeof listener !== 'function') return;
    
        if (this.isPostMessageUseful) {
          return window.addEventListener('message', listener);
        }
    
        const point = this.communicationPoint;
        if (point && typeof point.addEventListener === 'function') {
          point.addEventListener(this.fromEventName, () => {
            const eventData = this.parseEventData(point.innerText);
            //     window.addEventListener('message', ({data, source}) => {})        
            listener({data: eventData || {}, source: window});
          });
        }
      };
    
      postMessage = (data: Object) => {
        if (this.isPostMessageUseful) {
          return window.postMessage(data);
        }
    
        const point = this.communicationPoint;
        if (point && typeof point.dispatchEvent === 'function') {
          point.innerText = this.transferDataToString(data) || '';
          point.dispatchEvent(this.communicationEvent);
        }
      };
    
      //     extension   ,              
      hasPoint = (): boolean => {
        return !!this.communicationPoint;
      };
    
      judgeHasPostMessage() {
        let hasPostMessage = true;
        try {
          window.postMessage({type: 'TEST_POST_MESSAGE_USEFUL'});
        } catch (e) {
          hasPostMessage = false;
        }
    
        return hasPostMessage;
      }
    
      parseEventData(eventData: ?string): Object | null {
        let result = null;
        if (typeof eventData === 'string') {
          try {
            const temp = eventData; //            
            result = JSON.parse(temp);
          } catch (err) {
            console.error(err);
          }
        }
        return result;
      }
    
      transferDataToString(data: Object): string | null {
        let result = null;
        try {
          const temp = JSON.stringify(data); //            
          result = temp;
        } catch (err) {
          console.error(err);
        }
        return result;
      }
    }
    
    export default CommunicationPlatform;
    
    

    ページ内容部分の使用
    
    //         
    const extensionCommunicationPointId = 'xxx_extension_communication_point';
    const extensionCommunicationPoint = document.createElement('div');
    _.set(extensionCommunicationPoint, 'id', extensionCommunicationPointId);
    _.set(extensionCommunicationPoint, 'style.display', 'none'); //     
    document.body.appendChild(extensionCommunicationPoint);
    
    //   window                      
    window.CommunicationPlatform = new CommunicationPlatform(extensionCommunicationPointId, {
      from: 'communicationPageMessage', //         ,      
      to: 'communicationInjectMessage', //         ,               
    });
    
    //           
    window.CommunicationPlatform.postMessage(data);
    
    //            
    window.CommunicationPlatform.addEventListener((event: {source: Object, data: Object}) => {})
    

    注入スクリプト部分の使用
    const InjectCommunicationPlatform = new CommunicationPlatform(
      'xxx_extension_communication_point',
      {from: 'communicationInjectMessage', to: 'communicationPageMessage'},
    );
    
    const hasPoint = InjectCommunicationPlatform &&
      typeof InjectCommunicationPlatform.hasPoint === 'function' &&
      InjectCommunicationPlatform.hasPoint();
    const hasPostMessage = InjectCommunicationPlatform.judgeHasPostMessage();
    
    if (hasPoint || hasPostMessage) {
        InjectCommunicationPlatform.postMessage(data); //        
        InjectCommunicationPlatform.addEventListener((event: {source: Object, data: Object}) => {}) //        
    }