Merge pull request #378 from Deathamns/ports/update

Ports update
This commit is contained in:
Raymond Hill 2014-11-23 16:04:11 -02:00
commit 26518df057
23 changed files with 538 additions and 2365 deletions

4
.gitignore vendored
View File

@ -1,6 +1,4 @@
*.bak
*.pem
*.safariextension/
/meta/safariextz/certs/
/dist/build/
/tmp/
/dist/build/

View File

@ -1,29 +0,0 @@
{
"name": "µBlock",
"clean_name": "uBlock",
"url": "https://github.com/gorhill/uBlock",
"author": "Raymond Hill",
"author_email": "rhill@raymondhill.net",
"version": "0.7.0.10",
"def_lang": "en",
"vendors": {
"crx": {
"app_id": "cjpalhdlnbpafiamejdnhcphjbkeiagm",
"manifest": "manifest.json",
"locales": "_locales",
"file_ext": ".crx",
"private_key": "./meta/crx/key.pem"
},
"safariextz": {
"app_id": "net.gorhill.uBlock",
"manifest": {
"Info": "Info.plist",
"Settings": "Settings.plist"
},
"file_ext": ".safariextz",
"developer_identifier": "",
"cert_dir": "./meta/safariextz/certs/",
"private_key": "./meta/safariextz/key.pem"
}
}
}

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
<app appid="{app_id}">
<updatecheck codebase="{url}/{name}.crx" version="{version}"/>
</app>
</gupdate>

View File

@ -158,9 +158,9 @@ vAPI.tabs.open = function(details) {
if ( details.select ) {
chrome.tabs.query({ currentWindow: true }, function(tabs) {
var url = targetURL.replace(rgxHash, '');
// this is questionable
var rgxHash = /#.*/;
// this is questionable
var url = targetURL.replace(rgxHash, '');
var selected = tabs.some(function(tab) {
if ( tab.url.replace(rgxHash, '') === url ) {
chrome.tabs.update(tab.id, { active: true });

View File

@ -3,19 +3,19 @@
<plist version="1.0">
<dict>
<key>Author</key>
<string>{author}</string>
<string>Raymond Hill</string>
<key>Builder Version</key>
<string>534.57.2</string>
<key>CFBundleDisplayName</key>
<string>{name}</string>
<string>µBlock</string>
<key>CFBundleIdentifier</key>
<string>{app_id}</string>
<string>net.gorhill.uBlock</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>{version}</string>
<string>0.7.0.10</string>
<key>CFBundleVersion</key>
<string>{build_number}</string>
<string>1456132</string>
<key>Chrome</key>
<dict>
<key>Database Quota</key>
@ -43,7 +43,7 @@
<key>Image</key>
<string>img/icon16.png</string>
<key>Label</key>
<string>{name}</string>
<string>µBlock</string>
<key>Popover</key>
<string>popover</string>
</dict>
@ -70,7 +70,7 @@
</array>
</dict>
<key>Description</key>
<string>{description}</string>
<string>Finally, an efficient blocker. Easy on CPU and memory.</string>
<key>ExtensionInfoDictionaryVersion</key>
<string>1.0</string>
<key>Permissions</key>
@ -84,8 +84,8 @@
</dict>
</dict>
<!-- <key>Update Manifest URL</key>
<string>{url}update_safariextz.plist</string> -->
<string>https://github.com/gorhill/uBlockupdate_safariextz.plist</string> -->
<key>Website</key>
<string>{url}</string>
<string>https://github.com/gorhill/uBlock</string>
</dict>
</plist>

View File

@ -1,4 +1,3 @@
// Only for Safari
// Adding new URL requires to whitelist it in the background script too (addContentScriptFromURL)
// Note that the sitePach function will be converted to a string, and injected
// into the web-page in order to run in that scope. Because of this, variables

View File

@ -6,15 +6,15 @@
<array>
<dict>
<key>CFBundleIdentifier</key>
<string>{app_id}</string>
<string>net.gorhill.uBlock</string>
<key>Developer Identifier</key>
<string>{developer_identifier}</string>
<string></string>
<key>CFBundleShortVersionString</key>
<string>{version}</string>
<string>0.7.0.10</string>
<key>CFBundleVersion</key>
<string>{build_number}</string>
<string>1456132</string>
<key>URL</key>
<string>{url}/{name}.safariextz</string>
<string>https://.../uBlock.safariextz</string>
</dict>
</array>
</dict>

View File

@ -19,10 +19,9 @@
Home: https://github.com/gorhill/uBlock
*/
/* global µBlock, SafariBrowserTab */
// For background page
/* global SafariBrowserTab, Services, XPCOMUtils */
/******************************************************************************/
(function() {
@ -158,159 +157,183 @@ vAPI.storage = {
vAPI.tabs = {
stack: {},
stackID: 1,
registerListeners: function() {
var onNavigation = this.onNavigation;
stackID: 1
};
if (typeof onNavigation === 'function') {
this.onNavigation = function(e) {
// e.url is not present for local files or data URIs,
// or probably for those URLs which we don't have access to
if (!e.target || !e.target.url) {
return;
}
/******************************************************************************/
onNavigation({
frameId: 0,
tabId: vAPI.tabs.getTabId(e.target),
url: e.target.url
});
};
vAPI.tabs.registerListeners = function() {
var onNavigation = this.onNavigation;
safari.application.addEventListener('navigate', this.onNavigation, true);
}
// ??
/* if (typeof this.onUpdated === 'function') { } */
// onClosed handled in the main tab-close event
// onPopup is handled in window.open on web-pages?
/* if (typeof onPopup === 'function') { } */
},
getTabId: function(tab) {
for (var i in vAPI.tabs.stack) {
if (vAPI.tabs.stack[i] === tab) {
return +i;
}
}
return -1;
},
get: function(tabId, callback) {
var tab;
if (tabId === null) {
tab = safari.application.activeBrowserWindow.activeTab;
tabId = this.getTabId(tab);
}
else {
tab = this.stack[tabId];
}
if (!tab) {
callback();
return;
}
callback({
id: tabId,
index: tab.browserWindow.tabs.indexOf(tab),
windowId: safari.application.browserWindows.indexOf(tab.browserWindow),
active: tab === tab.browserWindow.activeTab,
url: tab.url,
title: tab.title
});
},
open: function(details) {
if (!details.url) {
return null;
}
// extension pages
else if (!details.url.match(/^\w{2,20}:/)) {
details.url = vAPI.getURL(details.url);
}
// properties of the details object:
// url: 'URL', // the address that will be opened
// tabId: 1, // the tab is used if set, instead of creating a new one
// index: -1, // undefined: end of the list, -1: following tab, or after index
// active: false, // opens the tab in background - true and undefined: foreground
// select: true // if a tab is already opened with that url, then select it instead of opening a new one
var curWin, tab;
if (details.select) {
tab = safari.application.browserWindows.some(function(win) {
var rgxHash = /#.*/;
// this is questionable
var url = details.url.replace(rgxHash, '');
for (var i = 0; i < win.tabs.length; ++i) {
if (win.tabs[i].url.replace(rgxHash, '') === url) {
win.tabs[i].activate();
return true;
}
}
});
if (tab) {
if (typeof onNavigation === 'function') {
this.onNavigation = function(e) {
// e.url is not present for local files or data URIs,
// or probably for those URLs which we don't have access to
if (!e.target || !e.target.url) {
return;
}
onNavigation({
frameId: 0,
tabId: vAPI.tabs.getTabId(e.target),
url: e.target.url
});
};
safari.application.addEventListener('navigate', this.onNavigation, true);
}
// ??
/* if (typeof this.onUpdated === 'function') { } */
// onClosed handled in the main tab-close event
// onPopup is handled in window.open on web-pages?
/* if (typeof onPopup === 'function') { } */
};
/******************************************************************************/
vAPI.tabs.getTabId = function(tab) {
for (var i in vAPI.tabs.stack) {
if (vAPI.tabs.stack[i] === tab) {
return +i;
}
}
if (details.active === undefined) {
details.active = true;
}
return -1;
};
curWin = safari.application.activeBrowserWindow;
/******************************************************************************/
// it must be calculated before opening a new tab,
// otherwise the new tab will be the active tab here
if (details.index === -1) {
details.index = curWin.tabs.indexOf(curWin.activeTab) + 1;
}
vAPI.tabs.get = function(tabId, callback) {
var tab;
tab = details.tabId && this.stack[details.tabId]
|| curWin.openTab(details.active ? 'foreground' : 'background');
if (tabId === null) {
tab = safari.application.activeBrowserWindow.activeTab;
tabId = this.getTabId(tab);
}
else {
tab = this.stack[tabId];
}
if (details.index !== undefined) {
curWin.insertTab(tab, details.index);
}
if (!tab) {
callback();
return;
}
tab.url = details.url;
},
close: function(tab) {
if (!(tab instanceof SafariBrowserTab)) {
tab = this.stack[tab];
}
callback({
id: tabId,
index: tab.browserWindow.tabs.indexOf(tab),
windowId: safari.application.browserWindows.indexOf(tab.browserWindow),
active: tab === tab.browserWindow.activeTab,
url: tab.url,
title: tab.title
});
};
// properties of the details object:
// url: 'URL', // the address that will be opened
// tabId: 1, // the tab is used if set, instead of creating a new one
// index: -1, // undefined: end of the list, -1: following tab, or after index
// active: false, // opens the tab in background - true and undefined: foreground
// select: true // if a tab is already opened with that url, then select it instead of opening a new one
if (tab) {
tab.close();
}
},
injectScript: function(tabId, details, callback) {
var tab = tabId ? this.stack[tabId] : safari.application.activeBrowserWindow.activeTab;
/******************************************************************************/
if (details.file) {
var xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/x-javascript;charset=utf-8');
xhr.open('GET', details.file, false);
xhr.send();
details.code = xhr.responseText;
}
vAPI.tabs.open = function(details) {
if (!details.url) {
return null;
}
// extension pages
if (!/^[\w-]{2,}:/.test(details.url)) {
details.url = vAPI.getURL(details.url);
}
tab.page.dispatchMessage('broadcast', {
portName: 'vAPI',
msg: {
cmd: 'runScript',
details: details
var curWin, tab;
if (details.select) {
tab = safari.application.browserWindows.some(function(win) {
var rgxHash = /#.*/;
// this is questionable
var url = details.url.replace(rgxHash, '');
for (var i = 0; i < win.tabs.length; ++i) {
if (win.tabs[i].url.replace(rgxHash, '') === url) {
win.tabs[i].activate();
return true;
}
}
});
if (typeof callback === 'function') {
setTimeout(callback, 13);
if (tab) {
return;
}
}
if (details.active === undefined) {
details.active = true;
}
curWin = safari.application.activeBrowserWindow;
// it must be calculated before opening a new tab,
// otherwise the new tab will be the active tab here
if (details.index === -1) {
details.index = curWin.tabs.indexOf(curWin.activeTab) + 1;
}
tab = details.tabId && this.stack[details.tabId]
|| curWin.openTab(details.active ? 'foreground' : 'background');
if (details.index !== undefined) {
curWin.insertTab(tab, details.index);
}
tab.url = details.url;
};
/******************************************************************************/
vAPI.tabs.close = function(tab) {
if (!(tab instanceof SafariBrowserTab)) {
tab = this.stack[tab];
}
if (tab) {
tab.close();
}
};
/******************************************************************************/
vAPI.tabs.injectScript = function(tabId, details, callback) {
var tab;
if (tabId) {
tab = this.stack[tabId];
}
else {
tab = safari.application.activeBrowserWindow.activeTab;
}
if (details.file) {
var xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/x-javascript;charset=utf-8');
xhr.open('GET', details.file, false);
xhr.send();
details.code = xhr.responseText;
}
tab.page.dispatchMessage('broadcast', {
portName: 'vAPI',
msg: {
cmd: 'runScript',
details: details
}
});
if (typeof callback === 'function') {
setTimeout(callback, 13);
}
};
/******************************************************************************/
@ -403,7 +426,9 @@ safari.application.addEventListener('popover', function(e) {
vAPI.tabIcons = { /*tabId: {badge: 0, img: dict}*/ };
vAPI.setIcon = function(tabId, img, badge) {
var curTabId = vAPI.tabs.getTabId(safari.application.activeBrowserWindow.activeTab);
var curTabId = vAPI.tabs.getTabId(
safari.application.activeBrowserWindow.activeTab
);
// from 'activate' event
if (tabId === undefined) {
@ -416,23 +441,25 @@ vAPI.setIcon = function(tabId, img, badge) {
};
}
if (tabId !== curTabId) {
return;
}
// if the selected tab has the same ID, then update the badge too,
// or always update it when changing tabs ('activate' event)
if (tabId === curTabId) {
var items = safari.extension.toolbarItems, i = items.length;
var items = safari.extension.toolbarItems, i = items.length;
while (i--) {
if (items[i].browserWindow === safari.application.activeBrowserWindow) {
if (vAPI.tabIcons[tabId]) {
items[i].badge = vAPI.tabIcons[tabId].badge;
// items[i].img = vAPI.tabIcons[tabId].img;
}
else {
items[i].badge = 0;
}
return;
while (i--) {
if (items[i].browserWindow === safari.application.activeBrowserWindow) {
if (vAPI.tabIcons[tabId]) {
items[i].badge = vAPI.tabIcons[tabId].badge;
// items[i].img = vAPI.tabIcons[tabId].img;
}
else {
items[i].badge = 0;
}
return;
}
}
};
@ -441,242 +468,283 @@ vAPI.setIcon = function(tabId, img, badge) {
vAPI.messaging = {
listeners: {},
listen: function(listenerName, callback) {
this.listeners[listenerName] = callback;
},
setup: function(connector) {
if (this.connector) {
return;
}
defaultHandler: null,
NOOPFUNC: function(){},
UNHANDLED: 'vAPI.messaging.notHandled'
};
this.connector = function(request) {
var callback = function(response) {
if (response !== undefined) {
request.target.page.dispatchMessage(
request.name,
{
requestId: request.message.requestId,
portName: request.message.portName,
msg: response
}
);
/******************************************************************************/
vAPI.messaging.listen = function(listenerName, callback) {
this.listeners[listenerName] = callback;
};
/******************************************************************************/
vAPI.messaging.onMessage = function(request) {
var callback = vAPI.messaging.NOOPFUNC;
if ( request.message.requestId !== undefined ) {
callback = function(response) {
request.target.page.dispatchMessage(
request.name,
{
requestId: request.message.requestId,
portName: request.message.portName,
msg: response !== undefined ? response : null
}
};
var sender = {
tab: {
id: vAPI.tabs.getTabId(request.target)
}
};
var listener = connector(request.message.msg, sender, callback);
if (listener === vAPI.messaging.UNHANDLED) {
listener = vAPI.messaging.listeners[request.message.portName];
if (typeof listener === 'function') {
listener(request.message.msg, sender, callback);
} else {
console.error('µBlock> messaging > unknown request: %o', request.message);
}
}
);
};
}
// the third parameter must stay false (bubbling), so later
// onBeforeRequest will use true (capturing), where we can invoke
// stopPropagation() (this way this.connector won't be fired)
safari.application.addEventListener('message', this.connector, false);
},
broadcast: function(message) {
message = {
broadcast: true,
msg: message
};
for (var tabId in vAPI.tabs.stack) {
vAPI.tabs.stack[tabId].page.dispatchMessage('broadcast', message);
var sender = {
tab: {
id: vAPI.tabs.getTabId(request.target)
}
};
// Specific handler
var r = vAPI.messaging.UNHANDLED;
var listener = vAPI.messaging.listeners[request.message.portName];
if ( typeof listener === 'function' ) {
r = listener(request.message.msg, sender, callback);
}
if ( r !== vAPI.messaging.UNHANDLED ) {
return;
}
// Default handler
r = vAPI.messaging.defaultHandler(request.message.msg, sender, callback);
if ( r !== vAPI.messaging.UNHANDLED ) {
return;
}
console.error('µBlock> messaging > unknown request: %o', request.message);
// Unhandled:
// Need to callback anyways in case caller expected an answer, or
// else there is a memory leak on caller's side
callback();
};
/******************************************************************************/
vAPI.messaging.setup = function(defaultHandler) {
// Already setup?
if ( this.defaultHandler !== null ) {
return;
}
if ( typeof defaultHandler !== 'function' ) {
defaultHandler = function(){ return vAPI.messaging.UNHANDLED; };
}
this.defaultHandler = defaultHandler;
// the third parameter must stay false (bubbling), so later
// onBeforeRequest will use true (capturing), where we can invoke
// stopPropagation() (this way this.onMessage won't be fired)
safari.application.addEventListener('message', this.onMessage, false);
};
/******************************************************************************/
vAPI.messaging.broadcast = function(message) {
message = {
broadcast: true,
msg: message
};
for (var tabId in vAPI.tabs.stack) {
vAPI.tabs.stack[tabId].page.dispatchMessage('broadcast', message);
}
};
/******************************************************************************/
vAPI.net = {
registerListeners: function() {
var onBeforeRequest = this.onBeforeRequest;
vAPI.net = {}
if (typeof onBeforeRequest.callback === 'function') {
if (!Array.isArray(onBeforeRequest.types)) {
onBeforeRequest.types = [];
/******************************************************************************/
vAPI.net.registerListeners = function() {
var onBeforeRequest = this.onBeforeRequest;
if (typeof onBeforeRequest.callback === 'function') {
if (!Array.isArray(onBeforeRequest.types)) {
onBeforeRequest.types = [];
}
onBeforeRequest = onBeforeRequest.callback;
this.onBeforeRequest.callback = function(e) {
var block;
if (e.name !== 'canLoad') {
return;
}
onBeforeRequest = onBeforeRequest.callback;
this.onBeforeRequest.callback = function(e) {
var block;
if (e.name !== 'canLoad') {
return;
}
// no stopPropagation if it was called from beforeNavigate event
if (e.stopPropagation) {
e.stopPropagation();
}
if (e.message.isWhiteListed) {
block = µBlock.URI.hostnameFromURI(e.message.isWhiteListed);
block = µBlock.URI.domainFromHostname(block) || block;
e.message = !!µBlock.netWhitelist[block];
return e.message;
}
// blocking unwanted pop-ups
if (e.message.type === 'popup') {
if (typeof vAPI.tabs.onPopup === 'function') {
e.message.type = 'main_frame';
e.message.sourceTabId = vAPI.tabs.getTabId(e.target);
if (vAPI.tabs.onPopup(e.message)) {
e.message = false;
return;
}
}
e.message = true;
return;
}
block = vAPI.net.onBeforeRequest;
if (block.types.indexOf(e.message.type) < 0) {
return true;
}
e.message.tabId = vAPI.tabs.getTabId(e.target);
block = onBeforeRequest(e.message);
// truthy return value will allow the request,
// except when redirectUrl is present
if (block && typeof block === 'object') {
if (block.cancel) {
e.message = false;
}
else if (e.message.type === 'script'
&& typeof block.redirectUrl === "string") {
e.message = block.redirectUrl;
}
else {
e.message = true;
}
}
else {
e.message = true;
}
// no stopPropagation if it was called from beforeNavigate event
if (e.stopPropagation) {
e.stopPropagation();
}
if (e.message.isWhiteListed) {
block = µBlock.URI.hostnameFromURI(e.message.isWhiteListed);
block = µBlock.URI.domainFromHostname(block) || block;
e.message = !!µBlock.netWhitelist[block];
return e.message;
};
}
safari.application.addEventListener('message', this.onBeforeRequest.callback, true);
}
}
};
// blocking unwanted pop-ups
if (e.message.type === 'popup') {
if (typeof vAPI.tabs.onPopup === 'function') {
e.message.type = 'main_frame';
e.message.sourceTabId = vAPI.tabs.getTabId(e.target);
/******************************************************************************/
vAPI.contextMenu = {
create: function(details, callback) {
var contexts = details.contexts;
var menuItemId = details.id;
var menuTitle = details.title;
if (Array.isArray(contexts) && contexts.length) {
contexts = contexts.indexOf('all') === -1 ? contexts : null;
}
else {
// default in Chrome
contexts = ['page'];
}
this.onContextMenu = function(e) {
var uI = e.userInfo;
if (uI && /^https?:\/\//i.test(uI.pageUrl)) {
if (contexts) {
var invalidContext = true;
for (var i = 0; i < contexts.length; ++i) {
if (contexts[i] === 'frame') {
if (uI.insideFrame) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'link') {
if (uI.linkHref) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'image') {
if (uI.srcUrl) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'audio' || contexts[i] === 'video') {
if (uI.srcUrl && uI.tagName === contexts[i]) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'editable') {
if (uI.editable) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'page') {
if (!(uI.insideFrame || uI.linkHref || uI.mediaType || uI.editable)) {
invalidContext = false;
break;
}
}
}
if (invalidContext) {
if (vAPI.tabs.onPopup(e.message)) {
e.message = false;
return;
}
}
e.contextMenu.appendContextMenuItem(menuItemId, menuTitle);
e.message = true;
return;
}
block = vAPI.net.onBeforeRequest;
if (block.types.indexOf(e.message.type) < 0) {
return true;
}
e.message.tabId = vAPI.tabs.getTabId(e.target);
block = onBeforeRequest(e.message);
// truthy return value will allow the request,
// except when redirectUrl is present
if (block && typeof block === 'object') {
if (block.cancel) {
e.message = false;
}
else if (e.message.type === 'script'
&& typeof block.redirectUrl === "string") {
e.message = block.redirectUrl;
}
else {
e.message = true;
}
}
else {
e.message = true;
}
return e.message;
};
this.onContextMenuCommand = function(e) {
if (e.command === menuItemId) {
var tab = e.currentTarget.activeBrowserWindow.activeTab;
e.userInfo.menuItemId = menuItemId;
callback(e.userInfo, tab ? {
id: vAPI.tabs.getTabId(tab),
url: tab.url
} : undefined);
}
};
safari.application.addEventListener('contextmenu', this.onContextMenu);
safari.application.addEventListener("command", this.onContextMenuCommand);
},
remove: function() {
safari.application.removeEventListener('contextmenu', this.onContextMenu);
safari.application.removeEventListener("command", this.onContextMenuCommand);
this.onContextMenu = null;
this.onContextMenuCommand = null;
safari.application.addEventListener('message', this.onBeforeRequest.callback, true);
}
};
/******************************************************************************/
vAPI.lastError = {
vAPI.contextMenu = {};
/******************************************************************************/
vAPI.contextMenu.create = function(details, callback) {
var contexts = details.contexts;
var menuItemId = details.id;
var menuTitle = details.title;
if (Array.isArray(contexts) && contexts.length) {
contexts = contexts.indexOf('all') === -1 ? contexts : null;
}
else {
// default in Chrome
contexts = ['page'];
}
this.onContextMenu = function(e) {
var uI = e.userInfo;
if (uI && /^https?:\/\//i.test(uI.pageUrl)) {
if (contexts) {
var invalidContext = true;
for (var i = 0; i < contexts.length; ++i) {
if (contexts[i] === 'frame') {
if (uI.insideFrame) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'link') {
if (uI.linkHref) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'image') {
if (uI.srcUrl) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'audio'
|| contexts[i] === 'video') {
if (uI.srcUrl && uI.tagName === contexts[i]) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'editable') {
if (uI.editable) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'page') {
if (!(uI.insideFrame || uI.linkHref
|| uI.mediaType || uI.editable)) {
invalidContext = false;
break;
}
}
}
if (invalidContext) {
return;
}
}
e.contextMenu.appendContextMenuItem(menuItemId, menuTitle);
}
};
this.onContextMenuCmd = function(e) {
if (e.command === menuItemId) {
var tab = e.currentTarget.activeBrowserWindow.activeTab;
e.userInfo.menuItemId = menuItemId;
callback(e.userInfo, tab ? {
id: vAPI.tabs.getTabId(tab),
url: tab.url
} : undefined);
}
};
safari.application.addEventListener('contextmenu', this.onContextMenu);
safari.application.addEventListener("command", this.onContextMenuCmd);
};
/******************************************************************************/
vAPI.contextMenu.remove = function() {
safari.application.removeEventListener('contextmenu', this.onContextMenu);
safari.application.removeEventListener("command", this.onContextMenuCmd);
this.onContextMenu = null;
this.onContextMenuCmd = null;
};
/******************************************************************************/
vAPI.lastError = function() {
return null;
};

View File

@ -21,8 +21,6 @@
// For non background pages
/* global addMessageListener, removeMessageListener, sendAsyncMessage */
/******************************************************************************/
(function() {
@ -36,42 +34,41 @@ self.vAPI.safari = true;
/******************************************************************************/
// since this is common across vendors
var messagingConnector = function(response) {
var channel, listener;
if (!response) {
if ( !response ) {
return;
}
if (response.broadcast === true) {
for (channel in vAPI.messaging.channels) {
listener = vAPI.messaging.channels[channel].listener;
var channel, listener;
if (typeof listener === 'function') {
if ( response.broadcast === true ) {
for ( channel in vAPI.messaging.channels ) {
listener = vAPI.messaging.channels[channel].listener;
if ( typeof listener === 'function' ) {
listener(response.msg);
}
}
return;
}
if (response.requestId) {
if ( response.requestId ) {
listener = vAPI.messaging.listeners[response.requestId];
delete vAPI.messaging.listeners[response.requestId];
delete response.requestId;
}
if (!listener) {
if ( !listener ) {
channel = vAPI.messaging.channels[response.portName];
listener = channel && channel.listener;
}
if (typeof listener === 'function') {
if ( typeof listener === 'function' ) {
listener(response.msg);
}
};
/******************************************************************************/
var uniqueId = function() {
return parseInt(Math.random() * 1e10, 10).toString(36);
};
@ -83,8 +80,9 @@ var uniqueId = function() {
vAPI.messaging = {
channels: {},
listeners: {},
requestId: 0,
requestId: 1,
connectorId: uniqueId(),
setup: function() {
this.connector = function(msg) {
// messages from the background script are sent to every frame,
@ -108,11 +106,13 @@ vAPI.messaging = {
close: function() {
if (this.connector) {
safari.self.removeEventListener('message', this.connector, false);
this.connector = this.channels = this.listeners = null;
this.connector = null;
this.channels = {};
this.listeners = {};
}
},
channel: function(channelName, callback) {
if (!channelName) {
if ( !channelName ) {
return;
}
@ -129,15 +129,19 @@ vAPI.messaging = {
msg: message
};
if (callback) {
message.requestId = ++vAPI.messaging.requestId;
if ( callback ) {
message.requestId = vAPI.messaging.requestId++;
vAPI.messaging.listeners[message.requestId] = callback;
}
// popover content doesn't know messaging...
if (safari.extension.globalPage) {
// popover content doesn't know messaging...
if (!safari.self.visible) {
return;
}
safari.extension.globalPage.contentWindow
.vAPI.messaging.connector({
.vAPI.messaging.onMessage({
name: vAPI.messaging.connectorId,
message: message,
target: {
@ -167,6 +171,8 @@ vAPI.messaging = {
/******************************************************************************/
// content scripts are loaded into extension pages by default, but they shouldn't
if (location.protocol === "safari-extension:") {
return;
}
@ -184,7 +190,7 @@ if (!window.MutationObserver) {
handler([{addedNodes: [e.target]}]);
}, true);
};
}
};
}
/******************************************************************************/

View File

@ -27,45 +27,44 @@
'use strict';
/******************************************************************************/
self.vAPI = self.vAPI || {};
/******************************************************************************/
// http://www.w3.org/International/questions/qa-scripts#directions
var setScriptDirection = function(langugae) {
var setScriptDirection = function(language) {
document.body.setAttribute(
'dir',
~['ar', 'he', 'fa', 'ps', 'ur'].indexOf(langugae) ? 'rtl' : 'ltr'
~['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) ? 'rtl' : 'ltr'
);
};
/******************************************************************************/
vAPI.download = function(details) {
if (!details.url) {
if ( !details.url ) {
return;
}
var a = document.createElement('a');
if ('download' in a) {
if ( 'download' in a ) {
a.href = details.url;
a.setAttribute('download', details.filename || '');
a.dispatchEvent(new MouseEvent('click'));
return;
}
else {
var messager = vAPI.messaging.channel('_download');
messager.send({
what: 'gotoURL',
details: {
url: details.url,
index: -1
}
});
messager.close();
}
var messager = vAPI.messaging.channel('_download');
messager.send({
what: 'gotoURL',
details: {
url: details.url,
index: -1
}
});
messager.close();
};
/******************************************************************************/
@ -76,22 +75,29 @@ vAPI.getURL = function(path) {
/******************************************************************************/
var xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/json;charset=utf-8');
xhr.open('GET', './locales.json', false);
xhr.send();
vAPI.i18nData = JSON.parse(xhr.responseText);
// supported languages
// first language is the default
vAPI.i18nData = [
"en", "ar", "cs", "da", "de", "el", "es", "et", "fi", "fr", "he", "hi",
"hr", "hu", "id", "it", "ja", "mr", "nb", "nl", "pl", "pt_BR", "pt_PT",
"ro", "ru", "sv", "tr", "uk", "vi", "zh_CN"
];
if (vAPI.i18nData[vAPI.i18n = navigator.language.replace('-', '_')]
|| vAPI.i18nData[vAPI.i18n = vAPI.i18n.slice(0, 2)]) {
vAPI.i18nLocale = vAPI.i18n;
} else {
vAPI.i18nLocale = vAPI.i18nData._;
vAPI.i18n = navigator.language.replace('-', '_');
if (vAPI.i18nData.indexOf(vAPI.i18n) === -1) {
vAPI.i18n = vAPI.i18n.slice(0, 2);
if (vAPI.i18nData.indexOf(vAPI.i18n) === -1) {
vAPI.i18n = vAPI.i18nData[0];
}
}
xhr = new XMLHttpRequest;
setScriptDirection(vAPI.i18n);
var xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/json;charset=utf-8');
xhr.open('GET', './_locales/' + vAPI.i18nLocale + '/messages.json', false);
xhr.open('GET', './_locales/' + vAPI.i18n + '/messages.json', false);
xhr.send();
vAPI.i18nData = JSON.parse(xhr.responseText);
@ -103,12 +109,10 @@ vAPI.i18n = function(s) {
return this.i18nData[s] || s;
};
setScriptDirection(vAPI.i18nLocale);
/******************************************************************************/
// update popover size to its content
if (safari.self.identifier === 'popover' && safari.self) {
if (safari.self.identifier === 'popover') {
var onLoaded = function() {
// Initial dimensions are set in Info.plist
var pWidth = safari.self.width;
@ -142,7 +146,7 @@ if (safari.self.identifier === 'popover' && safari.self) {
}
};
window.addEventListener('load', );
window.addEventListener('load', onLoaded);
}
/******************************************************************************/

9
meta/vapi-appinfo.js Normal file
View File

@ -0,0 +1,9 @@
// can be included anywhere if it's needed
'use strict';
self.vAPI = self.vAPI || {};
vAPI.app = {
name: 'µBlock',
version: '0.7.0.10'
};

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Author</key>
<string>Raymond Hill</string>
<key>Builder Version</key>
<string>534.57.2</string>
<key>CFBundleDisplayName</key>
<string>µBlock</string>
<key>CFBundleIdentifier</key>
<string>net.gorhill.uBlock</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>0.7.0.10</string>
<key>CFBundleVersion</key>
<string>1456132</string>
<key>Chrome</key>
<dict>
<key>Database Quota</key>
<real>52428800</real>
<key>Global Page</key>
<string>background.html</string>
<key>Popovers</key>
<array>
<dict>
<key>Filename</key>
<string>popup.html</string>
<key>Height</key>
<real>310</real>
<key>Identifier</key>
<string>popover</string>
<key>Width</key>
<real>180</real>
</dict>
</array>
<key>Toolbar Items</key>
<array>
<dict>
<key>Identifier</key>
<string>toolbarItem</string>
<key>Image</key>
<string>img/icon16.png</string>
<key>Label</key>
<string>µBlock</string>
<key>Popover</key>
<string>popover</string>
</dict>
</array>
</dict>
<key>Content</key>
<dict>
<key>Scripts</key>
<dict>
<key>End</key>
<array>
<string>js/contentscript-end.js</string>
</array>
<key>Start</key>
<array>
<string>js/vapi-client.js</string>
<string>js/contentscript-start.js</string>
</array>
</dict>
<key>Whitelist</key>
<array>
<string>http://*/*</string>
<string>https://*/*</string>
</array>
</dict>
<key>Description</key>
<string>Finally, an efficient blocker for Chromium-based browsers. Easy on CPU and memory.</string>
<key>ExtensionInfoDictionaryVersion</key>
<string>1.0</string>
<key>Permissions</key>
<dict>
<key>Website Access</key>
<dict>
<key>Include Secure Pages</key>
<true/>
<key>Level</key>
<string>All</string>
</dict>
</dict>
<!-- <key>Update Manifest URL</key>
<string>https://github.com/gorhill/uBlockupdate_safariextz.plist</string> -->
<key>Website</key>
<string>https://github.com/gorhill/uBlock</string>
</dict>
</plist>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>DefaultValue</key>
<false/>
<key>FalseValue</key>
<false/>
<key>Key</key>
<string>open_prefs</string>
<key>Secure</key>
<false/>
<key>Title</key>
<string>Click to see the Preferences</string>
<key>TrueValue</key>
<true/>
<key>Type</key>
<string>CheckBox</string>
</dict>
</array>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 B

View File

@ -1,10 +0,0 @@
// can be included anywhere if it's needed
'use strict';
self.vAPI = self.vAPI || {};
vAPI.app = {
/**/name: 'µBlock',
/**/version: '0.7.0.10',
/**/url: 'https://github.com/gorhill/uBlock',
};

View File

@ -1,905 +0,0 @@
// » header
/* global SafariBrowserTab, Services, XPCOMUtils */
// for background page only
(function() {
'use strict';
self.vAPI = self.vAPI || {};
// «
if (self.chrome) {
// » crx
var chrome = self.chrome;
vAPI.chrome = true;
vAPI.storage = chrome.storage.local;
vAPI.tabs = {
registerListeners: function() {
if (typeof this.onNavigation === 'function') {
chrome.webNavigation.onCommitted.addListener(this.onNavigation);
}
if (typeof this.onUpdated === 'function') {
chrome.tabs.onUpdated.addListener(this.onUpdated);
}
if (typeof this.onClosed === 'function') {
chrome.tabs.onRemoved.addListener(this.onClosed);
}
if (typeof this.onPopup === 'function') {
chrome.webNavigation.onCreatedNavigationTarget.addListener(this.onPopup);
}
},
get: function(tabId, callback) {
if (tabId === null) {
chrome.tabs.query(
{
active: true,
currentWindow: true
},
function(tabs) {
callback(tabs[0]);
}
);
}
else {
chrome.tabs.get(tabId, callback);
}
},
/*open: function(details) {
// to keep incognito context?
chrome.windows.getCurrent(function(win) {
details.windowId = win.windowId;
chrome.tabs.create(details);
});
},*/
open: function(details) {
if (!details.url) {
return null;
}
// extension pages
else if (!details.url.match(/^\w{2,20}:/)) {
details.url = vAPI.getURL(details.url);
}
// dealing with Chrome's asynhronous API
var wrapper = function() {
if (details.active === undefined) {
details.active = true;
}
var subWrapper = function() {
var _details = {
url: details.url,
active: !!details.active
};
if (details.tabId) {
// update doesn't accept index, must use move
chrome.tabs.update(details.tabId, _details, function(tab) {
// if the tab doesn't exist
if (chrome.runtime.lastError) {
chrome.tabs.create(_details);
}
else if (details.index !== undefined) {
chrome.tabs.move(tab.id, {index: details.index});
}
});
}
else {
if (details.index !== undefined) {
_details.index = details.index;
}
chrome.tabs.create(_details);
}
};
if (details.index === -1) {
vAPI.tabs.get(null, function(tab) {
if (tab) {
details.index = tab.index + 1;
}
else {
delete details.index;
}
subWrapper();
});
}
else {
subWrapper();
}
};
if (details.select) {
// note that currentWindow may be even the window of Developer Tools
// so, test with setTimeout...
chrome.tabs.query({currentWindow: true}, function(tabs) {
var url = details.url.replace(rgxHash, '');
// this is questionable
var rgxHash = /#.*/;
tabs = tabs.some(function(tab) {
if (tab.url.replace(rgxHash, '') === url) {
chrome.tabs.update(tab.id, {active: true});
return true;
}
});
if (!tabs) {
wrapper();
}
});
}
else {
wrapper();
}
},
close: chrome.tabs.remove.bind(chrome.tabs),
injectScript: function(tabId, details, callback) {
if (!callback) {
callback = function(){};
}
if (tabId) {
chrome.tabs.executeScript(tabId, details, callback);
}
else {
chrome.tabs.executeScript(details, callback);
}
}
};
// Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
// https://github.com/gorhill/uBlock/issues/19
// https://github.com/gorhill/uBlock/issues/207
// Since we may be called asynchronously, the tab id may not exist
// anymore, so this ensures it does still exist.
vAPI.setIcon = function(tabId, img, badge) {
var onIconReady = function() {
if ( chrome.runtime.lastError ) {
return;
}
chrome.browserAction.setBadgeText({ tabId: tabId, text: badge });
if ( badge !== '' ) {
chrome.browserAction.setBadgeBackgroundColor({ tabId: tabId, color: '#666' });
}
};
chrome.browserAction.setIcon({ tabId: tabId, path: img }, onIconReady);
};
vAPI.messaging = {
ports: {},
listeners: {},
listen: function(listenerName, callback) {
this.listeners[listenerName] = callback;
},
setup: function(connector) {
if (this.connector) {
return;
}
this.connector = function(port) {
var onMessage = function(request) {
var callback = function(response) {
if (chrome.runtime.lastError || response === undefined) {
return;
}
if (request.requestId) {
port.postMessage({
requestId: request.requestId,
portName: request.portName,
msg: response
});
}
};
var listener = connector(request.msg, port.sender, callback);
if (listener === null) {
listener = vAPI.messaging.listeners[request.portName];
if (typeof listener === 'function') {
listener(request.msg, port.sender, callback);
} else {
console.error('µBlock> messaging > unknown request: %o', request);
}
}
};
var onDisconnect = function(port) {
port.onDisconnect.removeListener(onDisconnect);
port.onMessage.removeListener(onMessage);
delete vAPI.messaging.ports[port.name];
};
port.onDisconnect.addListener(onDisconnect);
port.onMessage.addListener(onMessage);
vAPI.messaging.ports[port.name] = port;
};
chrome.runtime.onConnect.addListener(this.connector);
},
broadcast: function(message) {
message = {
broadcast: true,
msg: message
};
for (var portName in this.ports) {
this.ports[portName].postMessage(message);
}
}
};
vAPI.net = {
registerListeners: function() {
var listeners = [
'onBeforeRequest',
'onBeforeSendHeaders',
'onHeadersReceived'
];
for (var i = 0; i < listeners.length; ++i) {
chrome.webRequest[listeners[i]].addListener(
this[listeners[i]].callback,
{
'urls': this[listeners[i]].urls || ['<all_urls>'],
'types': this[listeners[i]].types || []
},
this[listeners[i]].extra
);
}
}
};
vAPI.contextMenu = {
create: function(details, callback) {
this.menuId = details.id;
this.callback = callback;
chrome.contextMenus.create(details);
chrome.contextMenus.onClicked.addListener(this.callback);
},
remove: function() {
chrome.contextMenus.onClicked.removeListener(this.callback);
chrome.contextMenus.remove(this.menuId);
}
};
// «
} else if (self.safari) {
// » safariextz
vAPI.safari = true;
// addContentScriptFromURL allows whitelisting,
// so load sitepaching this way, instead of adding it to the Info.plist
safari.extension.addContentScriptFromURL(
safari.extension.baseURI + 'js/sitepatch-safari.js',
[
'http://www.youtube.com/*',
'https://www.youtube.com/*',
'http://www.youtube-nocookie.com/*',
'https://www.youtube-nocookie.com/*'
]
);
safari.extension.settings.addEventListener('change', function(e) {
if (e.key === 'open_prefs') {
vAPI.tabs.open({url: 'dashboard.html', active: true});
}
}, false);
vAPI.storage = {
_storage: safari.extension.settings,
QUOTA_BYTES: 52428800, // copied from Info.plist
get: function(keys, callback) {
if (typeof callback !== 'function') {
return;
}
var i, value, result = {};
if (keys === null) {
for (i in this._storage) {
value = this._storage[i];
if (typeof value === 'string') {
result[i] = JSON.parse(value);
}
}
}
else if (typeof keys === 'string') {
value = this._storage[keys];
if (typeof value === 'string') {
result[keys] = JSON.parse(value);
}
}
else if (Array.isArray(keys)) {
for ( i = 0; i < keys.length; ++i) {
value = this._storage[i];
if (typeof value === 'string') {
result[keys[i]] = JSON.parse(value);
}
}
}
else if (typeof keys === 'object') {
for (i in keys) {
value = this._storage[i];
if (typeof value === 'string') {
result[i] = JSON.parse(value);
}
else {
result[i] = keys[i];
}
}
}
callback(result);
},
set: function(details, callback) {
for (var key in details) {
this._storage.setItem(key, JSON.stringify(details[key]));
}
if (typeof callback === 'function') {
callback();
}
},
remove: function(keys) {
if (typeof keys === 'string') {
keys = [keys];
}
for (var i = 0; i < keys.length; ++i) {
this._storage.removeItem(keys[i]);
}
},
clear: function(callback) {
this._storage.clear();
callback();
},
getBytesInUse: function(keys, callback) {
var key, size = 0;
if (keys === null) {
for (key in this._storage) {
size += (this._storage[key] || '').length;
}
}
else {
if (typeof keys === 'string') {
keys = [keys];
}
for (key = 0; key < keys.length; ++key) {
size += (this._storage[keys[key]] || '').length;
}
}
callback(size);
}
};
vAPI.tabs = {
stack: {},
stackID: 1,
registerListeners: function() {
var onNavigation = this.onNavigation;
if (typeof onNavigation === 'function') {
this.onNavigation = function(e) {
// e.url is not present for local files or data URIs,
// or probably for those URLs which we don't have access to
if (!e.target || !e.target.url) {
return;
}
onNavigation({
frameId: 0,
tabId: vAPI.tabs.getTabId(e.target),
url: e.target.url
});
};
safari.application.addEventListener('navigate', this.onNavigation, true);
}
// ??
/* if (typeof this.onUpdated === 'function') { } */
// onClosed handled in the main tab-close event
// onPopup is handled in window.open on web-pages?
/* if (typeof onPopup === 'function') { } */
},
getTabId: function(tab) {
for (var i in vAPI.tabs.stack) {
if (vAPI.tabs.stack[i] === tab) {
return +i;
}
}
return -1;
},
get: function(tabId, callback) {
var tab;
if (tabId === null) {
tab = safari.application.activeBrowserWindow.activeTab;
tabId = this.getTabId(tab);
}
else {
tab = this.stack[tabId];
}
if (!tab) {
callback();
return;
}
callback({
id: tabId,
index: tab.browserWindow.tabs.indexOf(tab),
windowId: safari.application.browserWindows.indexOf(tab.browserWindow),
active: tab === tab.browserWindow.activeTab,
url: tab.url,
title: tab.title
});
},
open: function(details) {
if (!details.url) {
return null;
}
// extension pages
else if (!details.url.match(/^\w{2,20}:/)) {
details.url = vAPI.getURL(details.url);
}
// properties of the details object:
// url: 'URL', // the address that will be opened
// tabId: 1, // the tab is used if set, instead of creating a new one
// index: -1, // undefined: end of the list, -1: following tab, or after index
// active: false, // opens the tab in background - true and undefined: foreground
// select: true // if a tab is already opened with that url, then select it instead of opening a new one
var curWin, tab;
if (details.select) {
tab = safari.application.browserWindows.some(function(win) {
var rgxHash = /#.*/;
// this is questionable
var url = details.url.replace(rgxHash, '');
for (var i = 0; i < win.tabs.length; ++i) {
if (win.tabs[i].url.replace(rgxHash, '') === url) {
win.tabs[i].activate();
return true;
}
}
});
if (tab) {
return;
}
}
if (details.active === undefined) {
details.active = true;
}
curWin = safari.application.activeBrowserWindow;
// it must be calculated before opening a new tab,
// otherwise the new tab will be the active tab here
if (details.index === -1) {
details.index = curWin.tabs.indexOf(curWin.activeTab) + 1;
}
tab = details.tabId && this.stack[details.tabId]
|| curWin.openTab(details.active ? 'foreground' : 'background');
if (details.index !== undefined) {
curWin.insertTab(tab, details.index);
}
tab.url = details.url;
},
close: function(tab) {
if (!(tab instanceof SafariBrowserTab)) {
tab = this.stack[tab];
}
if (tab) {
tab.close();
}
},
injectScript: function(tabId, details, callback) {
var tab = tabId ? this.stack[tabId] : safari.application.activeBrowserWindow.activeTab;
if (details.file) {
var xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/x-javascript;charset=utf-8');
xhr.open('GET', details.file, false);
xhr.send();
details.code = xhr.responseText;
}
tab.page.dispatchMessage('broadcast', {
portName: 'vAPI',
msg: {
cmd: 'runScript',
details: details
}
});
if (typeof callback === 'function') {
setTimeout(callback, 13);
}
}
};
// bind tabs to unique IDs
(function() {
var wins = safari.application.browserWindows, i = wins.length, j;
var tabs = [];
while (i--) {
j = wins[i].tabs.length;
while (j--) {
tabs.push(wins[i].tabs[j]);
}
}
return tabs;
})().forEach(function(tab) {
vAPI.tabs.stack[vAPI.tabs.stackID++] = tab;
});
safari.application.addEventListener('open', function(e) {
// ignore windows
if (e.target instanceof SafariBrowserTab) {
vAPI.tabs.stack[vAPI.tabs.stackID++] = e.target;
}
}, true);
safari.application.addEventListener('close', function(e) {
// ignore windows
if (!(e.target instanceof SafariBrowserTab)) {
return;
}
var tabId = vAPI.tabs.getTabId(e.target);
if (tabId > -1) {
// to not add another listener, put this here
// instead of vAPI.tabs.registerListeners
if (typeof vAPI.tabs.onClosed === 'function') {
vAPI.tabs.onClosed(tabId);
}
delete vAPI.tabIcons[tabId];
delete vAPI.tabs.stack[tabId];
}
}, true);
// update badge when tab is activated
safari.application.addEventListener('activate', function(e) {
// hide popover, since in some cases won't close by itself
var items = safari.extension.toolbarItems;
for (var i = 0; i < items.length; ++i) {
if (items[i].browserWindow === safari.application.activeBrowserWindow) {
if (items[i].popover) {
items[i].popover.hide();
}
break;
}
}
// ignore windows
if (!(e.target instanceof SafariBrowserTab)) {
return;
}
// update the badge, when tab is selected
vAPI.setIcon();
}, true);
// reload the popup when that is opened
safari.application.addEventListener('popover', function(e) {
e.target.contentWindow.document.body.textContent = '';
e.target.contentWindow.location.reload();
}, true);
vAPI.tabIcons = { /*tabId: {badge: 0, img: dict}*/ };
vAPI.setIcon = function(tabId, img, badge) {
var curTabId = vAPI.tabs.getTabId(safari.application.activeBrowserWindow.activeTab);
// from 'activate' event
if (tabId === undefined) {
tabId = curTabId;
}
else {
vAPI.tabIcons[tabId] = {
badge: badge || 0/*,
img: img*/
};
}
// if the selected tab has the same ID, then update the badge too,
// or always update it when changing tabs ('activate' event)
if (tabId === curTabId) {
var items = safari.extension.toolbarItems, i = items.length;
while (i--) {
if (items[i].browserWindow === safari.application.activeBrowserWindow) {
if (vAPI.tabIcons[tabId]) {
items[i].badge = vAPI.tabIcons[tabId].badge;
// items[i].img = vAPI.tabIcons[tabId].img;
}
else {
items[i].badge = 0;
}
return;
}
}
}
};
vAPI.messaging = {
listeners: {},
listen: function(listenerName, callback) {
this.listeners[listenerName] = callback;
},
setup: function(connector) {
if (this.connector) {
return;
}
this.connector = function(request) {
var callback = function(response) {
if (response !== undefined) {
request.target.page.dispatchMessage(
request.name,
{
requestId: request.message.requestId,
portName: request.message.portName,
msg: response
}
);
}
};
var sender = {
tab: {
id: vAPI.tabs.getTabId(request.target)
}
};
var listener = connector(request.message.msg, sender, callback);
if (listener === vAPI.messaging.UNHANDLED) {
listener = vAPI.messaging.listeners[request.message.portName];
if (typeof listener === 'function') {
listener(request.message.msg, sender, callback);
} else {
console.error('µBlock> messaging > unknown request: %o', request.message);
}
}
};
// the third parameter must stay false (bubbling), so later
// onBeforeRequest will use true (capturing), where we can invoke
// stopPropagation() (this way this.connector won't be fired)
safari.application.addEventListener('message', this.connector, false);
},
broadcast: function(message) {
message = {
broadcast: true,
msg: message
};
for (var tabId in vAPI.tabs.stack) {
vAPI.tabs.stack[tabId].page.dispatchMessage('broadcast', message);
}
}
};
vAPI.net = {
registerListeners: function() {
var onBeforeRequest = this.onBeforeRequest;
if (typeof onBeforeRequest.callback === 'function') {
if (!Array.isArray(onBeforeRequest.types)) {
onBeforeRequest.types = [];
}
onBeforeRequest = onBeforeRequest.callback;
this.onBeforeRequest.callback = function(e) {
var block;
if (e.name !== 'canLoad') {
return;
}
// no stopPropagation if it was called from beforeNavigate event
if (e.stopPropagation) {
e.stopPropagation();
}
if (e.message.isWhiteListed) {
block = µBlock.URI.hostnameFromURI(e.message.isWhiteListed);
block = µBlock.URI.domainFromHostname(block) || block;
e.message = !!µBlock.netWhitelist[block];
return e.message;
}
// blocking unwanted pop-ups
if (e.message.type === 'popup') {
if (typeof vAPI.tabs.onPopup === 'function') {
e.message.type = 'main_frame';
e.message.sourceTabId = vAPI.tabs.getTabId(e.target);
if (vAPI.tabs.onPopup(e.message)) {
e.message = false;
return;
}
}
e.message = true;
return;
}
block = vAPI.net.onBeforeRequest;
if (block.types.indexOf(e.message.type) < 0) {
return true;
}
e.message.tabId = vAPI.tabs.getTabId(e.target);
block = onBeforeRequest(e.message);
// truthy return value will allow the request,
// except when redirectUrl is present
if (block && typeof block === 'object') {
if (block.cancel) {
e.message = false;
}
else if (e.message.type === 'script'
&& typeof block.redirectUrl === "string") {
e.message = block.redirectUrl;
}
else {
e.message = true;
}
}
else {
e.message = true;
}
return e.message;
};
safari.application.addEventListener('message', this.onBeforeRequest.callback, true);
}
}
};
vAPI.contextMenu = {
create: function(details, callback) {
var contexts = details.contexts;
var menuItemId = details.id;
var menuTitle = details.title;
if (Array.isArray(contexts) && contexts.length) {
contexts = contexts.indexOf('all') === -1 ? contexts : null;
}
else {
// default in Chrome
contexts = ['page'];
}
this.onContextMenu = function(e) {
var uI = e.userInfo;
if (uI && /^https?:\/\//i.test(uI.pageUrl)) {
if (contexts) {
var invalidContext = true;
for (var i = 0; i < contexts.length; ++i) {
if (contexts[i] === 'frame') {
if (uI.insideFrame) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'link') {
if (uI.linkHref) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'image') {
if (uI.srcUrl) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'audio' || contexts[i] === 'video') {
if (uI.srcUrl && uI.tagName === contexts[i]) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'editable') {
if (uI.editable) {
invalidContext = false;
break;
}
}
else if (contexts[i] === 'page') {
if (!(uI.insideFrame || uI.linkHref || uI.mediaType || uI.editable)) {
invalidContext = false;
break;
}
}
}
if (invalidContext) {
return;
}
}
e.contextMenu.appendContextMenuItem(menuItemId, menuTitle);
}
};
this.onContextMenuCommand = function(e) {
if (e.command === menuItemId) {
var tab = e.currentTarget.activeBrowserWindow.activeTab;
e.userInfo.menuItemId = menuItemId;
callback(e.userInfo, tab ? {
id: vAPI.tabs.getTabId(tab),
url: tab.url
} : undefined);
}
};
safari.application.addEventListener('contextmenu', this.onContextMenu);
safari.application.addEventListener("command", this.onContextMenuCommand);
},
remove: function() {
safari.application.removeEventListener('contextmenu', this.onContextMenu);
safari.application.removeEventListener("command", this.onContextMenuCommand);
this.onContextMenu = null;
this.onContextMenuCommand = null;
}
};
// «
}
// » footer
if (!self.chrome) {
self.chrome = { runtime: { lastError: null } };
}
})();
// «

View File

@ -1,406 +0,0 @@
// » header
/* global addMessageListener, removeMessageListener, sendAsyncMessage */
// for non background pages
(function() {
'use strict';
self.vAPI = self.vAPI || {};
// since this is common across vendors
var messagingConnector = function(response) {
var channel, listener;
if (!response) {
return;
}
if (response.broadcast === true) {
for (channel in vAPI.messaging.channels) {
listener = vAPI.messaging.channels[channel].listener;
if (typeof listener === 'function') {
listener(response.msg);
}
}
return;
}
if (response.requestId) {
listener = vAPI.messaging.listeners[response.requestId];
delete vAPI.messaging.listeners[response.requestId];
delete response.requestId;
}
if (!listener) {
channel = vAPI.messaging.channels[response.portName];
listener = channel && channel.listener;
}
if (typeof listener === 'function') {
listener(response.msg);
}
};
var uniqueId = function() {
return parseInt(Math.random() * 1e10, 10).toString(36);
};
// «
if (self.chrome) {
// » crx
vAPI.chrome = true;
vAPI.messaging = {
port: null,
channels: {},
listeners: {},
requestId: 0,
connectorId: uniqueId(),
setup: function() {
this.port = chrome.runtime.connect({name: this.connectorId});
this.port.onMessage.addListener(messagingConnector);
},
close: function() {
if (this.port) {
this.port.disconnect();
this.port.onMessage.removeListener(messagingConnector);
this.port = this.channels = this.listeners = null;
}
},
channel: function(channelName, callback) {
if (!channelName) {
return;
}
this.channels[channelName] = {
portName: channelName,
listener: typeof callback === 'function' ? callback : null,
send: function(message, callback) {
if (!vAPI.messaging.port) {
vAPI.messaging.setup();
}
message = {
portName: this.portName,
msg: message
};
if (callback) {
message.requestId = ++vAPI.messaging.requestId;
vAPI.messaging.listeners[message.requestId] = callback;
}
vAPI.messaging.port.postMessage(message);
},
close: function() {
delete vAPI.messaging.channels[this.portName];
}
};
return this.channels[channelName];
}
};
// «
} else if (self.safari) {
// » safariextz
vAPI.safari = true;
// relevant?
// https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/MessagesandProxies/MessagesandProxies.html#//apple_ref/doc/uid/TP40009977-CH14-SW12
vAPI.messaging = {
channels: {},
listeners: {},
requestId: 0,
connectorId: uniqueId(),
setup: function() {
this.connector = function(msg) {
// messages from the background script are sent to every frame,
// so we need to check the connectorId to accept only
// what is meant for the current context
if (msg.name === vAPI.messaging.connectorId
|| msg.name === 'broadcast') {
messagingConnector(msg.message);
}
};
safari.self.addEventListener('message', this.connector, false);
this.channels['vAPI'] = {
listener: function(msg) {
if (msg.cmd === 'runScript' && msg.details.code) {
Function(msg.details.code).call(self);
}
}
};
},
close: function() {
if (this.connector) {
safari.self.removeEventListener('message', this.connector, false);
this.connector = this.channels = this.listeners = null;
}
},
channel: function(channelName, callback) {
if (!channelName) {
return;
}
this.channels[channelName] = {
portName: channelName,
listener: typeof callback === 'function' ? callback : null,
send: function(message, callback) {
if (!vAPI.messaging.connector) {
vAPI.messaging.setup();
}
message = {
portName: this.portName,
msg: message
};
if (callback) {
message.requestId = ++vAPI.messaging.requestId;
vAPI.messaging.listeners[message.requestId] = callback;
}
if (safari.extension.globalPage) {
// popover content doesn't know messaging...
safari.extension.globalPage.contentWindow
.vAPI.messaging.connector({
name: vAPI.messaging.connectorId,
message: message,
target: {
page: {
dispatchMessage: function(name, msg) {
messagingConnector(msg);
}
}
}
});
}
else {
safari.self.tab.dispatchMessage(
vAPI.messaging.connectorId,
message
);
}
},
close: function() {
delete vAPI.messaging.channels[this.portName];
}
};
return this.channels[channelName];
}
};
if (location.protocol === "safari-extension:") {
return;
}
window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
if (!window.MutationObserver) {
// dummy, minimalistic shim for older versions (<6)
// only supports node insertions, but currently we don't use it for anything else
window.MutationObserver = function(handler) {
this.observe = function(target) {
target.addEventListener('DOMNodeInserted', function(e) {
handler([{addedNodes: [e.target]}]);
}, true);
};
}
}
var beforeLoadEvent = document.createEvent('Event');
beforeLoadEvent.initEvent('beforeload');
var frameId = window === window.top ? 0 : Date.now() % 1E5;
var linkHelper = document.createElement('a');
var onBeforeLoad = function(e, details) {
if (e.url && e.url.slice(0, 5) === 'data:') {
return;
}
linkHelper.href = details ? details.url : e.url;
if (!/^https?:/.test(linkHelper.protocol)) {
return;
}
if (details) {
details.url = linkHelper.href;
}
else {
details = {
url: linkHelper.href
};
switch (e.target.nodeName.toLowerCase()) {
case 'frame':
case 'iframe':
details.type = 'sub_frame';
break;
case 'script':
details.type = 'script';
break;
case 'img':
case 'input': // type=image
details.type = 'image';
break;
case 'object':
case 'embed':
details.type = 'object';
break;
case 'link':
var rel = e.target.rel.trim().toLowerCase();
if (rel.indexOf('icon') > -1) {
details.type = 'image';
break;
}
else if (rel === 'stylesheet') {
details.type = 'stylesheet';
break;
}
default:
details.type = 'other';
}
// This can run even before the first DOMSubtreeModified event fired
if (firstMutation) {
firstMutation();
}
}
// tabId is determined in the background script
// details.tabId = null;
details.frameId = frameId;
details.parentFrameId = frameId ? 0 : -1;
details.timeStamp = Date.now();
var response = safari.self.tab.canLoad(e, details);
if (!response) {
if (details.type === 'main_frame') {
window.stop();
}
else {
e.preventDefault();
}
return false;
}
// local mirroring, response is a data: URL here
else if (typeof response === 'string' && details.type === 'script') {
// Content Security Policy with disallowed inline scripts may break things
e.preventDefault();
details = document.createElement('script');
details.textContent = atob(response.slice(response.indexOf(',', 20) + 1));
if (e.target.hasAttribute('defer') && document.readyState === 'loading') {
var jsOnLoad = function(ev) {
this.removeEventListener(ev.type, jsOnLoad, true);
this.body.removeChild(this.body.appendChild(details));
};
document.addEventListener('DOMContentLoaded', jsOnLoad, true);
}
else {
e.target.parentNode.insertBefore(details, e.target);
details.parentNode.removeChild(details);
}
}
};
document.addEventListener('beforeload', onBeforeLoad, true);
// block pop-ups, intercept xhr requests, and apply site patches
var firstMutation = function() {
document.removeEventListener('DOMSubtreeModified', firstMutation, true);
firstMutation = null;
var randEventName = parseInt(Math.random() * 1e15, 10).toString(36);
window.addEventListener(randEventName, function(e) {
var result = onBeforeLoad(beforeLoadEvent, e.detail);
if (result === false) {
e.detail.url = false;
}
}, true);
// the extension context is unable to reach the page context,
// also this only works when Content Security Policy allows inline scripts
var tmpJS = document.createElement('script');
var tmpScript = ["(function() {",
"var block = function(u, t) {",
"var e = document.createEvent('CustomEvent'),",
"d = {url: u, type: t};",
"e.initCustomEvent('" + randEventName + "', !1, !1, d);",
"dispatchEvent(e);",
"return d.url === !1;",
"}, wo = open, xo = XMLHttpRequest.prototype.open;",
"open = function(u) {",
"return block(u, 'popup') ? null : wo.apply(this, [].slice.call(arguments));",
"};",
"XMLHttpRequest.prototype.open = function(m, u, s) {",
"return xo.apply(this, block(u, 'xmlhttprequest') ? ['HEAD', u, s] : [].slice.call(arguments));",
"};"
];
if (vAPI.sitePatch
&& !safari.self.tab.canLoad(beforeLoadEvent, {isWhiteListed: location.href})) {
tmpScript.push('(' + vAPI.sitePatch + ')();');
}
tmpScript.push("})();");
tmpJS.textContent = tmpScript.join('');
document.documentElement.removeChild(document.documentElement.appendChild(tmpJS));
};
document.addEventListener('DOMSubtreeModified', firstMutation, true);
var onContextMenu = function(e) {
var details = {
tagName: e.target.tagName.toLowerCase(),
pageUrl: location.href,
insideFrame: window !== window.top
};
details.editable = details.tagName === 'textarea' || details.tagName === 'input';
if ('checked' in e.target) {
details.checked = e.target.checked;
}
if (details.tagName === 'a') {
details.linkUrl = e.target.href;
}
if ('src' in e.target) {
details.srcUrl = e.target.src;
if (details.tagName === 'img') {
details.mediaType = 'image';
}
else if (details.tagName === 'video' || details.tagName === 'audio') {
details.mediaType = details.tagName;
}
}
safari.self.tab.setContextMenuEventUserInfo(e, details);
};
self.addEventListener('contextmenu', onContextMenu, true);
// 'main_frame' simulation
if (frameId === 0) {
onBeforeLoad(beforeLoadEvent, {
url: location.href,
type: 'main_frame'
});
}
// «
}
// » footer
})();
// «

View File

@ -1,131 +0,0 @@
// » header
// could be used for background and other extension pages
(function() {
'use strict';
self.vAPI = self.vAPI || {};
// http://www.w3.org/International/questions/qa-scripts#directions
var setScriptDirection = function(langugae) {
document.body.setAttribute(
'dir',
~['ar', 'he', 'fa', 'ps', 'ur'].indexOf(langugae) ? 'rtl' : 'ltr'
);
};
vAPI.download = function(details) {
if (!details.url) {
return;
}
var a = document.createElement('a');
if ('download' in a) {
a.href = details.url;
a.setAttribute('download', details.filename || '');
a.dispatchEvent(new MouseEvent('click'));
}
else {
var messager = vAPI.messaging.channel('_download');
messager.send({
what: 'gotoURL',
details: {
url: details.url,
index: -1
}
});
messager.close();
}
};
// «
if (self.chrome) {
// » crx
var chrome = self.chrome;
vAPI.getURL = function(path) {
return chrome.runtime.getURL(path);
};
vAPI.i18n = function(s) {
return chrome.i18n.getMessage(s) || s;
};
setScriptDirection(vAPI.i18n('@@ui_locale'));
// «
} else if (self.safari) {
// » safariextz
vAPI.getURL = function(path) {
return safari.extension.baseURI + path;
};
var xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/json;charset=utf-8');
xhr.open('GET', './locales.json', false);
xhr.send();
vAPI.i18nData = JSON.parse(xhr.responseText);
if (vAPI.i18nData[vAPI.i18n = navigator.language.replace('-', '_')]
|| vAPI.i18nData[vAPI.i18n = vAPI.i18n.slice(0, 2)]) {
vAPI.i18nLocale = vAPI.i18n;
} else {
vAPI.i18nLocale = vAPI.i18nData._;
}
xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/json;charset=utf-8');
xhr.open('GET', './_locales/' + vAPI.i18nLocale + '/messages.json', false);
xhr.send();
vAPI.i18nData = JSON.parse(xhr.responseText);
for (var i18nKey in vAPI.i18nData) {
vAPI.i18nData[i18nKey] = vAPI.i18nData[i18nKey].message;
}
vAPI.i18n = function(s) {
return this.i18nData[s] || s;
};
setScriptDirection(vAPI.i18nLocale);
// update popover size to its content
if (safari.self.identifier === 'popover' && safari.self) {
window.addEventListener('load', function() {
// Initial dimensions are set in Info.plist
var pWidth = safari.self.width;
var pHeight = safari.self.height;
var upadteTimer = null;
var resizePopover = function() {
if (upadteTimer) {
return;
}
upadteTimer = setTimeout(function() {
safari.self.width = Math.max(pWidth, document.body.clientWidth);
safari.self.height = Math.max(pHeight, document.body.clientHeight);
upadteTimer = null;
}, 20);
};
var mutObs = window.MutationObserver || window.WebkitMutationObserver;
if (mutObs) {
(new mutObs(resizePopover)).observe(document, {
childList: true,
attributes: true,
characterData: true,
subtree: true
});
}
else {
// Safari doesn't support DOMAttrModified
document.addEventListener('DOMSubtreeModified', resizePopover);
}
});
}
// «
}
// » footer
})();
// «

View File

@ -1 +0,0 @@
{"_": "en", "ar": 1, "cs": 1, "da": 1, "de": 1, "el": 1, "en": 1, "es": 1, "et": 1, "fi": 1, "fr": 1, "he": 1, "hi": 1, "hr": 1, "hu": 1, "id": 1, "it": 1, "ja": 1, "mr": 1, "nb": 1, "nl": 1, "pl": 1, "pt-BR": 1, "pt-PT": 1, "ro": 1, "ru": 1, "sv": 1, "tr": 1, "uk": 1, "vi": 1, "zh-CN": 1}

View File

@ -1,61 +0,0 @@
{
"manifest_version": 2,
"minimum_chrome_version": "22.0",
"default_locale": "en",
"update_url": "https://clients2.google.com/service/update2/crx",
"version": "0.7.0.10",
"name": "µBlock",
"description": "__MSG_extShortDesc__",
"homepage_url": "https://github.com/gorhill/uBlock",
"author": "Raymond Hill",
"developer": {
"name": "Raymond Hill",
"email": "rhill@raymondhill.net"
},
"icons": {
"16": "img/icon_16.png",
"128": "img/icon_128.png"
},
"permissions": [
"contextMenus",
"storage",
"tabs",
"unlimitedStorage",
"webNavigation",
"webRequest",
"webRequestBlocking",
"http://*/*",
"https://*/*"
],
"background": {
"page": "background.html"
},
"options_page": "dashboard.html",
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/vapi-client.js", "js/contentscript-start.js"],
"run_at": "document_start",
"all_frames": true
},
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/contentscript-end.js"],
"run_at": "document_end",
"all_frames": true
}
],
"browser_action": {
"default_icon": {
"19": "img/browsericons/icon19-off.png",
"38": "img/browsericons/icon38-off.png"
},
"default_title": "µBlock",
"default_popup": "popup.html"
}
}

View File

@ -1,269 +0,0 @@
#!/usr/bin/env python3
import os
import re
import json
import glob
import sys
import subprocess
from time import strftime
from datetime import datetime
from shutil import which as iscmd, rmtree as rmt, copytree, copy, move
from collections import OrderedDict
from xml.sax.saxutils import escape
osp = os.path
pj = osp.join
os.chdir(pj(osp.split(osp.abspath(__file__))[0], '..'))
def rmtree(path):
if osp.exists(path):
rmt(path)
def mkdirs(path):
try:
os.makedirs(path)
finally:
return osp.exists(path)
def readfile(path, mode='rt'):
with open(path, mode) as f:
return f.read()
src_dir = osp.abspath(pj('src'))
meta_dir = osp.abspath(pj('meta'))
tmp_dir = osp.abspath(pj('tmp'))
with open(pj(meta_dir, 'config.json'), encoding='utf-8') as f:
config = json.load(f)
vendors = config['vendors']
del config['vendors']
tmp = datetime.now() - datetime(year=datetime.today().year, month=1, day=1)
config['build_number'] = strftime('%y' + str(int(tmp.total_seconds() * 65535 / 31536000)).zfill(5))
descriptions = OrderedDict({})
source_locale_dir = pj('src', '_locales')
build_tmp = pj(tmp_dir, config['clean_name'])
build_dir = osp.abspath(pj('dist', 'build', config['version']))
# fill 'descriptions'
for alpha2 in os.listdir(source_locale_dir):
with open(pj(source_locale_dir, alpha2, 'messages.json'), encoding='utf-8') as f:
string_data = json.load(f, object_pairs_hook=OrderedDict)
descriptions[alpha2] = string_data['extShortDesc']['message']
# only needed for Safari
with open(pj(src_dir, 'locales.json'), 'wt', encoding='utf-8', newline='\n') as f:
tmp = {
'_': config['def_lang']
}
for alpha2 in descriptions:
tmp[alpha2] = 1
json.dump(tmp, f, sort_keys=True, ensure_ascii=False)
with open(pj(src_dir, 'js', 'vapi-appinfo.js'), 'r+t', encoding='utf-8', newline='\n') as f:
tmp = f.read()
f.seek(0)
f.write(re.sub(
r'/\*\*/([^:]+:).+',
lambda m: '/**/' + m.group(1) + " '" + config[m.group(1)[:-1]] + "',",
tmp
))
with open(pj(src_dir, vendors['crx']['manifest']), 'wt', encoding='utf-8', newline='\n') as f:
cf_content = readfile(pj(meta_dir, 'crx', vendors['crx']['manifest']))
f.write(
re.sub(r"\{(?=\W)|(?<=\W)\}", r'\g<0>\g<0>', cf_content).format(**config)
)
with open(pj(src_dir, vendors['safariextz']['manifest']['Info']), 'wt', encoding='utf-8', newline='\n') as f:
config['app_id'] = vendors['safariextz']['app_id']
config['description'] = descriptions[config['def_lang']]
cf_content = readfile(pj(meta_dir, 'safariextz', vendors['safariextz']['manifest']['Info']))
f.write(cf_content.format(**config))
copy(pj(meta_dir, 'safariextz', vendors['safariextz']['manifest']['Settings']), pj(src_dir, vendors['safariextz']['manifest']['Settings']))
if 'meta' in sys.argv:
raise SystemExit('Metadata generated.')
rmtree(tmp_dir)
mkdirs(tmp_dir)
rmtree(build_dir)
mkdirs(build_dir)
# create update meta
for vendor, ext in {'crx': 'xml', 'safariextz': 'plist'}.items():
with open(pj(build_dir, 'update_' + vendor + '.' + ext), 'wt', encoding='utf-8', newline='\n') as f:
if vendor == 'safariextz':
config['developer_identifier'] = vendors[vendor]['developer_identifier']
config['app_id'] = vendors[vendor]['app_id']
cf_content = readfile(pj(meta_dir, vendor, 'update_' + vendor + '.' + ext))
f.write(cf_content.format(**config))
f.close()
# separate vendor specific code
for vapijsfile in [pj(src_dir, 'js', 'vapi-' + jsfile + '.js') for jsfile in ['background', 'common', 'client']]:
vapijs = readfile(vapijsfile)
# "» name" is the start marker, "«" is the end marker
js_parts = re.findall(r'»\s*(\w+)\n([^«]+)//', vapijs)
if not js_parts:
continue
js_header = js_parts.pop(0)[1]
js_footer = js_parts.pop()[1]
for js in js_parts:
with open(pj(tmp_dir, js[0] + '_' + osp.basename(vapijsfile)), 'wt', encoding='utf-8', newline='\n') as f:
f.write(js_header)
f.write(re.sub(r'^ ', '', js[1], flags=re.M))
f.write(js_footer)
def move_vendor_specific_js(vendor):
for file in ['background', 'common', 'client']:
move(pj(tmp_dir, vendor + '_vapi-' + file + '.js'), pj(build_tmp, 'js', 'vapi-' + file + '.js'))
def copy_vendor_files(files):
for file in files:
path = pj(src_dir, file)
if osp.isdir(path):
copytree(path, pj(build_tmp, file), copy_function=copy)
else:
copy(path, pj(build_tmp, file))
def remove_vendor_files(files):
for file in files:
path = pj(build_tmp, file)
if osp.isdir(path):
rmtree(path)
else:
os.remove(path)
def norm_cygdrive(path):
return '/cygdrive/' + path[0] + path[2:].replace('\\', '/') if path[1] == ':' else path
mkdirs(build_tmp)
for file in glob.iglob(pj(src_dir, '*')):
basename = osp.basename(file)
if osp.isfile(file) and (file.endswith('.html') or basename == 'icon.png'):
copy(file, pj(build_tmp, basename))
elif osp.isdir(file) and basename not in ['_locales', 'locale']:
copytree(file, pj(build_tmp, basename), copy_function=copy)
os.remove(pj(build_tmp, 'js', 'sitepatch-safari.js'))
package_name = config['clean_name'] + '-' + config['version']
# Chrome
if not iscmd('7z'):
print('Cannot build for Chrome: `7z` command not found.')
else:
vendor_files = ['_locales', 'manifest.json']
move_vendor_specific_js('crx')
copy_vendor_files(vendor_files)
package = pj(build_dir, package_name + '.zip')
subprocess.call('7z a -r -tzip -mx=8 "' + norm_cygdrive(package) + '" "' + norm_cygdrive(pj(build_tmp, '*')) + '"', stdout=subprocess.DEVNULL)
if osp.exists(vendors['crx']['private_key']):
if not iscmd('openssl'):
print('Cannot build for Chrome: `openssl` command not found.')
else:
# Convert the PEM key to DER (and extract the public form) for inclusion in the CRX header
derkey = subprocess.Popen([
'openssl', 'rsa', '-pubout',
'-inform', 'PEM',
'-outform', 'DER',
'-in', norm_cygdrive(vendors['crx']['private_key'])
], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.read()
# Sign the zip file with the private key in PEM format
signature = subprocess.Popen([
'openssl', 'sha1',
'-sign', norm_cygdrive(vendors['crx']['private_key']),
norm_cygdrive(package)
], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.read()
out = open(package.replace('.zip', vendors['crx']['file_ext']), "wb")
# Extension file magic number
out.write(bytes("Cr24\x02\x00\x00\x00", 'UTF-8') + len(derkey).to_bytes(4, 'little') + len(signature).to_bytes(4, 'little'))
out.write(derkey)
out.write(signature)
out.write(readfile(package, 'rb'))
out.close()
subprocess.call('7z a ' + norm_cygdrive(package) + ' ' + norm_cygdrive(osp.abspath(vendors['crx']['private_key'])), stdout=subprocess.DEVNULL)
remove_vendor_files(vendor_files)
# Safari
if not iscmd('xar'):
print('Cannot build for Safari: `xar` command not found.')
elif osp.exists(vendors['safariextz']['cert_dir']):
vendor_files = [
'_locales',
'locales.json',
'Info.plist',
'Settings.plist',
pj('js', 'sitepatch-safari.js')
]
move_vendor_specific_js('safariextz')
copy_vendor_files(vendor_files)
build_tmp = move(build_tmp, pj(tmp_dir, config['clean_name'] + '.safariextension'))
# xar accepts only unix style directory separators
package = pj(build_dir, package_name + vendors['safariextz']['file_ext']).replace('\\', '/');
subprocess.call('xar -czf "' + package + '" --compression-args=9 --distribution --directory="' + osp.basename(tmp_dir) + '" ' + config['clean_name'] + '.safariextension', stderr=subprocess.DEVNULL)
subprocess.call('xar --sign -f "' + package + '" --digestinfo-to-sign sfr_digest.dat --sig-size 256 ' + ' '.join('--cert-loc="' + vendors['safariextz']['cert_dir'] + 'cert0{0}"'.format(i) for i in range(3)), stderr=subprocess.DEVNULL)
subprocess.call('openssl rsautl -sign -inkey ' + vendors['safariextz']['private_key'] + ' -in sfr_digest.dat -out sfr_sig.dat', stderr=subprocess.DEVNULL)
subprocess.call('xar --inject-sig sfr_sig.dat -f "' + package + '"', stderr=subprocess.DEVNULL)
os.remove('sfr_sig.dat')
os.remove('sfr_digest.dat')
build_tmp = move(build_tmp, pj(tmp_dir, config['clean_name']))
remove_vendor_files(vendor_files)
rmtree(tmp_dir)
print("Files ready @ " + build_dir)

View File

@ -12,12 +12,10 @@ rm $DES/assets/*.sh
cp -R src/css $DES/
cp -R src/img $DES/
cp -R src/js $DES/
rm $DES/js/vapi-background.js
rm $DES/js/vapi-client.js
rm $DES/js/vapi-common.js
cp -R src/lib $DES/
cp -R src/_locales $DES/
cp src/*.html $DES/
cp meta/vapi-appinfo.js $DES/js/
cp meta/crx/*.js $DES/js/
cp meta/crx/manifest.json $DES/
echo "*** uBlock.chromium: Package done."

22
tools/make-safari.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/bash
#
# This script assumes a linux environment
echo "*** uBlock.safariextension: Copying files"
DES=dist/build/uBlock.safariextension
rm -r $DES
mkdir -p $DES
cp -R assets $DES/
rm $DES/assets/*.sh
cp -R src/css $DES/
cp -R src/img $DES/
cp -R src/js $DES/
cp -R src/lib $DES/
cp -R src/_locales $DES/
cp src/*.html $DES/
cp src/img/icon_128.png $DES/Icon.png
cp meta/vapi-appinfo.js $DES/js/
cp meta/safariextz/*.js $DES/js/
cp meta/safariextz/Info.plist $DES/
cp meta/safariextz/Settings.plist $DES/
echo "*** uBlock.safariextension: Package done."