- Bear Version: 2.3.3 (12890)
- OS Version: macOS 15.0.1, BuildVersion: 24A348
- What were you doing: Using bear chrome/brave extension
- What feature did you use: web clipper
- What happened: I’m being asked for permission for each and every site to open bear.
- What did you expect to happen: I expect extension to not ask about permissions.
Please note it’s no longer possible to just agree for opening bear from all site once. It’s per site setting. This problem does not exist other note taking apps web clippers, so I assume it’s ralted to the design and usage of “x-callbacks”
Chrome has the highest market share on browsers market. I’m not sure how many macOS user prefer Chrome or Brave to Safari as I was not able to find this data. However, people that switched from Safari to Chrome or Brave won’t go back to Safari, as they had their reasons to make a swich. For those people, me including, using Bear web clipper is frustrating.
I’ve modified locally the extension code to test why it happens. It seems the callback that does clipping is executed in the context of active tab.
If executed from a popup, chrome/brave asks for permission for whole extension, not a website
A POC slice follows.
manifest:
"action": {
"default_icon": {
"19": "icon.png",
"38": "icon@2x.png"
},
"default_title": "__MSG_extTitle__",
"default_popup": "index.html"
},
index.html:
<!DOCTYPE html>
<html>
<head>
<title>Bear Quick Capture</title>
<meta charset="utf-8" />
</head>
<body>
<script src="script2.js"></script>
</body>
</html>
script2.js
const runBearExtension = () => {
const getCurrentTab = async () => {
let [tab] = await chrome.tabs.query({
active: true,
lastFocusedWindow: true,
});
return tab;
};
const getSettings = async () => {
const opts = await chrome.storage.sync.get({
tags: '',
images: true,
appendurl: true
})
settings = '&tags=' + encodeURIComponent(opts.tags);
if ( !opts.images ) {
settings = settings + '&noimages=no';
}
if ( !opts.appendurl ) {
settings = settings + '&appendurl=no';
}
return settings;
};
const buildURL = async () => {
const tab = await getCurrentTab();
const settings = await getSettings();
var url = encodeURIComponent(tab.url);
var html = encodeURIComponent(document.documentElement.innerHTML);
var xurl = 'bear://x-callback-url/grab-url?url=' + url + '&html=' + html + settings;
return xurl;
};
buildURL().then((url) => {
window.open(url);
});
};
runBearExtension();
POC does not work for context menus as I have not modified them. However, if the execution or url open is separated from clipping itself via promise, so the clipping happens in tab context, but send happens in plugin context, it should work.
Well, turns out I badly want that to work, so I made a private copy of the plugin that works, at least for me.
loader.js:
/* CHROME EXTENSION CALLBACKS */
let runBearExtension = function (tab) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["script.js"]
}).then(() => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
// This will be serialized and run in the context of the current tab.
// All of the functions that are called must be defined in the script.js file.
// Chrome will wait for the promise to resolve before moving on.
func: async() => {
return capture_page();
}
}).then((results) => {
let url = results[0].result;
chrome.tabs.update({ url: url });
});
});
}
chrome.action.onClicked.addListener(runBearExtension);
/**
* Create a context menu which will only show up on right-click on the extension icon
*/
chrome.contextMenus.removeAll(function () {
// Now, create the new context menu items
chrome.contextMenus.create({
"id": "bearActionTitleSelection",
"title": chrome.i18n.getMessage("actionTitleSelection"),
"type": "normal",
"contexts": ["action"]
});
chrome.contextMenus.create({
"id": "bearActionTitleURL",
"title": chrome.i18n.getMessage("actionTitleURL"),
"type": "normal",
"contexts": ["action"]
});
chrome.contextMenus.create({
"id": "bearActionSelectionURL",
"title": chrome.i18n.getMessage("actionSelectionURL"),
"type": "normal",
"contexts": ["action"]
});
chrome.contextMenus.create({
"id": "bearActionWholePage",
"title": chrome.i18n.getMessage("actionWholePage"),
"type": "normal",
"contexts": ["action"]
});
});
let runBearMenuAction = async (info, tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["script.js"]
}).then(() => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
// This will be serialized and run in the context of the current tab.
// All of the functions that are called must be defined in the script.js file.
// Chrome will wait for the promise to resolve before moving on.
func: async (menuItemId) => { return menu_action_capture(menuItemId); },
args: [ info.menuItemId ]
}).then((results) => {
let url = results[0].result;
chrome.tabs.update({ url: url });
});
});
};
chrome.contextMenus.onClicked.addListener(runBearMenuAction)
var open_xurl = function (url) {
chrome.tabs.update({ url: url });
};
script.js:
var get_html_selection = function() {
var ranges = window.getSelection();
if ( ranges.rangeCount == 0 ) { return ''; }
var content = ranges.getRangeAt(0).cloneContents();
var span = document.createElement('span');
span.appendChild(content);
return span.innerHTML;
}
var get_options = function(exclude_url) {
var settings = '';
chrome.storage.sync.get({
tags: '',
images: true,
appendurl: true
}, function(items) {
settings = '&tags=' + encodeURIComponent(items.tags);
if ( !items.images ) {
settings = settings + '&noimages=no';
}
if ( exclude_url || !items.appendurl ) {
settings = settings + '&appendurl=no';
}
return settings;
});
return settings;
}
var title_and_selection = function(options) {
var title = encodeURIComponent(document.title);
var url = encodeURIComponent(window.location.href);
var selection = encodeURIComponent(get_html_selection());
var xurl = 'bear://x-callback-url/create?type=html&title=' + title + '&text=' + selection + '&url=' + url + options;
return xurl;
};
var title_and_url = function(options) {
var title = encodeURIComponent(document.title);
var url = encodeURIComponent(window.location.href);
var xurl = 'bear://x-callback-url/create?title=' + title + '&text=' + url + options;
return xurl;
};
var grab_url = function(options) {
var url = encodeURIComponent(window.location.href);
var html = encodeURIComponent(document.documentElement.innerHTML);
var xurl = 'bear://x-callback-url/grab-url?url=' + url + '&html=' + html + options;
return xurl;
};
var selection_and_url = function(settings) {
var url = encodeURIComponent(window.location.href);
var selection = encodeURIComponent(get_html_selection());
var text = selection + encodeURIComponent("<br>\n<br>\n<br>") + url;
var xurl = 'bear://x-callback-url/create?type=html&url=' + url + '&text=' + text + settings;
return xurl;
};
var capture_page = async() => {
if (get_html_selection() === '') {
const opts = await chrome.storage.sync.get({
urlonly: false
});
if (!opts.urlonly) {
url = grab_url(get_options(false));
return url;
} else {
return title_and_url(get_options(false));
}
}
return title_and_selection(get_options(false));
}
var menu_action_capture = async (actionid) => {
const actions = {
'bearActionWholePage': async () => grab_url(get_options(false)),
'bearActionSelectionURL': async () => selection_and_url(get_options(true)),
'bearActionTitleURL': async () => title_and_url(get_options(false)),
'bearActionTitleSelection': async () => title_and_selection(get_options(false)),
};
const capture = actions[actionid];
return await capture();
}
Hope it will help you solve the issue in next version of the plugin.
Hello and thanks for your work here. I’ll check if we can improve the extension but I’d prefer to avoid the unnecessary popup.
Welcome. The version from a previous post has no popup at all. I just refactored your own code. It separates opening the bear app and capturing, so brave does not think it’s the site that opens the bear app.
I’m a developer, but no JS dev, so it may not be perfect.
1 Like