在瀏覽某些網站時,總會被不斷出現的廣告打擾,或是那些時不時出現在畫面中間要你登入的介面。如果是在 PC 的瀏覽器,使用者一般會安裝 extension 或是 plugin 來隱藏這些資訊。這些 extension 和 plugin 定義了一套機制,可以在特定網頁中注入程式碼,做適度的 html 調整,讓使用者不想看到的元件可以隱藏或刪除。
那麼,如果想在 EinkBro 這麼小的 APP 中,也實作一套類似的機制,要怎麼進行呢?以下將說明目前在 EinkBro 中有針對特定網站做了一些微整型。
建立一個網頁內容處理器。在網頁載入完成後,如果網址屬於某個 domain 時,便注入對應的 Javascript,對 html 內容動手腳。
11行的 postProcess()
會先檢查 WebView
的 url 是不是有符合我們想要調整的網址,如果是的話,就會在 18 行呼叫 ninjaWebView.evaluatejavascript()
,帶入 script,調整畫面。
這邊以 facebook 網站為例:有兩類文章我會想要把它隱藏起來,一是廣告文章;二是推薦的文章。
首先,我們要開啟 PC 版的 Google Chrome,連結到 facebook 網站,並且打開 Developer Tools。在畫面稍微捲動一下,可以看到寫有 Sponsored 的 post,利用 Developer Tools 定位到這則 post,可以看到右邊它是個 article 開頭的 html element。這時,就要利用一點 html 的技巧,從一堆 article element 中的資訊找到足以辨識它就是我們想要隱藏的 post,然後把它寫成 Javascript 撈出來,並且將其隱藏。
下面就是寫好,針對 mobile 版的 facebook 隱藏廣告 post 的 Javascript 片段。裡頭主要做了兩件事:一是先將目前已經顯示出來的網頁內容,隱藏廣告 post 和推薦 post。然而,大家有用過 facebook mobile 版就會發現,當你滑動畫面幾次後,它會再動態載入更多的內容。所以,必須要在新內容被載入時,再做一次同樣的操作。
對於 Javascript 的實作這裡就不做太多的說明,相信熟悉 Javascript 實作的人,下面的內容都蠻好懂的;對於不熟的人來說,這也不是三言兩語就可以解釋清楚的。建議有興趣的人可以多去看看 Chrome Extensions 的實作,就可以更了解這類的應用。
private const val facebookHideSponsoredPostsJs = """
javascript:(function() {
var posts = [].filter.call(document.getElementsByTagName('article'), el => (
(el.attributes['data-store'] != null && el.attributes['data-store'].value.indexOf('is_sponsored.1') >= 0) ||
(el.attributes['data-ft'] != null && el.attributes['data-ft'].value.indexOf('is_sponsored') >= 0) ||
(el.attributes['data-xt-vimp'] != null && el.attributes['data-xt-vimp'].value.indexOf('is_sponsored') >= 0) ||
(el.getElementsByTagName('header')[0] != null && el.getElementsByTagName('header')[0].innerText == 'Suggested for you')));
while(posts.length > 0) { posts.pop().style.display = "none"; }
var ads = Array.from(document.getElementsByClassName("bg-s3")).filter(e => e.innerText.indexOf("Sponsored") != -1);
ads.forEach(el => {el.style.display="none"; el.nextSibling.style.display="none";el.nextSibling.nextSibling.style.display="none"});
ads.forEach(el => {el.nextSibling.nextSibling.nextSibling.style.display="none"});
ads.forEach(el => {el.nextSibling.nextSibling.nextSibling.nextSibling.style.display="none"});
var qcleanObserver = new window.MutationObserver(function(mutation, observer){
var posts = [].filter.call(document.getElementsByTagName('article'), el => (
(el.attributes['data-store'] != null && el.attributes['data-store'].value.indexOf('is_sponsored.1') >= 0) ||
(el.attributes['data-ft'] != null && el.attributes['data-ft'].value.indexOf('is_sponsored') >= 0) ||
(el.attributes['data-xt-vimp'] != null && el.attributes['data-xt-vimp'].value.indexOf('is_sponsored') >= 0) ||
(el.getElementsByTagName('header')[0] != null && el.getElementsByTagName('header')[0].innerText == 'Suggested for you')));
while(posts.length > 0) { posts.pop().style.display = "none"; }
var ads = Array.from(document.getElementsByClassName("bg-s3")).filter(e => e.innerText.indexOf("Sponsored") != -1);
ads.forEach(el => {el.style.display="none"; el.nextSibling.style.display="none";el.nextSibling.nextSibling.style.display="none"});
ads.forEach(el => {el.nextSibling.nextSibling.nextSibling.style.display="none"});
ads.forEach(el => {el.nextSibling.nextSibling.nextSibling.nextSibling.style.display="none"});
});
qcleanObserver.observe(document, { subtree: true, childList: true });
})()
"""
網頁內容的後處理,要在網頁載入後才能進行,所以這件事必須在 NinjaWebViewClient::onPageFinished()
中進行。這麼一來,如果一切處理得當,就可以得到眼不見為淨的瀏覽體驗。
目前的作法很沒有彈性,一旦該網站的介面做了些許的調整,可能這作法就失效了,要再找到新的方式操作。而且現在是寫死在 EinkBro APP 程式碼中。未來可以改進的方向是:建立管理 extension 的介面,讓使用者可以自行建立想要調整的 domain 或 url,再自行填入可行的 javascript 程式碼。