0

I'm trying to make a simple Chrome Extension where I will 'block' the content of the website Reddit by replacing it with a short text. This is what I have so far:

manifest.json

{
"manifest_version": 2,

"name": "BlockIt",
"description": "Block Reddit, increase productivity!",
"version": "1.0",

"browser_action": {
  "default_icon": "icon.png",
  "default_title": "BlockIt"
},

"permissions": [
  "storage", "tabs",
  "http://www.reddit.com/*"
],

"content_scripts": [
  {
    "matches": [ "*://reddit.com/*" ],
    "js": ["content-script.js"]
  }
] 
}

popup.html

<!doctype html>
<html>
  <head>
    <style>
      body {
        font-family: Verdana, Arial, Helvetica, Tahoma, sans-serif;
        background-color:#EFF7FF;
        margin: 5px 5px 5px 5px;
        width: 110px;
        height: 100%;
        text-align:center;
      }
    </style>
    <!--Scripts-->
      <script src="popup.js"></script>
  </head>
  <body>
    <h2>BlockIt!</h2>
    <div id="en"><label for="enable">Enable BlockIt</label> <input id="enable" type="checkbox" style="vertical-align:middle; position:relative; bottom: 1px;"/>
    </div>
  </body>
</html>

popup.js

document.addEventListener('DOMContentLoaded', function () {
    document.querySelector('#enable').addEventListener('change', changeHandler);
});

function changeHandler() {
    if (enable.checked) {
        chrome.storage.sync.set({ 'enable': true }, function () { });
    }
}

content-script.js

var content = document.getElementsByTagName("body");
var text = "BlockIt enabled (to disable, click on the icon).";
//TODO: replace content with text

I'm having two major issues right now: I'm not sure how I should go about in modifying the content of the webpage and replace it with the text above, and I'm not sure how to inject content-script.js when the checkbox in popup.html is checked. How do I go about in approaching this?

Edit: I've made the following change to my code:

content-script.js

chrome.storage.sync.get({ enable: false }, items=> {
    if (items.enable) {
        document.body.textContent = "BlockIt enabled (to disable, click on the icon).";
    }
});

It successfully changes the body of the webpage, but the issue now is that the content of popup.html changes to the same text as well. What seems to be the problem?

Edit 2: I've removed content-scrip.js in popup.html. The state of the checkbox still doesn't persist. It seems like such a simple solution but I can't seem to fix it. Any help would be appreciated.

coriandres
  • 1
  • 1
  • 4
  • That is really two distinct questions. For the first, we need to know what you are wanting to replace. Are you wanting to replace the entire `` (e.g. `document.body.textcontent=text;`), the entire ``, etc.? It might be easier to block `webRequests` to `redit.com`. – Makyen Nov 07 '16 at 01:54
  • @Makyen I would like to replace the enter `body` . Is it possible to display a custom message when blocking `webRequests`? – coriandres Nov 07 '16 at 02:13
  • Yes, you could display a custom message from blocking a `webRequest`. However, the way you would do so is different. You can redirect the request to a HTML file within your extension which would have the blocking message. – Makyen Nov 07 '16 at 02:27
  • Arg... There is a typo in my first comment. It should have said `document.body.textContent=text;` (note the capital `C` in `textContent`). – Makyen Nov 07 '16 at 02:29
  • FYI: From Stack Overflow's point of view, both your recent edits should be [new Questions](http://stackoverflow.com/questions/ask) (you can have a link in a new question back to this one for context). Also, nobody is notified when you make an edit to your question. If you want to inform specific people, you will need to put a comment on a Question or Answer they wrote, or add a comment that includes their username preceded by an `@` (e.g. `@Makyen`, for me). You can only specify one person via `@`/comment, but the original poster of the Question/Answer on which you comment is always notified. – Makyen Nov 07 '16 at 20:02
  • As to your most recent edit, if posted as a new Question, it would be closed as a duplicate. Your issue is not that the user's selection is not remembered from one time of opening your panel to the next, it is that you are not reading the stored information and updating the DOM to reflect the state of the checkbox. [My answer here](http://stackoverflow.com/a/40367226/3773011) answers a question from another user with an identical issue. You will need to change the DOM ID used, and the key used in the `storage.sync.get`. If you still have an issue, please leave a comment with `@Makyen`. – Makyen Nov 07 '16 at 20:03

2 Answers2

0

One possibility would be to use chrome.storage.sync.get() to get the enabled status at the time the content-script.j is injected into the page:

content-script.js:

chrome.storage.sync.get({enable:false}, items=> {
    if(items.enable) {
        document.body.textContent="BlockIt enabled (to disable, click on the icon).";
    }
});

This is not reversible for the current page. Given that your users will probably expect the block to be reversible without having to reload the page, you might want to consider a different method of accomplishing your block than to replace all of the content.

In addition, this will only replace the <body> for pages which are loaded after the block is enabled. It will not affect pages which are already loaded. You will probably want to use chrome.tabs.query() to get a list of tabs which are currently showing reddit.com. You will then need to inject the script again using chrome.tabs.executeScript().

Something like:

//This requires the "tabs" permission.
//Execute after you have stored the data to chrome.storage.sync (e.g. in the callback)
chrome.tabs.query({url:"*://reddit.com/*"},tabs=>{
    tabs.forEach(tab=>{
        chrome.tabs.executeScript(tab.id,{file:"content-script.js"});
    });
});
Makyen
  • 31,849
  • 12
  • 86
  • 121
  • thank you for your help. Just making sure, is `=>` supposed to be part of the name of the function? – coriandres Nov 07 '16 at 02:28
  • No, `items=>{}` is a shorter way to write `function(items) {}`. They are called [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). They are a new thing in JavaScript which was introduced in ES6. You should not, yet, use them for general JavaScript programming because the syntax is not supported by all browsers. However, for a Chrome extension we know that it will be running on Chrome, which does support the syntax. I used it because it is faster to write. I can change the answer to using `function(...){...}` if you would prefer. – Makyen Nov 07 '16 at 02:36
  • that's fine, you don't have to change the answer since you explained to me what it was. Thanks again :) – coriandres Nov 07 '16 at 02:39
  • I'm glad I was able to help. If you have any issues with actually putting this in your code, feel free to ask for clarification. – Makyen Nov 07 '16 at 02:41
  • @coriandres, I'm not sure why you using any of the methods in that answer. From what you have described in your question as what you desire, and the code you have shown, there is no need to use any of those methods. Given the *manifest.json* you have in your question you are already injecting your content script into the page. The methods in that answer are only if you need to interact directly with the scripts that are already running on the webpage, which you don't need to do. You may need to disable such scripts by changing the `` of the `document`, but not using those methods. – Makyen Nov 07 '16 at 15:46
  • @coriandres, That problem is caused by having the line `` in your *popup.html*. It should not be there. Remove that line. – Makyen Nov 07 '16 at 16:35
0

It's likely to be much more resource-efficient to block using webRequest API request redirection.

Let's say you have a page blocked.html that is shown in place of content.

Then, again, suppose that the extension state is stored in chrome.storage.sync, as in Mayken's answer. You'll need a synchronous cache of the value, because redirect must be synchronous.

// Background script
var enabled;

function cacheEnabled() {
  chrome.storage.sync.get({enable: false}, data => {
    enabled = data.enable;
  });
}

cacheEnabled();
chrome.storage.onChanged.addListener(cacheEnabled);

chrome.webRequest.onBeforeRequest.addListener(
  details => {
    if (enabled) {
      return { redirectUrl: chrome.runtime.getURL("blocked.html") };
    }
  },
  {
    urls: ["*://*.reddit.com/*"],
    types: ["main_frame", "sub_frame"]
  },
  ["blocking"]
);

This requires permissions "webRequest", "webRequestBlocking", "*://*.reddit.com/*".

Note: This is not an optimal solution: it's even better to register/unregister the listener depending on enabled rather than check it inside the listener (which should be as fast as possible). This is left as an exercise.

Xan
  • 74,770
  • 16
  • 179
  • 206