4

What I have so far:

manifest.json

{
  "name": "Append Test Text",
  "description": "Add test123 to body",
  "version": "1.0",
  "permissions": [
    "activeTab"
  ],
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "browser_action": {
    "default_title": "Append Test Text"
  },
  "manifest_version": 2,
  "permissions": [
    "https://*/*",
    "http://*/*",
    "tabs"
  ]
}

background.js

chrome.browserAction.onClicked.addListener(function(tab) {
  chrome.tabs.executeScript({
    code: 'var div=document.createElement("div"); document.body.appendChild(div); div.innerText="test123";'
  });
});

What it does: Upon clicking the chrome extension icon, it adds <div>test123</div> to the <body></body> of any given page.


What I Would Like To Achieve:

Alike the bit.ly Chrome extension, I would like to append a lot of content to the DOM. Upon clicking on the extension icon, I would like an overlay element to be added tot he <body></body> along with a sidebar where I can add jQuery tab switches.

enter image description here

As you can see, I've just taken this picture whilst creating this question for StackOverflow.

Questions:

  1. Regarding my current progress and/or any scripts you may submit with your answer; How can I give this appended element an ID and/or class name and check whether this is already present on the DOM before adding it over and over again upon clicking the icon.
  2. How can I append a lot of content to the page to reside within my sidebar. jQuery is present within the site I am creating this extension to be used on. Am I able to create a standard HTML file which is then fetched and then programmatic injected into the DOM which I can use jQuery scripts on for the likes of switching tabs (not Chrome tabs, tabs within the injected content).
Xan
  • 74,770
  • 16
  • 179
  • 206
Tyler
  • 854
  • 1
  • 10
  • 26
  • 1
    By the way, code you presented so far does not require `tabs` permission. It's only needed to read URLs of current tabs. And even for that consider `"activeTab"` permission. – Xan Nov 23 '16 at 11:07

2 Answers2

3

Why not take a look at how bit.ly extension does it?

Looking at the code, bit.ly appends a fixed position, 100% width/height iframe to the page that contains an "app" page from the package. Clicking again removes the iframe. There's a bit of special code to work with frameset pages (which I won't comment on here), but other than that that's the general idea.

Part of the page is half-transparent, which gives the illusion of a sidebar, but it does indeed cover the whole page. This is the easiest way to do it, since otherwise you risk breaking the page's layout and there is no general "magic" solution that works everywhere to have your content side by side.

// Injection
var iframe = document.createElement("iframe");
iframe.id = "my-awesome-extension-iframe";
iframe.style.width = "100%";
/* ..more styling like that.. */
iframe.src = chrome.runtime.getURL('my_ui.html');
document.body.appendChild(iframe);

// Removal
var iframe = document.getElementById("my-awesome-extension-iframe");
if (iframe) {
  iframe.parentNode.removeChild(iframe);
}

To have full control over the looks of your UI, injecting a frame is preferable, as the page's own CSS and scripts won't "bleed" into your context. Whatever libraries you want, you can include there as you would in a normal page.

If you vehemently object to the idea of using a frame, you can try and inject your UI directly into the page - but beware interfering/incompatible code, restrictive CSP and CSS that bleeds through. This question is relevant: How to really isolate stylesheets in the Google Chrome extension?

You could make the above snippets as separate files, and use executeScript with the file attribute to inject them. They do not require any libraries like jQuery.

Note that code in that frame will have the same level of privilege as content script code - if you need APIs unavailable in content scripts, you'll need to message the background to do it. You'll also need to list the page itself, and all of its resources, in web_accessible_resources.

Xan
  • 74,770
  • 16
  • 179
  • 206
  • I had looked over the bit.ly a little however it has a file which has 42,000 lines within and I just did not know what I was looking for at all. So within `my_ui.html` I can basically create a website I am used to creating? Also, my understanding is that using an iframe on a site with is not local to their server means it cannot interact with the page, correct jme if I am wrong as extension work is new to me. Thank you for your very useful suggestion! +R – Tyler Nov 23 '16 at 11:20
  • 2
    Those 42000 lines are mostly code of the UI itself - and the code that injects/removes the UI is not that big. Analyzing extensions should start at the manifest, then background script, looking for specific `chrome.*` calls. – Xan Nov 23 '16 at 11:22
  • 2
    _"Also, my understanding is that using an iframe on a site with is not local to their server means it cannot interact with the page, correct jme if I am wrong as extension work is new to me."_ That would be correct. You can, however, have a content script that would serve as an intermediary, communicating either through the background page with Messaging or with `postMessage` directly. You have to understand that you asked a very broad question (which is bad), so I provided only an overview answer. If you have followup questions, ask them separately as new questions. – Xan Nov 23 '16 at 11:24
  • 2
    _"So within my_ui.html I can basically create a website I am used to creating?"_ To some extent, yes. It's just an HTML page. You just need to whitelist local resources you use in it in `web_accessible_resources`. – Xan Nov 23 '16 at 11:25
  • I looked for those `chrome.*` but evidently underlooked. I am starting to think that I may need to create a new question, a bit of advice prior would be useful please. So I'm working on my UI at the moment however I wish this extension to be able to go to pages, trigger click(s) and obtain information frequently. This cannot be done using this iFrame UI or the [Frederik.L answers](http://stackoverflow.com/a/40757823/6410837) method, can it? – Tyler Nov 23 '16 at 11:27
  • _whitelist local resources you use in it in _ I think this is pretty much a really reasonable answer, however could you kindly also explain how I would do this please? – Tyler Nov 23 '16 at 11:30
  • The iframe'd page wouldn't have access to the embedding page. But you can create an intermediary that can (a content script). Note one important detail: you can't interact with the page as long as the 100% width/height iframe is over it. Frederik.L's approach means your UI will coexist with the page - question is, where would the code reside. Normally, in the content script context, which can access the page. But then your UI can run into site-specific problems. See question I linked about isolation. – Xan Nov 23 '16 at 11:32
  • Hopefully this is the last question as I've taken up quite a bit of your time which I am extremely grateful for; `background.js` currently only works upon clicking the extension tab which is how I wish for things to work. How do I modify this to call for your or any other script as I understand your code but not how to correctly execute it. – Tyler Nov 23 '16 at 12:05
0

Dynamic DOM elements may be the way to go if you are adding simple user controls inside a layout. However, if you want to inject a full layout it can be a pain. jQuery may come handy with its function load.

You could have the layout of your sidebar in a static HTML file and have it loaded with jQuery in a given container. Something like this should do it, assuming an id="myContainer":

jQuery

$('#myContainer').load('layout.html')

To answer the first question, to give a specific ID to a dynamically created element, you only have to treat it like a DOM element and set an id to it.

JavaScript

var myElement = document.createElement('div');
myElement.id = 'myContainer';
myElement.className = 'kitten-background jumbo button';
// ...
// other properties you may need
// ...
document.body.appendChild(myElement);

For the second question, to check if an element exists you may as well use jQuery again. If a selector matches something, its length attribute will be greater than zero so that's a clean validation to check that $('#myContainer').length == 0 before inserting.

Good luck in what you are trying to achieve and have fun!

Frederik.L
  • 5,522
  • 2
  • 29
  • 41
  • Hiya Frederik.L, what is in your answer so far I completely understand and I have used it all on my sites I'm in development of, however I do not understand how to implement this within my `background.js` file. Would it be at all possible for you to amend your answer with a very small jQuery based example which does not have to follow suit to any specifics I'm looking to achieve? Thank you and +R – Tyler Nov 23 '16 at 09:33
  • answer is not correct in the context of chrome extensions where it will not work as its not aware of context (popup, content, background). see Xan answer. – Zig Mandel Nov 23 '16 at 12:40
  • I was assuming indeed that the context was accessible. For that, I usually append a script tag inside the document. – Frederik.L Nov 23 '16 at 12:55
  • If we need a contextual script without actual access to context, we are probably getting in trouble everytime we add a line. A clean way that I found for that is to inject a script tag which refers to a script in the extension scope. – Frederik.L Nov 23 '16 at 13:04