moving from permissions to optional_permissions: how to handle content_scripts? - google-chrome-extension

Originally posted this question here:
https://groups.google.com/a/chromium.org/d/msg/chromium-extensions/wbSpXvnO10A/nov36skmnQ0J
My extension has an optional feature that interacts with the user's gmail tab. We don't want to mention mail.google.com domains at all in the permission confirmation that the user sees when first installing the extension. So I moved that entry out of the manifest's permissions block and into the optional_permissions block. We also needed to use a content script tied to mail.google.com, but defining this in the manifest causes the 'mail.google.com' permission warning that is sppoking some users.
I've tried removing the content_script manifest block and using Programmatic Injection instead as describe here. http://developer.chrome.com/extensions/content_scripts.html#pi
However scripts injected that way are not content scripts and don't have access to the needed APIs (chrome.tabs, etc)
Is there some way to get the best of both worlds: use optional_permission, AND get the content scripts added to a matching URL, but only if the user has approved the optional permission?

It seems like you could create a background page, and call chrome.tabs.query against your optional origin to get a list of tabs that match that host. You can then call programmatic injection to the content script (chrome.tabs.executeScript). None of these require the "tabs" permission (many "tabs" functions don't require any special permission, and it intelligently lets you query for tabs whose origins match your optional permission)
You could call this every second or so to see if there are any new tabs for which you haven't yet called executeScript.
It would be nice if this were edge-triggered. See https://code.google.com/p/chromium/issues/detail?id=264704
You can actually get it to be mostly edge triggered by using chrome.tabs.onUpdated.addListener and simply trying to inject every time that is triggered (which will be every time a page loads in any tab, regardless of whether you have permission or not). You'll get a lot of errors in the background script's console when you don't have permission. It will be important to have your content script set a variable like _I_already_executed=true and check for its existence so that you're not injecting multiple times (this event gets triggered several times for each page load)

Now there's the contentScripts.register() API, which lets you programmatically register content scripts.
browser.contentScripts.register({
matches: ['https://mail.google.com/*'],
js: [{file: 'content.js'}]
});
This API is only available in Firefox but there's a Chrome polyfill you can use. The new scripts will work exactly like your regular content scripts in manifest.json.
For a more comprehensive solution you could look into my webext-domain-permission-toggle and webext-dynamic-content-scripts modules, which don't apply directly to your use case but can be helpful to who wants to drop the <all_urls> permission and inject content scripts on demand.

Related

Chrome Extension - Scrape any url, ignoring sandboxing and Content Security Policy?

I'd like to build a chrome extension that can make requests against any web page that the user has access to, even pages that are protected by Content Security Policies, preferably in the background (without having to have the page open in the browser).
So for example, I'd like to be able to:
request info from a page the user may be logged into, like Gmail
request info from a RSS/other pages
request info from pages on Facebook
Is this possible? It seems like I could have the extension open a new window, and a tab for every page I want to pull info from. Is this the only way this can work? I'd prefer to have this happen behind the scenes, without having to open a window.
CSP is not a problem as long as your manifest.json adds the URLs you want to process in permissions e.g. "*://*/" or "<all_urls>" will allow access to any site.
The solution, however, depends on how that page is built. If the server response contains all the info you need then you can simply make a direct request via XMLHttpRequest or fetch (more info) in the background script, parse it with DOMParser and extract the data. Otherwise you can try to run it in an iframe (you'll have to strip X-Frame-Options) or in an inactive/pinned tab and use a content script to extract the data. To access JavaScript variables of the page you'll need to add a DOM script so its code will run in page context.

Chrome extension - go to url without chrome.tabs

I have restrictions for including 'tabs' permission in manifest, exist any way to go to some url without using chrome.tabs in chrome extensions?
"tabs" permission used to be needed for all operations, but it's not needed anymore for a long time (since Chrome 7!).
The only thing that this permission grants is reading information about existing tabs.
Quoting the documentation itself (emphasis mine):
The majority of the chrome.tabs API can be used without declaring any
permission. However, the "tabs" permission is required in order to
populate the url, title, and favIconUrl properties of Tab.
So you can easily manipulate tabs (e.g. create or update them) without ever needing any permission.
Also, "activeTab" permission is worth a look.

How to detect when another Chrome Extension overrides the same page

An example would be if my extension overrides the newtab page and the user installs another extension that also overrides the newtab page. Currently, only one newtab extension shows up and it usually isn't mine.
What can I do to detect when such a conflict occurs and inform the user of such?
The management API doesn't tell me if the extensions override any pages, so I sadly can't use that.
This doesn't seem to be an exant feature of the API. I'd suggest you open a bug at http://crbug.com.
Failing that, you can perform the following nasty hack (which I haven't tested):
Have your new tab page send a message to your background page whenever it loads.
Listen for chrome.webNavigation.onBeforeNavigate events that deal with chrome://newtab:
chrome.webNavigation.onBeforeNavigate.addListener(function(details) {
/* send message */
}, { url: [{ urlEquals: 'chrome://newtab/' }] });
When webNavigation sees the browser load chrome://newtab but you don't see a message to your background page shortly afterwards, your new tab page is probably not being used. From there, you can send a notification, or open another tab/window with a notice.
Unfortunately, this requires the webNavigation permission, which is unfortunate if your extension doesn't otherwise need it. The warning that it carries ("This extension can access your tabs and browsing activity") might scare away some potential users, especially if there's no reason for it that is obvious to the user. (Then again, perhaps I'm being too optimistic about the security-conscientiousness of users.) If your extension currently uses the tabs API, then it already carries this notice anyway.

tabs permission or content script?

I'm writing an extension that needs to show a page action on amazon.com pages.
Would it be better to request the "tabs" permission or to inject a content script into amazon.com pages?
The tabs permission strikes me as using less resources (because it just checks the URL against a regex in the background script) but I think it's a scarier permission message ("access your tabs and browsing activity")?
Injecting a content script into amazon.com pages seems like it would take more resources it but would only need permission to amazon.com...
It is a generic question and answer depends on Client to Client. You have pointed out the + and - of each.
I suggest you to go for content scripts if your clients are particular about security and privacy, in this you are adding an extra load to pages(with content scripts and message passing) which may slow down the normal execution process.
I suggest you to go for tab permission, if you are all about performance. It is a native API, and executes in background page no extra load on tabs. Many extensions on web store does use tabs API, i dont think this would scare them as this is not new.
However, it is all about your target section of users.

Communicating between IFRAME and an entity

I couldn't make a request to a remote server using JavaScript in the onload function due to access is denied insanity. So, just to make CRM obey, I set up an IFRAME and connect that to a HTML page running my JavaScript. Now, provided that I get some values inside the script (run in an IFRAME) how can I communicate them to a method in the holding parent?
Not quite sure how to explain it more detailed so please feel free to ask.
The access is denied is the Same Origin Policy. You're going to run into the same problem from the IFRAME unless you're serving the page or just the script src from the same server you're subsequently trying to make the AJAX request to.
Assuming you are doing the latter then you just need to make sure you have unchecked the "Restrict cross-frame scripting" option on the IFRAME you added to the CRM form. From the IFRAME you will now have access to your function that you've defined at global scope on the parent CRM form via window.parent.yourfunctionNameHere(xyz).
postMessage sounds like it might fit.
window.postMessage, when called, causes a MessageEvent to be dispatched at the target window when any pending script that must be executed completes (e.g. remaining event handlers if window.postMessage is called from an event handler, previously-set pending timeouts, etc.). The MessageEvent has the type message, a data property which is set to the string value of the first argument provided to window.postMessage, an origin property corresponding to the origin of the main document in the window calling window.postMessage at the time window.postMessage was called, and a source property which is the window from which window.postMessage is called.
To use window.postMessage, an event listener must be attached:
// Internet Explorer
window.attachEvent('onmessage',receiveMessage);
// Opera/Mozilla/Webkit
window.addEventListener("message", receiveMessage, false);
And a receiveMessage function must be declared:
function receiveMessage(event) {
// do something with event.data;
}
The off-site iframe must also send events properly via postMessage:
<script>window.parent.postMessage('foo','*')</script>
Any window may access this method on any other window, at any time, regardless of the location of the document in the window, to send it a message. Consequently, any event listener used to receive messages must first check the identity of the sender of the message, using the origin and possibly source properties. This cannot be understated: Failure to check the origin and possibly source properties enables cross-site scripting attacks.
Source: https://developer.mozilla.org/en/DOM/window.postMessage
Recently I had the joy of connecting to a web service and retrieve some data. When that’s been achieved, I found myself sitting on the said data not exactly knowing where to put it.
To make the long story short, I used the following source code.
parent.window.Xrm.Page.data.entity.attributes
.get("new_Konrad").setValue("Viltersten");
Notable is the fact that in order to communicate with the parent form, the HTML file (where my JavaScript resided), needed to be placed as a web resource within the CRM structure. By other words, just by pointing to an external “http://some.where/some.thing” we can consume a service but won’t be able to convey the obtained information up the CRM server, at least not when developing a solution for the on-line version.

Resources