# 所見即所得 - 專案功能盤點 ###### tags: `WYSIWYG` <style> .note{ color: Teal } .note-tag{ color: CadetBlue; } .level1{ color: Maroon; } .level2{ color: IndianRed; } .level3{ color: LightCoral; } .helpfunc{ color: green; } .idontknow{ color: Crimson; font-weight: bolder; } </style> 下圖為目前的檔案架構 ![](https://i.imgur.com/mar9gid.png) ![](https://i.imgur.com/8RgYoup.png) ## <span class="level1">background</span> 邏輯處理中心,負責 Chrome Extension 大部分的事件處理 ### <span class="level2">background.js</span> #### <span class="level3">chrome.runtime.onInstalled</span> 會先去判斷被觸發的原因是哪種 * 第一次安裝 先確認一下使用者這邊有沒有先前儲存過的資料, 如果有的話就使用它,沒有的話就初始化資料為空值 ![](https://i.imgur.com/B1PEle8.png) * 更新 目前並沒做任何處理 #### <span class="level3">chrome.tabs.onUpdated</span> 判斷 tab 的變動原因 * 如果狀態是 loading ,它就會去 storage 去抓 validList 的資料 (<span class="idontknow">目前還不確定 validList 在幹嘛</span>) (感覺是在對 URL, 如果有對到就讓它執行content.script) ![](https://i.imgur.com/jBHudot.png) #### <span class="level3">chrome.runtime.onMessage</span> 會先看 send 過來的 request,它的子結點 action 是不是 array,是的話就繼續 再來看 action 中有沒有 "save" ,有的話就用 chrome.storage 來儲存進去 ![](https://i.imgur.com/PMhjIyf.png) #### <span class="level3">chrome.commands.onCommand</span> 設定來處理 ChromeExtension 的快捷鍵處理事項 目前有兩種快捷鍵功能 * 開啟關閉 Edit Mode ![](https://i.imgur.com/xVglG6H.png) * 開啟 devPannel 小視窗 ![](https://i.imgur.com/e3wj16A.png) ### <span class="level2">backgroundRequestManage.js</span> #### <span class="level3">CloseLanguageToolTab</span> 一個關閉 chrome tab 的 function, TABID_TO_CLOSR 已經在 [createLanguageToolTab](#createLanguageToolTab) 中被定義 ![](https://i.imgur.com/r4y4pft.png) #### <span class="level3" id="GetUserGoogleInfoByToken">GetUserGoogleInfoByToken</span> 它是一個非同步的 code,它依序做以下的操作 1. fetch API 到這 google identitytoolkit 2. 可以獲得3筆使用者資料(displayName, email, photoURL), 並使用 chrome.storage 來儲存 3. 使用 chrome.runTime.sendMessage 來傳出一個 action : `getUserInfoDone` #### <span class="level3">chrome.storage.sync.get</span> 首先先去取得 chrome storage 中名為 `language` 的值, (<span class="idontknow">這個值好像跟 popup 的 script 有關,還不確定在幹嘛</span>) 並設定為變數 `MAINPAGE_LANGUAGE` 的值 ![](https://i.imgur.com/3e5HYp2.png) #### <span class="level3">chrome.runtime.onMessage</span> 這邊的 runtime.onMessage 負責監聽兩個 action,分別是 * <span id="createLanguageToolTab">createLanguageToolTab</span> 看註解是說: 當偵測到 devPannel 的 token 是 invalid 的時候, 會開一個新的 tab 去開 Language Tool 的頁面, 並且將新的 tabid 記錄於變數 `TABID_TO_CLOSE` 中 ![](https://i.imgur.com/HdYlTuX.png) * changeLocalLanguage 看註解是說: 當 user 改變 popup 裡的 language 值時, 會更新變數 `MAINPAGE_LANGUAGE` 的語言值 ![](https://i.imgur.com/X7bDJ9K.png) #### <span class="level3">chrome.webRequest.onBeforeRequest</span> 在 Request 發出後會去判斷以下資訊 * Method 是不是 Post * initiator 是不是來自 Language Tool * requestbody 看有沒有資訊 * requestbody.raw 看有沒有資訊 如果上述判斷皆可成立,就可已從 request body 中獲得 ID_Token, 並將其存放於 [GetUserGoogleInfoByToken](#GetUserGoogleInfoByToken) 這個 POST request 的 body 中 以獲得 user 的 display name, photo url 及 email 的資訊 ![](https://i.imgur.com/MwOFkle.png) #### <span class="level3">chrome.webRequest.onBeforeSendHeaders</span> <span class="idontknow">好像跟設改變語系有關,但還不知道他在幹麻</span> ![](https://i.imgur.com/VuUGolZ.png) ### <span class="level2">backgroundsendKeysURL.js</span> #### <span class="level3" id="ReportKeyUrlMap">ReportKeyUrlMap</span> 使用 chrome.storage.get 取得 email 及 token 接著會去打以下這個 API `http://${DOMAIN}/api/v3/modules/${module_key[0]}/translations/${module_key[1]}/paths/add` Method 是 PATCH, header 的資訊也有另外處理 <span class="idontknow">目前還不知道在幹麻 QQ</span> #### <span class="level3">chrome.runtime.onMessage</span> 這邊的 runtime.onMessage 負責監聽一個 action, * sendKeysUrl 把它的 request body 裡的 module_key 去 run [ReportKeyUrlMap](#ReportKeyUrlMap) 這個 function ![](https://i.imgur.com/aTBog20.png) ## <span class="level1">popup</span> ### <span class="level2">popup.html</span> 主要有以下部份 ![](https://i.imgur.com/2TyUfwa.png) * **Google 帳號資訊** * 比較要注意的是它在未登入時的登入按鈕,它綁定的事件是 getLoginData, 會使用 chrome.runtime 發送一個 message 來通知 background.js, 它所綁的事件為 `createLanguageToolTab` ![](https://i.imgur.com/iuK2IhD.png) ![](https://i.imgur.com/vCrSEDu.png) * **加入網址** ![](https://i.imgur.com/T3ohte1.png) * addSiteBtnClick 將目前的 domain 加入至 `this.validSite.validList` 這個變數當中 然後使用 chrome.runtime.sendMessage 去發出 `save` 任務給 background.js 最後再針對當前的 tab 做 reload 的動作 ![](https://i.imgur.com/gzxvX9e.png) * deleteSiteBtnClick 將目前的 domain 從`this.validSite.validList` 這個變數當中刪除 然後使用 chrome.runtime.sendMessage 去發出 `save` 任務給 background.js 最後再針對當前的 tab 做 reload 的動作 ![](https://i.imgur.com/3CNTnVf.png) * **切換閱覽及編輯模式** 主要重點是 `changeMode` 這個 function, 它會在 popup.js 使用 chrome.tabs.sendMessage 發送 `changeMode` 這個 action 給 content.js ![](https://i.imgur.com/9igr0EX.png) ![](https://i.imgur.com/gtYCeUk.png) * **切換頁面語系** 主要做的有三個動作 * 發送 `save` 這個 action, 所帶 data 為 `{ language: this.language.selected }` * 發送 `changeLocalLanguage` 這個 action, 所帶 data 為 `{ language: this.language.selected }` * 更新頁面的 URL 的 query Selector ![](https://i.imgur.com/IGwyivl.png) ![](https://i.imgur.com/iYCQM1V.png) ### <span class="level2">popup.js</span> 所使用的前端框架為 Vue, 以下是他的架構 #### <span class="level3">chrome.runtime.onMessage</span> 一開始會先取得所需要的資訊 使用 chrome.storage.sync.get 來取得以下資訊 * editorMode * email * token * language * validList * displayName * photoUrl ![](https://i.imgur.com/fgroyBz.png) **Data 架構**: ```json { "profile":{ "name":"", "email":"", "img":"", "valid":false }, "controller":{ "token":"", "editorMode":true }, "validSite":{ "domain":"", "validList":[ "https://sms.qa.91dev.tw", "https://sms.qa8.91dev.tw", "https://getbootstrap.com" ] }, "language":{ "selected":"zh-TW", "options":[ { "value":"zh-TW", "text":"中文" }, { "value":"zh-HK", "text":"香港中文" }, { "value":"en-US", "text":"英文" }, { "value":"ms-MY", "text":"馬來文" }, { "value":"kok-IN", "text":"火星文" } ] } } ``` **mounted (概念類似 React 的 componentDidMount)** 首先先使用 chrome.storage.sync.get 來取得以下資訊 * token * language * validList * editorMode 再來使用 `chrome.tab.query` 來取得 Domain Name 的樣子 (但它的 regular expression 好像怪怪的) 並將 Domain Name 存放於 this.validSite.domain 這個變數來做使用 最後再跑 `loadIframe` 這個 function #### <span class="level3" id="loadIframe">loadIframe</span> 先使用 querySelector 找到 class 名為 `language-tool` 的 element, 並在其注入 tag 為 iframe 的 element , 並且在該 element 加以下特性, * frameborder: 0 * style.height: 0 * <span class="idontknow">使用 setTimeout,加入 src 這個屬性</span> ![](https://i.imgur.com/eXwfvaj.png) ## <span class="level1">content</span> ### <span class="level2">監聽 DOM 結構的變動</span> #### **<span class="level3">MutationObserver</span>** MutationObserver 它是一個 WebAPI,設計用來監控 DOM tree 結構的變化 建構式: ```javascript new MutationObserver( function callback ); ``` 一但有變動,裡面的 callback 就會被呼叫 ![](https://i.imgur.com/Y1AWeIv.png) #### **<span class="level3">setObserver</span>** 有一個全域變數叫做 updateCounter 來計時用,這邊賦予它值為 5, 接下來用 setInterval 來計時,並以 `checkAfterUpdated` 這個變數來存取它, 每 100 ms 為一個循環,一共跑 5 次 時間一到, 先將 mutationObserver 關閉一下, 並清除這個 setInterval, 再重新將 `checkAfterUpdated` 賦值為 null, 接下來就是把 iFrame 放到畫面上,並以 `module_key_list` 這個變數來存這資料, 再用 chrome 的 sendmessage 發送 `sendKeysUrl` 這個 action 並帶著 module_key 這個 Data 最後重新開啟 mutationObserver.observe 去觀看 iFrame 的變化 ***補:*** [.observe](https://developer.mozilla.org/zh-TW/docs/Web/API/MutationObserver#observe()) 是 mutationObserver 的一個 Instance methods, 它的結構如下, Node 為想要監控的節點,MutationObserverInit 則是一些監控的設定, 可以決定說什麼樣的事件才會回傳,是否監控子結構等 ```javascript void observe( Node target, MutationObserverInit options ); ``` ![](https://i.imgur.com/EnKPSff.png) ### <span class="level2">初始化 iframe</span> #### **<span class="level3">initIframe()</span>** 這邊就是在製作一個 iframe 的 element, * iframe 的 src Attribute 是看當前所在的 URL, 會先判斷目前的 URL 是不是具有 query string, 如果有 query string 有 lang 的話,就把當前的 lang 替換成 `kok-IN` (火星文), 沒有的話就是保持當前 URL * 預設是不希望將 iframe 呈現在畫面上, 所以把他的 height 調成 0 * 當 iFrame 是 onload 的狀態,就執行以下處理 * <span class="idontknow">setObserver()</span> * <span class="idontknow">mutationObserver()</span> * 一切處理完之後,就把 iFrame 這個 element 放在 body 的最底端 ![](https://i.imgur.com/GuSWsrP.png) #### **<span class="level3">parseIframeArea()</span>** 先準備好 3 變數 * base : iFrame 的 document body * all : iFrame 的 innerHTML * reg : 三種 regular expression * `"\s*\[[^\[\]\(\{\"\']+\]\s*"` : "[key]" * `\s*\[[^\[\]\(\{\"\']+\]\s*` : [key] * `\s*\[\[[^\[\]\(\{\"\']+\]\]\s*` : [[key]] 接下來是製作 virtualDOM 去尋找有對應到的 key 值,多語系 kok-IN 的值為 [key] 的形式 簡單來說這邊 漏漏登 的 code 就是在有多語系的地方做標記 ![](https://i.imgur.com/zJ2N4hD.png) ### <span class="level2">針對 body 監聽的事件</span> #### **<span class="helpfunc">validTarget()</span>** 判斷目前的 target element 是不是具有 `have-key` 這個 class ![](https://i.imgur.com/a255BZu.png) #### **<span class="level3">mouseover</span>** 為 `pointElement` 這個變數指定為 `e.target` 先判斷是不是在編輯模式,是用 selected 這個變數做確認, 滑鼠移入時,加上 `is-selected` 這個 class ![](https://i.imgur.com/uT0SlZ5.png) #### **<span class="level3">mouseout</span>** 先判斷是不是在編輯模式,是用 selected 這個變數做確認, 滑鼠移出時,移除 `is-selected` 這個 class ![](https://i.imgur.com/3lBhv9j.png) #### **<span class="helpfunc">checkFakeButton()</span>** 判斷這個 element 是不是 button,有三種可能形式 `button` , `submit`, `navbar-toggle` 如果是其中之一,則回傳 true ![](https://i.imgur.com/RPRcP74.png) #### **<span class="helpfunc">getDomIndexString()</span>** 取得它在整個 DOM 結構中所在的位置 <span class="idontknow">雖然覺得有點怪</span> 回傳的格式為 `body>${domindex}`, 似乎是一個奇妙的遞迴~ ![](https://i.imgur.com/y8JAhOl.png) #### **<span class="level3">click</span>** * 先判斷是不是在編輯模式,是用 selected 這個變數做確認 * 判斷是不是 button, submit 或 navbar-toggle 其中一種, 如果不是的話,對它使用 `e.preventDefault()` * 判斷是不是 valid target : class 是不是有 `have-key` 這屬性 是的話,先看他是不是 a 連結,是的話對它使用 `e.preventDefault()` 再來將 e.target 存放在 `editorElement` 這個全域變數中 接著為 `updateDevPage` 這個 chrome.sendmessagfe 的動作準備 這是要傳送到 devPannel 的 content.js ### <span class="level2">切換編輯及閱覽模式</span> **chrome.runtime.onMessage** 這支監控的任務為 `changeMode`, #### <span class="level3">switchMode</span> #### <span class="level3">showMode</span> ## <span class="level1">devPanel</span> ## <span class="note">Language Tool API 筆記</span> ### 取得單一 key 值多語系資料 METHODS : `GET` URL : `http://translation.qa.91dev.tw/api/v3/modules/[module_name]?key=[module_key]` ## <span class="note">Chrome extension API 筆記</span> ### <span class="note-tag">chrome.runtime</span> 它可以取得我們 background page 的資訊 (manifest.json) 也可以用來監控我們 extension life cycle 中的事件 及 取得我們 URL 的資訊 * [**onInstalled**](https://developer.chrome.com/extensions/runtime#event-onInstalled) 只有在 Extension **第一次安裝**, **Extension 更新版本** 或 **Chrome 更新** 這 3 種狀況才會觸發 * [**onMessage**](https://developer.chrome.com/extensions/runtime#event-onMessage) 當接受到 Message 時會被觸發,有兩個管道可以接受到 Message * From other extension (PopUp or devPannel) : `runtime.sendMessage` * From content script : `tabs.sendMessage` ### <span class="note-tag">chrome.storage</span> 它是設計用來處理 extension 的 storage 需求, 可以針對 user data 做 取得, 儲存 及 追蹤變化 等操作 類似於 localstorage 的存在, 但它的儲存格式為 json,不是用 string * [**sync**](https://developer.chrome.com/apps/storage#property-sync) 使用 storage.sync 會將 user data 儲存到使用者所登入的 chrome browser 裡 如果使用者現在的 Chrome 是離線狀態,會先將資料儲存在 local 端,一旦恢復連線就會同步 ### <span class="note-tag">chrome.tabs</span> 它是來處理 chrome browser 裡的 tab 操作, 可以透過這個 API 來 新增修改 瀏覽器中的 tab * [**onUpdated**](https://developer.chrome.com/apps/storage#property-sync) 當我們的 tab 有更動的時候就會被觸發,它有三個變數可以做使用 * tabId : tab ID (int) * changeInfo : 變動的資訊 (它是一個 ogject) * tab : 詳細的 tab 資訊 (它是一個 object) ### <span class="note-tag">chrome.commands</span> 它可以幫助我們使用鍵盤組合鍵來做相對應的動作, 而組合鍵的設定都在 manifest.json 中處理 ### <span class="note-tag">chrome.webRequest</span> 用來監控發出的 request ,也可以對它攔截修改 下方是整個 request 的生命週期, 可以依照對應的 API 在想攔截的點做操作 ![img](https://developer.chrome.com/static/images/webrequestapi.png) onBeforeRequest onBeforeSendHeaders