From 1c557c97a5a4a7185004e68cc9eecfd3d727257e Mon Sep 17 00:00:00 2001 From: DanSnow Date: Sat, 12 Oct 2019 00:56:00 +0800 Subject: [PATCH 1/2] feat: Support startpage.com --- src/manifest.json.js | 3 ++ src/ts/content.ts | 73 +++++++++++++++++++++++++++++++++++--------- src/ts/inspector.ts | 22 ++++++++++++- 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/src/manifest.json.js b/src/manifest.json.js index e24963c2c..6b45b0886 100644 --- a/src/manifest.json.js +++ b/src/manifest.json.js @@ -248,6 +248,9 @@ const manifest = { 'https://www.google.co.zm/search?*', 'https://www.google.co.zw/search?*', 'https://www.google.cat/search?*', + 'https://www.startpage.com/do/search*', + 'https://www.startpage.com/rvd/search*', + 'https://www.startpage.com/sp/search*', ], }, ], diff --git a/src/ts/content.ts b/src/ts/content.ts index 9734a6bd7..54d2514a8 100644 --- a/src/ts/content.ts +++ b/src/ts/content.ts @@ -14,6 +14,60 @@ function $(id: string): HTMLElement | null { return document.getElementById(id) as HTMLElement | null; } +const CONTROL_INFO = [ + { + site: /^www.google/, + insert: [ + (control: HTMLElement): boolean => { + const resultStats = $('resultStats') as HTMLDivElement | null; + if (!resultStats) { + return false; + } + resultStats.appendChild(control); + return true; + }, + (control: HTMLElement): boolean => { + const abCtls = $('ab_ctls') as HTMLOListElement | null; + if (!abCtls) { + return false; + } + const li = document.createElement('li'); + li.className = 'ab_ctl'; + li.id = 'ubImageSearchControl'; + li.appendChild(control); + abCtls.appendChild(li); + return true; + }, + ], + }, + { + site: /^www.startpage/, + insert: [ + (control: HTMLElement): boolean => { + const searchFilter = document.querySelector('.search-filters-toolbar__container'); + if (!searchFilter) { + return false; + } + const div = document.createElement('div'); + div.className = 'search-filters-toolbar__advanced'; + div.appendChild(control); + searchFilter.appendChild(div); + return true; + }, + (control: HTMLElement): boolean => { + const imageFilter = document.querySelector('.images-filters-toolbar__container'); + if (!imageFilter) { + return false; + } + const div = document.createElement('div'); + div.appendChild(control); + imageFilter.appendChild(div); + return true; + }, + ], + }, +]; + class Main { blacklists: BlacklistAggregation | null = null; blacklistUpdate: BlacklistUpdate | null = null; @@ -210,22 +264,13 @@ class Main { control.appendChild(showButton); control.appendChild(hideButton); - const resultStats = $('resultStats') as HTMLDivElement | null; - if (resultStats) { - resultStats.appendChild(control); - } else { - const abCtls = $('ab_ctls') as HTMLOListElement | null; - if (abCtls) { - const li = document.createElement('li'); - li.className = 'ab_ctl'; - li.id = 'ubImageSearchControl'; - li.appendChild(control); - abCtls.appendChild(li); - } else { - return; + for (const info of CONTROL_INFO) { + if (info.site.exec(window.location.hostname)) { + if (!info.insert.some((insert): boolean => insert(control))) { + return; + } } } - this.updateControl(); } diff --git a/src/ts/inspector.ts b/src/ts/inspector.ts index 80b6e6dd8..a3056f5d5 100644 --- a/src/ts/inspector.ts +++ b/src/ts/inspector.ts @@ -187,6 +187,26 @@ const ENTRY_INFO: EntryInfo[] = [ actionClass: 'ubNewsSearchAction', display: 'default', }, + { + id: 'StartPage.Search.Default', + target: '.w-gl__result', + targetDepth: 0, + pageLink: '> .w-gl__result-title', + pageLinkType: 'default', + actionParent: '', + actionClass: 'ubDefaultAction', + display: 'default', + }, + { + id: 'StartPage.Search.Image', + target: '.ig-gl__list .image-container', + targetDepth: 0, + pageLink: 'span.site', + pageLinkType: 'default', + actionParent: '', + actionClass: 'ubImageSearchAction', + display: 'imageSearch', + }, ]; export interface InspectResult { @@ -223,7 +243,7 @@ export function inspectEntry(elem: HTMLElement): InspectResult | null { } pageUrl = parsed.searchParams.get('q') || pageUrl; } else { - pageUrl = (pageLink as HTMLAnchorElement).href; + pageUrl = pageLink.getAttribute('href') as string; } let actionParent!: HTMLElement; if (info.actionParent) { From 5482420a37d55d2ffbbfdd9617ffc35f70a30e03 Mon Sep 17 00:00:00 2001 From: DanSnow Date: Sat, 19 Oct 2019 22:17:18 +0800 Subject: [PATCH 2/2] feat: Support startpage optional --- src/_locales/en/messages.json | 16 +++++ src/_locales/zh_TW/messages.json | 13 ++++ src/manifest.json.js | 3 - src/options.html | 29 ++++++++ src/ts/background.ts | 117 +++++++++++++++++++++++++++++++ src/ts/common.ts | 7 ++ src/ts/options.ts | 59 +++++++++++++--- 7 files changed, 233 insertions(+), 11 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 3599e4cdf..07502b1c6 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -244,5 +244,21 @@ "options_addSubscriptionDialog_addButton": { "message": "Add", "description": "The text of the add button on the add-subscription dialog." + }, + "options_extraSiteSupportTitle": { + "message": "Extra site support", + "description": "The title of the extra-site-support section." + }, + "options_extraSiteSupportDescription": { + "message": "You can enable these site supporting below. Please notice this will require the user permission to access the site you enabled", + "description": "The title of the extra-site-support section." + }, + "options_enableSite": { + "message": "Enable", + "description": "The text of enable extra site support" + }, + "options_enabled": { + "message": "Enabled", + "description": "The text for site already enabled" } } diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json index 9f9f8c832..fdea8aa00 100644 --- a/src/_locales/zh_TW/messages.json +++ b/src/_locales/zh_TW/messages.json @@ -184,5 +184,18 @@ }, "options_addSubscriptionDialog_addButton": { "message": "Add" + }, + "options_extraSiteSupportTitle": { + "message": "支援額外的網站" + }, + "options_extraSiteSupportDescription": { + "message": "你可以開啟以下網站的支援,請注意你必須允許此擴充功能存取你要支援的網站" + }, + "options_enableSite": { + "message": "啟用", + "description": "The text of enable extra site support" + }, + "options_enabled": { + "message": "已啟用" } } diff --git a/src/manifest.json.js b/src/manifest.json.js index 6b45b0886..e24963c2c 100644 --- a/src/manifest.json.js +++ b/src/manifest.json.js @@ -248,9 +248,6 @@ const manifest = { 'https://www.google.co.zm/search?*', 'https://www.google.co.zw/search?*', 'https://www.google.cat/search?*', - 'https://www.startpage.com/do/search*', - 'https://www.startpage.com/rvd/search*', - 'https://www.startpage.com/sp/search*', ], }, ], diff --git a/src/options.html b/src/options.html index 4926f635a..e1609a084 100644 --- a/src/options.html +++ b/src/options.html @@ -58,6 +58,35 @@

+
+

+ +

+
    +
  • +

    + +

    +
  • +
  • +
    +
    + +
    +
    + + +
    +
    +
  • +
+

diff --git a/src/ts/background.ts b/src/ts/background.ts index 25e3734cc..ce14fdb83 100644 --- a/src/ts/background.ts +++ b/src/ts/background.ts @@ -14,6 +14,7 @@ import { sendMessage, addMessageListener, BackgroundPage, + SiteID, } from './common'; const backgroundPage = (window as Window) as BackgroundPage; @@ -438,12 +439,128 @@ backgroundPage.removeCachedAuthToken = async function(token: string): Promise { + return new Promise((resolve, reject): void => { + chrome.tabs.executeScript( + tabId, + { code: loadCheck, runAt: 'document_start' }, + (result): void => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + return; + } + + resolve(result && result[0]); + }, + ); + }); +} + +function checkSiteAccess(url: string, request = false): Promise { + const method = request ? 'request' : 'contains'; + return new Promise((resolve, reject) => { + const u = new URL(url); + chrome.permissions[method]({ origins: [`${u.protocol}//${u.hostname}/*`] }, granted => { + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + } else { + resolve(granted); + } + }); + }); +} + +// #if BROWSER === 'chrome' +chrome.tabs.onUpdated.addListener((tabId, { status }): void => { + if (status !== 'loading') { + return; + } + + chrome.tabs.get( + tabId, + async (tab): Promise => { + const loadCheck = `window['__UBLACKLIST_CONTENT_SCRIPT_DYNAMIC_INJECTED__']`; + + if (!tab.url || !(await checkSiteAccess(tab.url)) || !wasPreviouslyLoaded(tabId, loadCheck)) { + return; + } + for (const { matches } of Object.values(EXTRA_SITE_INFO)) { + for (const url of matches) { + if (tab.url.startsWith(url)) { + chrome.tabs.insertCSS({ file: 'css/content.css', runAt: 'document_start' }); + chrome.tabs.executeScript({ file: 'js/content.js', runAt: 'document_start' }); + chrome.tabs.executeScript({ code: `${loadCheck}=true`, runAt: 'document_start' }); + } + } + } + }, + ); +}); +// #endif + +backgroundPage.hasSiteEnable = function(site: SiteID): Promise { + return checkSiteAccess(EXTRA_SITE_INFO[site].baseUrl); +}; + +backgroundPage.enableSite = async function(site: SiteID): Promise { + // #if BROWSER === 'firefox' + const info = EXTRA_SITE_INFO[site]; + if (info.registration) { + await info.registration.unregister(); + } + + EXTRA_SITE_INFO[site].registration = await chrome.contentScripts.register({ + css: [{ file: 'css/content.css' }], + js: [{ file: 'js/content.js' }], + runAt: 'document_start', + matches: EXTRA_SITE_INFO[site].matches, + }); + // #endif +}; + +backgroundPage.requestEnableSite = function(site: SiteID): Promise { + return checkSiteAccess(EXTRA_SITE_INFO[site].baseUrl, true).then(async granted => { + if (granted) { + await backgroundPage.enableSite(site); + } + return granted; + }); +}; + +// #endregion Extra Site + async function main(): Promise { addMessageListener('setBlacklist', ({ blacklist }) => { backgroundPage.setBlacklist(blacklist); }); const { syncInterval, updateInterval } = await getOptions('syncInterval', 'updateInterval'); + if (await backgroundPage.hasSiteEnable('startpage')) { + await backgroundPage.enableSite('startpage'); + } backgroundPage.syncBlacklist(); setInterval(() => { backgroundPage.syncBlacklist(); diff --git a/src/ts/common.ts b/src/ts/common.ts index dad3b722c..5324dd06b 100644 --- a/src/ts/common.ts +++ b/src/ts/common.ts @@ -185,6 +185,8 @@ export function addMessageListener(type: string, listener: (args: T) => void) // #region BackgroundPage +export type SiteID = 'startpage'; + export interface BackgroundPage extends Window { // Blacklist setBlacklist(blacklist: string): Promise; @@ -197,6 +199,11 @@ export interface BackgroundPage extends Window { updateSubscription(id: SubscriptionId): Promise; updateAllSubscriptions(): Promise; + // Extra Site + hasSiteEnable(site: SiteID): Promise; + requestEnableSite(site: SiteID): Promise; + enableSite(site: SiteID): Promise; + // Auth getAuthToken(interactive: boolean): Promise; removeCachedAuthToken(token: string): Promise; diff --git a/src/ts/options.ts b/src/ts/options.ts index 9d5636f8f..e0a9911ba 100644 --- a/src/ts/options.ts +++ b/src/ts/options.ts @@ -1,26 +1,28 @@ import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; import 'dayjs/locale/en'; import 'dayjs/locale/ja'; import 'dayjs/locale/ru'; import 'dayjs/locale/tr'; import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-tw'; -import relativeTime from 'dayjs/plugin/relativeTime'; import { - lines, - unlines, + BackgroundPage, Result, - isErrorResult, - SubscriptionId, + SiteID, Subscription, + SubscriptionId, Subscriptions, - getOptions, - setOptions, addMessageListener, - BackgroundPage, getBackgroundPage, + getOptions, + isErrorResult, + lines, + setOptions, + unlines, } from './common'; + let backgroundPage: BackgroundPage; async function requestSiteAccess(url: string): Promise { @@ -75,6 +77,8 @@ function $(id: 'showSubscriptionDialog_background'): HTMLDivElement; function $(id: 'showSubscriptionDialog_name'): HTMLParagraphElement; function $(id: 'showSubscriptionDialog_blacklist'): HTMLTextAreaElement; function $(id: 'showSubscriptionDialog_ok'): HTMLButtonElement; +function $(id: 'startpageSupport'): HTMLButtonElement; +function $(id: 'startpageSupportOn'): HTMLButtonElement; function $(id: string): Element | null { return document.getElementById(id) as Element | null; } @@ -129,6 +133,44 @@ function setupGeneralSection(blacklist: string, hideBlockLinks: boolean): void { // #endregion General +// #region ExtraSiteSupport + +async function bindSiteSupportEvent( + site: SiteID, + $grantButton: HTMLButtonElement, + $grantedButton: HTMLButtonElement, +): Promise { + async function turnOn(): Promise { + $grantedButton.classList.remove('is-hidden'); + $grantButton.classList.add('is-hidden'); + } + + if (await backgroundPage.hasSiteEnable(site)) { + turnOn(); + return; + } + $grantButton.addEventListener( + 'click', + async (): Promise => { + try { + chrome.permissions.request({ origins: ['https://www.startpage.com/*'] }, granted => { + if (granted) { + turnOn(); + } + }); + } catch { + // ignore + } + }, + ); +} + +function setupExtraSiteSupport(): void { + bindSiteSupportEvent('startpage', $('startpageSupport'), $('startpageSupportOn')); +} + +// #endregion ExtraSiteSupport + // #region Sync function onSyncChanged(sync: boolean): void { @@ -341,6 +383,7 @@ async function main(): Promise { 'subscriptions', ); setupGeneralSection(blacklist, hideBlockLinks); + setupExtraSiteSupport(); setupSyncSection(sync, syncResult); // #if BROWSER === 'firefox' const { os } = await browser.runtime.getPlatformInfo();