I am writing a Firefox add-on using the new SDK that does 5 very simple jobs. They are roughly related, so I would prefer a single add-on with a dropdown menu which I have implemented in a Panel.
I am working my way through the new SDK documentation, but can't find a direct solution. I find I can add a panel, but I cannot see how to manipulate the document in the current tab.
This is possible, isn't it? Using the global document doesn't work as presumably refers to the panel, or at least not to the document I am viewing.
So, how do I access the document from an add-on panel?
The short answer to your question is that you don't access web content from your main JavaScript code. You interact with the document (web content) in the browser's tab using Content Scripts. You can have the content script pass messages to your main script.
MDN summarizes the principals of using content scripts with the SDK as follows:
Content scripts can be one of the more confusing aspects of working
with the SDK, but you're very likely to have to use them. There are
five basic principles:
the add-on's main code, including "main.js" and other modules in "lib", can use the SDK high-level and low-level APIs, but
can't access web content directly
content scripts can't use the SDK's APIs (no access to globals exports, require) but can access web content
SDK APIs that use content scripts, like page-mod and tabs, provide functions that enable the add-on's main code to
load content scripts into web pages
content scripts can be loaded in as strings, but are more often stored as separate files under the add-on's "data" directory. jpm
doesn't make a "data" directory by default, so you must add it and put
your content scripts in there.
a message-passing API allows the main code and content scripts to communicate with each other
Exactly how you would do what you are wanting is unclear because you have not provided a clear description of what you are doing. However, it almost sounds like you could implement what you want using the context-menu. The context menu is one of the ways to attach a content script to the current tab and may provide you with the dropdown menu feel which you desired (although it is part of the context menu, not a dropdown menu).
Related
Here are links to specific parts of the electron docs:
Browser Window
Webview Tag
(Edit)
The use case I was thinking about is for example if I want to build a browser, would each webpage in a tab be an instance of a Webview or a BrowserWindow? Or for instance if I wanted to build a programming editor, and I wanted to display the rendered HTML page right next to the code, would that be a new BrowserWindow or a Webview?
I can understand why it would be confusing on which of these to host your content in given their similarities. They both start in separate processes and have many similar configurations. The key difference between the BrowserWindow and the webview is that the BrowserWindow is a window on the platform and a webview is an element on an webpage This may be a bit of an obvious, superficial distinction, but much of their differences and usages derive from it.
Much of the reason a webview exists is to allow for untrusted content to be embedded within your application. If you read up on the use cases for the webview, a lot of them point to the fact that the BrowserWindow, by default, has complete access to the Node APIs. Hosting untrusted content within it is handing that content significant access to your system and presents a security concern. The webview, however, does not have Node integration turned on by default, and so it shields your application, and the platform, from the hosted content.
However, this distinction is a bit of a red herring as Node integration can be disabled on BrowserWindow and can be enabled on a webview element. That is to say, you should be able to safely host untrusted content in a BrowserWindow by taking away access to Node and host trusted content in a webview and provide it access to Node.
The key to the webview is that it allows the embedding of untrusted content on a webpage/view in your application. If, within the same view/page, you would like to have some content that is trusted with full access to Node APIs and some content that is untrusted and given limited or no access to Node APIs then this may only be accomplished with the webview element. It is a way to segregate and lock down a piece of a webpage hosted in a BrowserWindow while allowing the rest of it to be open.
Aside from embedding untrusted content, the only other case I can think of for using webviews over BrowserWindows is if you want to open and view multiple separate processes in a single window. An application could choose to create 10 different windows for 10 different processes and have the platform handle layout, focus, etc or it could open 1 window with 10 webviews for 10 different processes and handle the layout, focus, etc itself within that window.
(Edit) To address the edit to the question:
For both of these cases I would suggest using a webview.
In the first scenario, a browser, you mentioned "tabs". There is no easy, cross-platform method that I know of for building a tabbed applications using multiple BrowserWindows because the windows are created by the native OS. You could, however, achieve this by creating a tabbed application within a single webpage, each tab containing a webview. In this case you would want to ensure Node integration is disabled on the webview (it should be by default), since loading content from the web is generally untrusted.
The second scenario, an editor with rendered HTML, is not as clear cut. You could use a webview, an iframe, or render the content directly to a div. Rendering directly to a div may be the best option for something like Markdown or small snippets of HTML so long as you do not need custom css or want to execute JavaScript. It otherwise makes sense to use a webview or an iframe. The difference being that a webview starts in a separate process and may have Node integration or flexed security whereas an iframe is loaded within the same process as the BrowserWindow and, I think, has locked down security. Regardless, to get a side-by-side look without another window you need to use a HTML element like a webview and not a BrowserWindow.
Electron 5 suggests using browserview/iframe instead of webview.
https://electronjs.org/docs/api/webview-tag
I am working on a Chrome extension that will add content to a particular set of pages. From my research, it sounds like what I want is a content script that will execute for the appropriate pages. I can specify the "appropriate pages" using the content_script.matches manifest.json field.
However, the problem I'm running into is that content scripts run in an isolated world, separate from the rest of your extension.
How I had envisioned my extension was a set of UI pages that would be embedded on the appropriate pages by the content script. The background page would contain the code for build the content of the UI pages. The background page, and by extension, the UI pages, would need access to the various Chrome APIs (e.g., local storage), as well as being able to make cross-domain requests to retrieve their data. However, it seems this is not possible, since the content scripts run in an isolated world, and don't have access to the Chrome APIs that I need.
Message passing allows a content script to send and receive data from the background page, but doesn't allow you to take a UI page and embed it on the current webpage.
I initially thought I was making some headway on this when I was able to make a jQuery AJAX request from my content script for an UI page, but that only gets me the HTML file itself. My UI pages depend on code to programmatically build the content--it's not just a static HTML page. And that "build the page" JavaScript code depends on Chrome APIs that are not available to the content script. So, if I just tried to make all my UI pages and JavaScript resources web_accessible_resources, I could inject them into the page but they wouldn't be able to run.
Which brings me to my question: how can a content script pull down, or embed, UI pages that can invoke code in the background page?
Tldr: you need to read about sending messages between content/background. Its in the docs and many samples.
From what I've been able to find, the architecture I was hoping for (as outlined in my question) is not possible in a Chrome Extension. Chrome's security model requires a different approach. Here's what worked for me.
Make your templates, JavaScript files, and anything that's part of your UI, web_accessible_resources.
Use your content script to load these resources and display them to the user at the appropriate times/locations.
(Almost) any calls to chrome.* API need to be done through your background page or event page. In my case, the "background page" is strictly JavaScript, there's no HTML.
Your content script, and UI, can send messages to your background/event page(s).
This model is not unlike the traditional client/server architecture of a web app. The "background page" is like your server, and your content script can send "messages" (think HTTP request) to the "background page" just like it might send a request to your server.
The background page, just like the server, has access to resources that the content script does not, e.g., the background page can use more of the chrome APIs.
This mental analogy helped me to "redesign" my app in a way that (so far) is working within the Chrome Extension security model. I had originally been thinking more along the lines of a traditional desktop app, where the entire app can do things like make cross domain requests or write to the file system. Chrome Extensions and Apps don't work this way, however.
What do these drop downs do? I assume they execute console commands in different contexts but I see weird, nonsensical choices when I click them.
Lets take Gmail as an example and start by looking at the first drop down:
List of frames
What you can see here are all frames that are embedded into the current page. Each of these frames is sandboxed. Being sandboxed means that there is no access from one sandbox to the other sandboxes. Scripts executed inside one frame can't access DOM or JS variables of the other frame. This is due to security reasons, we don't want a script inside an iframe to have access to the page it was embedded in (this would allow e.g. ads embedded into a blog to read what you are typing or what you keep in your cookies).
List of contexts
In the second drop down we have list of contexts for selected frame e.g. here is a list of contexts for Gmails <top frame>:
These are sandboxes created for each script that was injected by Chrome extension to the selected frame (these scripts are known as 'content scripts'). However, these are different from the frame sandboxes that I've mentioned before. Scripts injected by Chrome extensions have unlimited access to the DOM of the page they were injected to but operate in a separate JS execution context called isolated world (don't have access to "JavaScript variables or functions created by the page"). In this case it's more about making sure that scripts from different extensions don't interfere with each other than about the security:
Isolated worlds allow each content script to make changes to its
JavaScript environment without worrying about conflicting with the
page or with other content scripts. For example, a content script
could include JQuery v1 and the page could include JQuery v2, and they
wouldn't conflict with each other.
BTW Part of the url after chrome:// represents extension ID and using it you can figure out the name of the extension that injected the code (check 'Developer mode' on the chrome://extensions/ page).
Why do we need that?
You may want to see errors / console.* messages generated by an iframe.
While debugging your Chrome extension you may want to see errors / console.* messages that your injected script have produced.
You may want to execute some code from console in context different than the default one.
I'm developing a modal/popup system for my users to embed in their sites, along the lines of what KissInsights and Hello Bar (example here and here) do.
What is the best practice for architecting services like this? It looks like users embed a bit of JS but that code then inserts additional script tag.
I'm wondering how it communicates with the web service to get the user's content, etc.
TIA
You're right that usually it's simply a script that the customer embeds on their website. However, what comes after that is a bit more complicated matter.
1. Embed a script
The first step as said is to have a script on the target page.
Essentially this script is just a piece of JavaScript code. It's pretty similar to what you'd have on your own page.
This script should generate the content on the customer's page that you wish to display.
However, there are some things you need to take into account:
You can't use any libraries (or if you do, be very careful what you use): These may conflict with what is already on the page, and break the customer's site. You don't want to do that.
Never override anything, as overriding may break the customer's site: This includes event listeners, native object properties, whatever. For example, always use addEventListener or addEvent with events, because these allow you to have multiple listeners
You can't trust any styles: All styles of HTML elements you create must be inlined, because the customer's website may have its own CSS styling for them.
You can't add any CSS rules of your own: These may again break the customer's site.
These rules apply to any script or content you run directly on the customer site. If you create an iframe and display your content there, you can ignore these rules in any content that is inside the frame.
2. Process script on your server
Your embeddable script should usually be generated by a script on your server. This allows you to include logic such as choosing what to display based on parameters, or data from your application's database.
This can be written in any language you like.
Typically your script URL should include some kind of an identifier so that you know what to display. For example, you can use the ID to tell which customer's site it is or other things like that.
If your application requires users to log in, you can process this just like normal. The fact the server-side script is being called by the other website makes no difference.
Communication between the embedded script and your server or frames
There are a few tricks to this as well.
As you may know, XMLHttpRequest does not work across different domains, so you can't use that.
The simplest way to send data over from the other site would be to use an iframe and have the user submit a form inside the iframe (or run an XMLHttpRequest inside the frame, since the iframe's content resides on your own server so there is no cross domain communication)
If your embedded script displays content in an iframe dialog, you may need to be able to tell the script embedded on the customer site when to close the iframe. This can be achieved for example by using window.postMessage
For postMessage, see http://ejohn.org/blog/cross-window-messaging/
For cross-domain communication, see http://softwareas.com/cross-domain-communication-with-iframes
You could take a look here - it's an example of an API created using my JsApiToolkit, a framework for allowing service providers to easily create and distribute Facebook Connect-like tools to third-party sites.
The library is built on top of easyXDM for Cross Domain Messaging, and facilitates interaction via modal dialogs or via popups.
The code and the readme should be sufficient to explain how things fit together (it's really not too complicated once you abstract away things like the XDM).
About the embedding itself; you can do this directly, but most services use a 'bootstrapping' script that can easily be updated to point to the real files - this small file could be served with a cache pragma that would ensure that it was not cached for too long, while the injected files could be served as long living files.
This way you only incur the overhead of re-downloading the bootstrapper instead of the entire set of scripts.
Best practice is to put as little code as possible into your code snippet, so you don't ever have to ask the users to update their code. For instance:
<script type="text/javascript" src="http://your.site.com/somecode.js"></script>
Works fine if the author will embed it inside their page. Otherwise, if you need a bookmarklet, you can use this code to load your script on any page:
javascript:(function(){
var e=document.createElement('script');
e.setAttribute('language','javascript');
e.setAttribute('src','http://your.site.com/somecode.js');
document.head.appendChild(e);
})();
Now all your code will live at the above referenced URI, and whenever their page is loaded, a fresh copy of your code will be downloaded and executed. (not taking caching settings into account)
From that script, just make sure that you don't clobber namespaces, and check if a library exists before loading another. Use the safe jQuery object instead of $ if you are using that. And if you want to load more external content (like jQuery, UI stuff, etc.) use the onload handler to detect when they are fully loaded. For example:
function jsLoad(loc, callback){
var e=document.createElement('script');
e.setAttribute('language','javascript');
e.setAttribute('src',loc);
if (callback) e.onload = callback;
document.head.appendChild(e);
}
Then you can simply call this function to load any js file, and execute a callback function.
jsLoad('http://link.to/some.js', function(){
// do some stuff
});
Now, a tricky way to communicate with your domain to retrieve data is to use javascript as the transport. For instance:
jsLoad('http://link.to/someother.js?data=xy&callback=getSome', function(){
var yourData = getSome();
});
Your server will have to dynamically process that route, and return some javascript that has a "getSome" function that does what you want it to. For instance:
function getSome(){
return {'some':'data','more':'data'};
}
That will pretty effectively allow you to communicate with your server and process data from anywhere your server can get it.
You can serve a dynamically generated (use for example PHP or Ruby on Rails) to generate this file on each request) JS file from your server that is imported from the customers web site like this:
<script type="text/javascript" src="//www.yourserver.com/dynamic.js"></script>
Then you need to provide a way for your customer to decide what they want the modal/popup to contain (e.g. text, graphics, links etc.). Either you create a simple CMS or you do it manually for each customer.
Your server can see where each request for the JS file is coming from and provide different JS code based on that. The JS code can for example insert HTML code into your customers web site that creates a bar at the top with some text and a link.
If you want to access your customers visitors info you probably need to either read it from the HTML code, make your customers provide the information you want in a specific way or figure out a different way to access it from each customers web server.
I'm developing a Google Chrome Extension and I'm wondering what is the Chrome equivilant of window.content.document?
Looks like crome extensions depend on script injection if you want to interact with pages:
http://code.google.com/chrome/extensions/content_scripts.html
You can inject code or .js files.
For extension <-> DOM communication:
http://code.google.com/chrome/extensions/content_scripts.html#host-page-communication
To access windows: http://code.google.com/chrome/extensions/windows.html
Lets just say there's no direct access to DOM from Extension as far as I can see.
There is no window.content in Chrome right now (August 2014)
but window.document works just as well.
Like BGerrissen said, there is no direct access capability across different windows.
It is a restriction that different domain script cannot directly access each other.
As an extension, the main script itself is in a standalone chrome process.
You can put many functions, computations, preference code in this background.html.
And inject small portion of JS code into the content of other tab or window to manipulate the HTML content. And there communications to each other can be achieved by using postMessage and message handler.
Yes, BGerrissen it correct. You need to communicate with web pages via a shared DOM using content scripts.
It's quite tricky, requiring you to register events and listeners and bind data to a DOM element that both scripts can access.
http://code.google.com/chrome/extensions/content_scripts.html#host-page-communication
There's an example in Google Chrome labs where an extension script makes a web page red:
http://code.google.com/chrome/extensions/samples.html#ede3c47b7757245be42ec33fd5ca63df4b490066