Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
esperecyan committed Oct 15, 2017
0 parents commit c3dc956
Show file tree
Hide file tree
Showing 23 changed files with 2,191 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
globals:
WebDAVClient: true
UserChromeESOptionsStorage: true
UserScriptsInitializer: true
h: true
parseUserScript: true

parserOptions:
ecmaVersion: 2017

env:
webextensions: true
363 changes: 363 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

89 changes: 89 additions & 0 deletions background/background.es
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@

new class Background {
/**
* @constant {RegExp}
*/
static get ALLOWED_WEBDAV_DIRECTORY_URL_PATTERN() {return /^http:\/\/localhost(?::[0-9]+)?\/([^?#]+\/)?$/;}

constructor()
{
this.watchWebDAVDirectorySettingChanged();

console.group('userChromeES');
this.initialize()
.then(console.groupEnd)
.catch(function (exception) {
setTimeout(console.groupEnd, 1);
throw exception;
})
.then(() => UserScriptsInitializer.executeScripts(window));
}

/**
* @access private
* @returns {Promise.<void>}
*/
async initialize()
{
const directory = (await UserChromeESOptionsStorage.getOptionsFromStorage()).directory;

if (directory) {
console.info(`${directory} からスクリプトを取得します。`);
await new UserScriptsInitializer().loadScripts(await this.getScriptFileURLs(directory));
} else {
console.warn('WebDAVディレクトリのURLを設定する必要があります。');
}
}

/**
* @access private
* @param {string} directory
* @returns {Promise.<string[]>}
*/
async getScriptFileURLs(directory)
{
return (await WebDAVClient.index(directory)).filter(fileURL => /\.uc\.(?:es|js)$/.test(fileURL));
}

/**
* @access private
*/
watchWebDAVDirectorySettingChanged(directory)
{
UserChromeESOptionsStorage.getOptionsFromStorage().then(function (options) {
if (options.webDAVDirectorySettingChangedIllegally) {
browser.notifications.create('', {
type: 'basic',
title: 'userChromeES',
message: 'WebDAVディレクトリのURLが、ユーザースクリプトにって不正な値に変更されたため、設定を削除し、userChromeESを再起動しました。',
});
UserChromeESOptionsStorage.setOptionsToStorage({webDAVDirectorySettingChangedIllegally: false});
}
});

browser.storage.onChanged.addListener(function (changes, areaName) {
if (areaName === 'local') {
const userChromeESOptionsStorageChnage = changes['user-chrome-es'];
if (userChromeESOptionsStorageChnage) {
const newDirectory = userChromeESOptionsStorageChnage.newValue.directory;
if (newDirectory) {
console.group('userChromeES');
if (Background.ALLOWED_WEBDAV_DIRECTORY_URL_PATTERN.test(newDirectory)) {
console.info(`WebDAVディレクトリのURLが ${newDirectory} に変更されました。`);
console.groupEnd();
} else {
console.error(`WebDAVディレクトリのURLが、ユーザースクリプトにって不正な値 ${newDirectory} に変更されました。`
+ '設定を削除し、userChromeESを再起動します。');
console.groupEnd();
browser.storage.local.set({'user-chrome-es': {
directory: '',
webDAVDirectorySettingChangedIllegally: true,
}}).then(() => browser.runtime.reload());
}
}
}

}
});
}
}();
15 changes: 15 additions & 0 deletions background/background.xhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>userChromeES</title>
</head>
<body>
<script src="/third-party/parse-meta-line.js"></script>
<script src="/third-party/parse-user-script.js"></script>
<script src="/options/user-chrome-es-options-storage.es"></script>
<script src="webdav-client.es"></script>
<script src="user-scripts-initializer.es"></script>
<script src="background.es"></script>
</body>
</html>
138 changes: 138 additions & 0 deletions background/user-scripts-initializer.es
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

window.UserScriptsInitializer = class {
/**
* @constant {string[]}
*/
static get VALID_INCLUDE_KEY_VALUES() {return ['background', 'popup', 'options', 'devtools', 'sidebar'];}

static executeScripts(win)
{
win.document.body
.append(UserScriptsInitializer.scripts[/^\/(.+)\/\1\.xhtml$/.exec(win.location.pathname)[1]]);
}

/**
* @param {string[]} scriptFileURLs
* @returns {Promise.<void>}
*/
async loadScripts(scriptFileURLs)
{
const table = {};

for (const url of scriptFileURLs) {
const file = await this.getScriptFile(url);

const metaData = await this.getMetaData(file);
if (!this.validateMetaData(metaData, url)) {
continue;
}

for (const value of metaData.includes) {
UserScriptsInitializer.scripts[value].append(this.createScriptElement(file));
}

const fileName = /[^/]*$/.exec(url)[0];

UserScriptsInitializer.scriptsInfomation.push({
name: metaData.name || fileName,
url: url,
});

table[fileName] = {
'@name': metaData.name || null,
'@description': metaData.description || null,
'@include': metaData.includes.join('" "'),
};
}

if (Object.keys(table).length > 0) {
console.table(table);
console.info('以上のスクリプトを読み込みました。');
} else {
console.info('スクリプトは一つも読み込まれていません。');
}
}

/**
* @access private
* @param {Blob} file
* @returns {HTMLScriptElement}
*/
createScriptElement(file)
{
const script = document.createElement('script');
script.src = URL.createObjectURL(file);
return script;
}

/**
* @access private
* @param {?Object} metaData
* @param {string} url
* @returns {boolean}
*/
validateMetaData(metaData, url)
{
if (!metaData) {
console.error(`メタデータが含まれていないため、 ${url} を無視します。`);
return false;
}

if (metaData.includes.length === 0) {
console.error(
`妥当な %c@include%c キーが含まれていないため、 ${url} を無視します。`,
'color: black; background: rgba(255, 255, 255, 0.5); margin: 0 0.3em; padding: 0.1em 0.3em;',
''
);
return false;
}

return true;
}

/**
* @access private
* @see [greasemonkey/parse-user-script.js at master · greasemonkey/greasemonkey]{@link https://github.com/greasemonkey/greasemonkey/blob/master/src/parse-user-script.js}
* @param {Blob} file
* @returns {Promise.<Object>}
*/
async getMetaData(file)
{
const metaData = parseUserScript(await new Response(file).text(), 'https://dummy.invalid/', true);
if (metaData) {
metaData.includes
= UserScriptsInitializer.VALID_INCLUDE_KEY_VALUES.filter(value => metaData.includes.includes(value));
}
return metaData;
}

/**
* @access private
* @param {string} url
* @returns {Promise.<Blob>}
*/
async getScriptFile(url)
{
const response = await fetch(url);

if (response.status === 200) {
return response.blob();
}

return Promise.reject(new Error(`次のスクリプトの取得に失敗しました。\n${url}\n\n${await response.text()}`));
}
};

/**
* @type {Array.<Object.<string>>}
*/
UserScriptsInitializer.scriptsInfomation = [];

/**
* @access private
* @type {Object.<DocumentFragment>}
*/
UserScriptsInitializer.scripts = {};
for (const value of UserScriptsInitializer.VALID_INCLUDE_KEY_VALUES) {
UserScriptsInitializer.scripts[value] = new DocumentFragment();
}
40 changes: 40 additions & 0 deletions background/webdav-client.es
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

class WebDAVClient
{
/**
* Gets child file URLs from the specified directory URL.
* @param {string} directory
* @returns {Promise.<string[]>}
*/
static async index(directory)
{
const response = await fetch(directory, {
method: 'PROPFIND',
headers: {depth: '1'},
body: `<?xml version="1.0" encoding="utf-8" ?>
<propfind xmlns="DAV:">
<prop />
</propfind>`,
});

const responseText = await response.text();

if (response.status === 207) {
const responses = new DOMParser().parseFromString(responseText, 'application/xml')
.getElementsByTagNameNS('DAV:', 'response');

if (responses.length > 0) {
const fileURLs = [];
for (const response of Array.from(responses)) {
const url = response.getElementsByTagNameNS('DAV:', 'href')[0].textContent;
if (!url.endsWith('/')) {
fileURLs.push(new URL(url, directory).href);
}
}
return fileURLs;
}
}

return Promise.reject(new Error(`ファイル一覧の取得に失敗しました。\n\n${responseText}`));
}
}
2 changes: 2 additions & 0 deletions devtools/devtools.es
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

browser.runtime.getBackgroundPage().then(win => win.UserScriptsInitializer.executeScripts(window));
10 changes: 10 additions & 0 deletions devtools/devtools.xhtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>userChromeES</title>
</head>
<body>
<script src="devtools.es"></script>
</body>
</html>
78 changes: 78 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"manifest_version": 2,
"name": "userChromeES",
"description": "uc / userChromeJS+サブスクリプトローダ 風に、ローカルに立てたWebDAVサーバーを介し、WebExtension APIを叩くユーザースクリプトを読み込みます。",
"version": "0.1.1",
"applications": {
"gecko": {
"id": "[email protected]"
}
},

"permissions": [
"http://localhost/*",

"alarms",
"background",
"browserSettings",
"browsingData",
"contextMenus",
"contentSettings",
"contextualIdentities",
"debugger",
"downloads",
"downloads.open",
"find",
"identity",
"management",
"menus",
"nativeMessaging",
"notifications",
"pageCapture",
"privacy",
"proxy",
"sessions",
"storage",
"theme",

"unlimitedStorage"
],
"optional_permissions": [
"<all_urls>",

"bookmarks",
"cookies",
"geolocation",
"history",
"idle",
"tabs",
"topSites",
"webNavigation",
"webRequest",
"webRequestBlocking",

"clipboardWrite",
"clipboardRead"
],
"content_security_policy": "script-src 'self' blob:; object-src 'self' blob:",

"background": {
"page": "background/background.xhtml"
},
"browser_action": {
"browser_style": true,
"default_popup": "popup/popup.xhtml"
},
"options_ui": {
"browser_style": true,
"page": "options/options.xhtml"
},
"devtools_page": "devtools/devtools.xhtml",
"sidebar_action": {
"browser_style": true,
"default_panel": "sidebar/sidebar.xhtml"
},

"author": "100の人",
"homepage_url": "https://addons.mozilla.org/firefox/addon/user-chrome-es/"
}
Loading

0 comments on commit c3dc956

Please sign in to comment.