<commit_before>/* User click in the addon icon */
browser.browserAction.onClicked.addListener(() => {
  browser.tabs.create({
     'url': './public/config.html'
   });
});

// when the used add or delete words, update all the lists (usually called by the script in the page)
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action == 'get_list') {
    browser.storage.local.get('list_words').then((data) => {
      if (data.list_words)
        sendResponse(data.list_words);
      else
        sendResponse({});
    });

    // tells the browser to wait async call from sendResponse
    return true;
  }
});

// @see: https://stackoverflow.com/a/2736070
function isObjectsEqual(y, x) {
  for (var p in y) {
      if(typeof(y[p]) !== typeof(x[p])) return false;
      if((y[p]===null) !== (x[p]===null)) return false;
      switch (typeof(y[p])) {
          case 'undefined':
              if (typeof(x[p]) != 'undefined') return false;
              break;
          case 'object':
              if(y[p]!==null && x[p]!==null && (y[p].constructor.toString() !== x[p].constructor.toString() || !isObjectsEqual(y[p], x[p]))) return false;
              break;
          case 'function':
              if (p != 'isObjectsEqual' && y[p].toString() != x[p].toString()) return false;
              break;
          default:
              if (y[p] !== x[p]) return false;
      }
  }

  if (typeof(y) == 'object') {
    return Object.keys(y).length === Object.keys(x).length;
  }

  return true;
}

// normalize how the notifications will be created in the addon
function displayNotification(msg) {
  return browser.notifications.create('', {
    'type': 'basic',
    'iconUrl': browser.extension.getURL('icon.png'),
    'title': 'TextFast Notification',
    'message': msg
  });
}

/* sync words when initialize addon */
browser.storage.sync.get('list_words').then(words => {
  words = words.list_words;
  // empty
  if (Object.keys(words).length === 0) {
    // set local keys to sync
    browser.storage.local.get('list_words').then(local_words => {
      // just normalize, I dunno why they don't give me the obj directly
      local_words = local_words.list_words;

      if (Object.keys(local_words).length !== 0)
        browser.storage.sync.set({'list_words': local_words})
        .then(() => displayNotification('We synced the cloud with the new changes'));
    });
  }
  else {
    // use sync as local words
    browser.storage.local.get('list_words').then(local_words => {
      local_words = local_words.list_words;

      if (!(isObjectsEqual(words, local_words) && isObjectsEqual(local_words, words)))
        browser.storage.local.set({'list_words': words})
        .then(() => displayNotification('We synced yours shortcuts with the one in the cloud.'));
    });
  }
});

/* keep words synced between browsers or it should do that */
browser.storage.onChanged.addListener((changes, areaName) => {
  switch(areaName) {
    case 'local':
      // check if the event is caused by the update below
      browser.storage.sync.get('list_words').then(sync_words => {
        if (!isObjectsEqual(changes.list_words.newValue, sync_words.list_words)) {
          // it's different, update sync
          browser.storage.sync.set({'list_words': changes.list_words.newValue})
          .then(() => displayNotification('Shortcuts synced to cloud'));
        }
      });
      break;

    case 'sync':
      // check if the event is caused by the update above
      browser.storage.local.get('list_words').then(local_words => {
        if (!isObjectsEqual(changes.list_words.newValue, local_words.list_words)) {
          // it's different, update local
          browser.storage.local.set({'list_words': changes.list_words.newValue})
          .then(() => displayNotification('Cloud shortcuts synced to local'));
        }
      });
      break;
  }
});
<commit_msg>Fixes bug in sync when it is the first time of the addon syncing<commit_after>/* User click in the addon icon */
browser.browserAction.onClicked.addListener(() => {
  browser.tabs.create({
     'url': './public/config.html'
   });
});

// when the used add or delete words, update all the lists (usually called by the script in the page)
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action == 'get_list') {
    browser.storage.local.get('list_words').then((data) => {
      if (data.list_words)
        sendResponse(data.list_words);
      else
        sendResponse({});
    });

    // tells the browser to wait async call from sendResponse
    return true;
  }
});

// @see: https://stackoverflow.com/a/2736070
function isObjectsEqual(y, x) {
  for (var p in y) {
      if(typeof(y[p]) !== typeof(x[p])) return false;
      if((y[p]===null) !== (x[p]===null)) return false;
      switch (typeof(y[p])) {
          case 'undefined':
              if (typeof(x[p]) != 'undefined') return false;
              break;
          case 'object':
              if(y[p]!==null && x[p]!==null && (y[p].constructor.toString() !== x[p].constructor.toString() || !isObjectsEqual(y[p], x[p]))) return false;
              break;
          case 'function':
              if (p != 'isObjectsEqual' && y[p].toString() != x[p].toString()) return false;
              break;
          default:
              if (y[p] !== x[p]) return false;
      }
  }

  if (typeof(y) == 'object') {
    return Object.keys(y).length === Object.keys(x).length;
  }

  return true;
}

// normalize how the notifications will be created in the addon
function displayNotification(msg) {
  return browser.notifications.create('', {
    'type': 'basic',
    'iconUrl': browser.extension.getURL('icon.png'),
    'title': 'TextFast Notification',
    'message': msg
  });
}

/* sync words when initialize addon */
browser.storage.sync.get('list_words').then(words => {
  words = words.list_words;
  // empty
  if (words && Object.keys(words).length === 0) {
    // set local keys to sync
    browser.storage.local.get('list_words').then(local_words => {
      // just normalize, I dunno why they don't give me the obj directly
      local_words = local_words.list_words;

      if (Object.keys(local_words).length !== 0)
        browser.storage.sync.set({'list_words': local_words})
        .then(() => displayNotification('We synced the cloud with the new changes'));
    });
  }
  else {
    // use sync as local words
    browser.storage.local.get('list_words').then(local_words => {
      local_words = local_words.list_words;

      if (!(isObjectsEqual(words, local_words) && isObjectsEqual(local_words, words)))
        browser.storage.local.set({'list_words': words})
        .then(() => displayNotification('We synced yours shortcuts with the one in the cloud.'));
    });
  }
});

/* keep words synced between browsers or it should do that */
browser.storage.onChanged.addListener((changes, areaName) => {
  switch(areaName) {
    case 'local':
      // check if the event is caused by the update below
      browser.storage.sync.get('list_words').then(sync_words => {
        sync_words.list_words = sync_words.list_words || {};

        if (!isObjectsEqual(changes.list_words.newValue, sync_words.list_words)) {
          // it's different, update sync
          browser.storage.sync.set({'list_words': changes.list_words.newValue})
          .then(() => displayNotification('Shortcuts synced to cloud'));
        }
      });
      break;

    case 'sync':
      // check if the event is caused by the update above
      browser.storage.local.get('list_words').then(local_words => {
        local_words.list_words = local_words.list_words || {};

        if (!isObjectsEqual(changes.list_words.newValue, local_words.list_words)) {
          // it's different, update local
          browser.storage.local.set({'list_words': changes.list_words.newValue})
          .then(() => displayNotification('Cloud shortcuts synced to local'));
        }
      });
      break;
  }
});
<|endoftext|>


<commit_before>'use strict';

var CONSTANTS = (function() {
     const privateVars = {
         'URL': 'http://www.hardmob.com.br/promocoes/',
         'INTERVAL': {
             'MIN_MINUTE': 20, // 20 min
             'MAX_MINUTE': 59, // 59 mins
             'MIN_HOUR': 1, //1 hour
             'MAX_HOUR': 20, //20 hours
             'MIN_DAY': 1, // 1 day
             'MAX_DAY': 5, //5 days
         },
         'OPTIONS': {
            set_words: 'set_words',
            set_time_intverval: 'set_time_intverval',
            set_state_monitoring: 'set_state_monitoring'
        }
     };

    return {
        get: function(name) { return privateVars[name]; }
    };
})();

let ARRAY_WORDS;
let ID_INTERVAL;
let TIME_INTERVAL = {};
let SET_STATE_MONITORING;

chrome.storage.sync.get(function(obj){
    console.log("Load words"); //TODO: delete
    console.log('OBJ:', obj); //TODO: delete
    ARRAY_WORDS = obj.words;
    SET_STATE_MONITORING = obj.set_state_monitoring || true;
    TIME_INTERVAL.time = obj.time_interval.time || CONSTANTS.get('INTERVAL').MIN_HOUR;
    TIME_INTERVAL.type = obj.time_interval.type || 'HOUR';

    if(SET_STATE_MONITORING)
        StartOrStopMonitoring();
});

//execute options
var backgroundFunction = function(opt, val){
    var msg = {option: opt, values: val};

    console.log('MSG: ', msg); //TODO: delete

    switch(msg.option) {
        case CONSTANTS.get('OPTIONS').set_words:
            setWords(msg.values);
            break;
        case CONSTANTS.get('OPTIONS').set_time_intverval:
            setTimeInterval(msg.values.time, msg.values.type);
            break;
        case CONSTANTS.get('OPTIONS').set_state_monitoring:
            setStateMonitoring(msg.values);
            break;
    }
}

function setWords(words){
    const sanitizedWords = words.map(function(word){
        if(word !== undefined && word !== "" && typeof word === 'string')
            return word.trim().toLowerCase();
    });

    ARRAY_WORDS = new Set(sanitizedWords);

    //save words
    chrome.storage.local.remove('words');
    chrome.storage.sync.set({'words': sanitizedWords}, function(){
        console.log("set storage"); // TODO: delete callback
    });
}

function setTimeInterval(time, type){
    if(type !== 'MINUTE' && type !== 'HOUR' && type !== 'DAY')
        type = 'HOUR';

    if(type === 'MINUTE')
        time = time < CONSTANTS.get('INTERVAL').MIN_MINUTE ? CONSTANTS.get('INTERVAL').MIN_MINUTE : time; // < 20 min
    else if(type === 'HOUR')
        time = time < CONSTANTS.get('INTERVAL').MIN_HOUR ? CONSTANTS.get('INTERVAL').MIN_HOUR : time; // < 23 hour
    else if(time === 'DAY')
        time = time > CONSTANTS.get('INTERVAL').MIN_DAY ? CONSTANTS.get('INTERVAL').MIN_DAY : time; // > 5 days

    TIME_INTERVAL.time = time;
    TIME_INTERVAL.type= type;

    chrome.storage.sync.set({'time_interval': TIME_INTERVAL}, function(){
        console.log("set storage interval"); // TODO: delete callback
    });

    StartOrStopMonitoring();
}

function setStateMonitoring(val){
    if(typeof val !== 'boolean') return;

    SET_STATE_MONITORING = val;

    chrome.storage.sync.set({'set_state_monitoring': SET_STATE_MONITORING}, function(){
        console.log("set storage"); // TODO: delete callback
    });

    StartOrStopMonitoring();
}

function sendRequest(url, callback) {
    var req = new XMLHttpRequest();
    if (!req) return;

    const method = "GET";

    req.onreadystatechange = function () {
        if (req.readyState != 4) return;
        if (req.status != 200 && req.status != 304) {
            console.log('HTTP error: ' + req.status);
            return;
        }
        callback(req);
    }

    req.open(method, url, true);
    req.send();
}

function searchWords(text){
    let arrayThread = [];

    let elementHtml = document.createElement("html");
    elementHtml.innerHTML = text;
    let threads = elementHtml.querySelector("#threads");
    let threadsTitle = threads.querySelectorAll(".threadtitle");

    // Get title and link thread
    threadsTitle.forEach(function(elem, index){
        let thread = elem.querySelector("a.title");
        let threadLink = thread.href;
        let threadTitle = thread.textContent;

        // Check if have the words in titles
        ARRAY_WORDS.forEach(function(word){
            if(threadTitle.toLowerCase().search(word) != -1){
                let obj = {'title': threadTitle, 'link': threadLink};

                //to next thread, if duplicate
                for(let i=0; i < arrayThread.length; i++)
                    if(obj.link === arrayThread[i].link)
                        return;

                arrayThread.push(obj);
            }
        });
    });

    chrome.storage.sync.set({'threads': arrayThread}, function(){
        console.log("set storage threads"); // TODO: delete callback
    });

    console.log(arrayThread); // TODO: delete
}

function StartOrStopMonitoring(){
    if(!SET_STATE_MONITORING){
        clearInterval(ID_INTERVAL);
        return;
    }

    let interval;

    switch(TIME_INTERVAL.type){
        case 'MINUTE': interval = TIME_INTERVAL.time * 60*1000; break;
        case 'HOUR': interval = TIME_INTERVAL.time * 60*60*1000; break;
        case 'DAY': interval = TIME_INTERVAL.time * 60*60*24*1000; break;
    }

    ID_INTERVAL = setInterval(function(){
        sendRequest(CONSTANTS.get('URL'), function(req){
            searchWords(req.responseText);
        });
    }, interval);
}
<commit_msg>0.1.2: change minimum interval<commit_after>'use strict';

var CONSTANTS = (function() {
     const privateVars = {
         'URL': 'http://www.hardmob.com.br/promocoes/',
         'INTERVAL': {
             'MIN_MINUTE': 30, // 20 min
             'MAX_MINUTE': 59, // 59 mins
             'MIN_HOUR': 1, //1 hour
             'MAX_HOUR': 20, //20 hours
             'MIN_DAY': 1, // 1 day
             'MAX_DAY': 5, //5 days
         },
         'OPTIONS': {
            set_words: 'set_words',
            set_time_intverval: 'set_time_intverval',
            set_state_monitoring: 'set_state_monitoring'
        }
     };

    return {
        get: function(name) { return privateVars[name]; }
    };
})();

let ARRAY_WORDS;
let ID_INTERVAL;
let TIME_INTERVAL = {};
let SET_STATE_MONITORING;

chrome.storage.sync.get(function(obj){
    console.log("Load words"); //TODO: delete
    console.log('OBJ:', obj); //TODO: delete
    ARRAY_WORDS = obj.words;
    SET_STATE_MONITORING = obj.set_state_monitoring || true;
    TIME_INTERVAL.time = obj.time_interval.time || CONSTANTS.get('INTERVAL').MIN_HOUR;
    TIME_INTERVAL.type = obj.time_interval.type || 'HOUR';

    if(SET_STATE_MONITORING)
        StartOrStopMonitoring();
});

//execute options
var backgroundFunction = function(opt, val){
    var msg = {option: opt, values: val};

    console.log('MSG: ', msg); //TODO: delete

    switch(msg.option) {
        case CONSTANTS.get('OPTIONS').set_words:
            setWords(msg.values);
            break;
        case CONSTANTS.get('OPTIONS').set_time_intverval:
            setTimeInterval(msg.values.time, msg.values.type);
            break;
        case CONSTANTS.get('OPTIONS').set_state_monitoring:
            setStateMonitoring(msg.values);
            break;
    }
}

function setWords(words){
    const sanitizedWords = words.map(function(word){
        if(word !== undefined && word !== "" && typeof word === 'string')
            return word.trim().toLowerCase();
    });

    ARRAY_WORDS = new Set(sanitizedWords);

    //save words
    chrome.storage.local.remove('words');
    chrome.storage.sync.set({'words': sanitizedWords}, function(){
        console.log("set storage"); // TODO: delete callback
    });
}

function setTimeInterval(time, type){
    if(type !== 'MINUTE' && type !== 'HOUR' && type !== 'DAY')
        type = 'HOUR';

    if(type === 'MINUTE')
        time = time < CONSTANTS.get('INTERVAL').MIN_MINUTE ? CONSTANTS.get('INTERVAL').MIN_MINUTE : time; // < 20 min
    else if(type === 'HOUR')
        time = time < CONSTANTS.get('INTERVAL').MIN_HOUR ? CONSTANTS.get('INTERVAL').MIN_HOUR : time; // < 23 hour
    else if(time === 'DAY')
        time = time > CONSTANTS.get('INTERVAL').MIN_DAY ? CONSTANTS.get('INTERVAL').MIN_DAY : time; // > 5 days

    TIME_INTERVAL.time = time;
    TIME_INTERVAL.type= type;

    chrome.storage.sync.set({'time_interval': TIME_INTERVAL}, function(){
        console.log("set storage interval"); // TODO: delete callback
    });

    StartOrStopMonitoring();
}

function setStateMonitoring(val){
    if(typeof val !== 'boolean') return;

    SET_STATE_MONITORING = val;

    chrome.storage.sync.set({'set_state_monitoring': SET_STATE_MONITORING}, function(){
        console.log("set storage"); // TODO: delete callback
    });

    StartOrStopMonitoring();
}

function sendRequest(url, callback) {
    var req = new XMLHttpRequest();
    if (!req) return;

    const method = "GET";

    req.onreadystatechange = function () {
        if (req.readyState != 4) return;
        if (req.status != 200 && req.status != 304) {
            console.log('HTTP error: ' + req.status);
            return;
        }
        callback(req);
    }

    req.open(method, url, true);
    req.send();
}

function searchWords(text){
    let arrayThread = [];

    let elementHtml = document.createElement("html");
    elementHtml.innerHTML = text;
    let threads = elementHtml.querySelector("#threads");
    let threadsTitle = threads.querySelectorAll(".threadtitle");

    // Get title and link thread
    threadsTitle.forEach(function(elem, index){
        let thread = elem.querySelector("a.title");
        let threadLink = thread.href;
        let threadTitle = thread.textContent;

        // Check if have the words in titles
        ARRAY_WORDS.forEach(function(word){
            if(threadTitle.toLowerCase().search(word) != -1){
                let obj = {'title': threadTitle, 'link': threadLink};

                //to next thread, if duplicate
                for(let i=0; i < arrayThread.length; i++)
                    if(obj.link === arrayThread[i].link)
                        return;

                arrayThread.push(obj);
            }
        });
    });

    chrome.storage.sync.set({'threads': arrayThread}, function(){
        console.log("set storage threads"); // TODO: delete callback
    });

    console.log(arrayThread); // TODO: delete
}

function StartOrStopMonitoring(){
    if(!SET_STATE_MONITORING){
        clearInterval(ID_INTERVAL);
        return;
    }

    let interval;

    switch(TIME_INTERVAL.type){
        case 'MINUTE': interval = TIME_INTERVAL.time * 60*1000; break;
        case 'HOUR': interval = TIME_INTERVAL.time * 60*60*1000; break;
        case 'DAY': interval = TIME_INTERVAL.time * 60*60*24*1000; break;
    }

    ID_INTERVAL = setInterval(function(){
        sendRequest(CONSTANTS.get('URL'), function(req){
            searchWords(req.responseText);
        });
    }, interval);
}
<|endoftext|>


<commit_before>(function() {
  'use strict';
}());

var sessions = [];
var tabStatus = {};
var selectedTree = {};
// required JSON object structure for using with JIT and d3.js
/*
json = {
  "id" : , "node00",
  "name" : "name",
  "data" : {},
  "children" : [ array of child nodes ]
  }
}
*/

// Session Handler
// take in event commitData, direct it to the correct session
function sessionHandler(commitData) {
  var session = findSessionOf(commitData);

  createPageObject(session, commitData, function(page) {
    if (page.id == 1) {
      recordRoot(page);
    } else {
      recordChild(page);
    }
  });
}

// Session Finder
// does logic to determine which wikipedia 'session' the
// navigation event's commitData belongs to.
// creates new session if needed
// returns id (timestamp) and parentNode of session object
function findSessionOf(commitData) {
  var ret = { "id": "",
        "parentNode": "",
      };

  sessions.forEach(function(session) {
    if (session.tabs.indexOf(commitData.tabId) >= 0) {
      ret.id = session.id;
      ret.parentNode = tabStatus[commitData.tabId].id;
    }
    else if(commitData.parentId !== undefined) {
      var index = session.tabs.indexOf(commitData.parentId);
      if (index >= 0) {
        ret.id = session.id;
        ret.parentNode = tabStatus[commitData.parentId].id;
        session.tabs.push(commitData.tabId);
      }
    }
  });

  if (ret.id !== "") {
    return ret;
  }
  else {
    return createNewSession(commitData);
  }
}

// Create New Session
// make a new session object and assign it an ID from its timestamp
// return the id of the created session
function createNewSession(commitData) {
  var session = {   "id": Date.now(),
            "tabs": [ commitData.tabId ],
            "nodeIndex": 1,
        };
  sessions.push(session);
  return { "id": session.id, "parentNode": "" };
}

// Create Page Object
// create a new page object that will get inserted into
// the tree data structure
function createPageObject(session, commitData, callback) {
  sessions.forEach(function(activeSession) {
    if (activeSession.id == session.id) {
      var page = {  "id": activeSession.nodeIndex,
              "name": shortenURL(commitData.url),
              "data": {   "url": commitData.url,
                    "date": commitData.timeStamp,
                    "sessionId": session.id,
                    "tabId": commitData.tabId,
                    "parentId": session.parentNode,
                  },
              "children": [],
            };
      activeSession.nodeIndex += 1;
      callback(page);
    }
  });
}

function recordRoot(page) {
  // set the tabStatus of this tabId to the page content
  setTabStatus(page.data.tabId, page);
  // record new session and root node to localStorage
  localStorage.setItem(page.data.sessionId, JSON.stringify(page));
}

function recordChild(page) {
  // set the tabStatus of this tabId to the page content
  setTabStatus(page.data.tabId, page);
  // retrieve the tree for this session from localStorage
  var tree = JSON.parse(localStorage.getItem(page.data.sessionId));
  // find the parent node
  var parent = findNode(tree, page.data.parentId);
  // add this node as a child
  parent.children.push(page);
  // record the modified tree to localStorage
  localStorage.setItem(page.data.sessionId, JSON.stringify(tree));
}

// Find Node
// recursively look through a JSON tree for the specified node and return it
function findNode(tree, nodeId) {
   if (tree.id === nodeId) return tree;

   var result;
   var len = tree.children.length;
   for (var i = 0; i < len; i++) {
    result = findNode(tree.children[i], nodeId);
    if (result !== undefined) return result;
   }
}

// Find Node By URL
// recursively look through a JSON tree for first node with a matching url
// TODO: improve this by comparing URLs AND tabIds for a more precise match
function findNodeByURLAndName(tree, url, name) {
  if (tree.data.url == url && tree.name != name) return tree;

  var result;
  var len = tree.children.length;
  for (var i = 0; i < len; i++) {
    result = findNodeByURLAndName(tree.children[i], url, name);
    if (result !== undefined) return result;
  }
}

// Set TabStatus
// function to update the tab status object with the page contents of tabId
function setTabStatus(tabId, page) {
  tabStatus[tabId] = page;
}

// Update Name
// function to find the session and node of a page
// and to update the node's name with the proper document name
function updateName(tab, name) {
  sessions.forEach(function(s) {
     if ( s.tabs.indexOf(tab.id) >= 0 ) {
      // i know the session id to go find this node in
      var tree = JSON.parse(localStorage[s.id]);
      var node = findNodeByURLAndName(tree, tab.url, name);
      if (node !== undefined) {
        node.name = name;
      }
      localStorage.setItem(s.id, JSON.stringify(tree));
    }
  });
}

// deletes a specific key from localStorage
function deleteHistoryItem(key) {
  if (localStorage.length == 1) {
    clearHistory();
  }
  else localStorage.removeItem(key);
}

// clears all history including current in-memory tree
function clearHistory() {
  tabStatus = {};
  sessions = [];
  selectedTree = {};
  localStorage.clear();
}

// trim url to just the page name from the url
// this returns a placeholder value for node.name until the
// loaded page DOM can be queried for its real name
function shortenURL(url) {
  return /[^/]*$/.exec(url)[0];
}

// message listener
chrome.runtime.onMessage.addListener(function(request, sender, response) {
  switch (request.payload) {
    case "status":
      if (sessions.length === 0) response();
      else response("active");
    break;

    case "set":
      var recent = 0;
      if (request.key) {
        selectedTree = JSON.parse(localStorage.getItem(request.key));
      }
      else if (sessions.length === 0) selectedTree = {};
      else {
        if (localStorage.length > 0)
          for (var key in localStorage) {
            if (key > recent) recent = key;
          }
          selectedTree = JSON.parse(localStorage.getItem(recent));
      }
      response(selectedTree);
    break;

    // tree page requesting json tree object
    case "load":
      response(selectedTree);
    break;

    case "update":
      updateName(sender.tab, request.name);
    break;

    // history page requesting localStorage object
    case "localStorage":
      response(localStorage);
    break;

    // history page requesting to remove a specific tree by key
    case "delete":
      deleteHistoryItem(request.key);
      response("History Item Deleted");
    break;

    // history page requesting to clear all history
    case "clear":
      clearHistory();
      response("History Cleared");
    break;
  }
});

// Navigation Event Listener
chrome.webNavigation.onCommitted.addListener(function(details) {
  // check that this event is a link or typed address
  // filter out back, reload, etc events
  if (details.transitionType == "link" ||
    details.transitionType == "typed" ||
    details.transitionType == "form_submit" ) {

    // get the parent tab id of the tab that the nav event occurs in
    // and add it as an additional key in commitData
    var commitData = details;
    chrome.tabs.get(details.tabId, function(tab) {
      commitData.parentId = tab.openerTabId;
      sessionHandler(commitData);
    });
  }
}, { url: [ { urlContains: ".wikipedia.org/wiki" },
      { urlContains: ".wiktionary.org/wiki"}]});

// Listener for when the user clicks on the Wikimapper button
chrome.browserAction.onClicked.addListener(function() {
  chrome.tabs.create({'url': chrome.extension.getURL('html/index.html')}, function() {
  });
});

// Listener for first install
chrome.runtime.onInstalled.addListener(function(details) {
  if(details.reason == "install") {
    chrome.tabs.create({'url': chrome.extension.getURL('html/index.html')});
  }
});
<commit_msg>WIP #64 back/forward button friendly<commit_after>(function() {
  'use strict';
}());

var sessions = [];
var tabStatus = {};
var selectedTree = {};
// required JSON object structure for using with JIT and d3.js
/*
json = {
  "id" : , "node00",
  "name" : "name",
  "data" : {},
  "children" : [ array of child nodes ]
  }
}
*/

function eventFilter(details) {
  var triggers = ['link','typed','form_submit'];
  var commitData = details;
  // handle forward and back button (they are unfortunately the same qualifier)
  if (details.transitionQualifiers.indexOf('forward_back') >= 0) {
    // back button
    if (details.url === tabStatus[details.tabId].parent.data.url) {
      var backPage = tabStatus[details.tabId].parent;
      commitData = backPage;
      commitData.forwardId = tabStatus[details.tabId].id;
      tabStatus[details.tabId] = backPage;
    }
    // forward button
    else {
      commitData.id = tabStatus[details.tabId].forwardId;
      commitData.parent = tabStatus[details.tabId];
      commitData.children = [];
      tabStatus[details.tabId] = commitData;
    }
  }

  // handle navigation events that match triggers
  else if (triggers.indexOf(details.transitionType) >= 0) {
    // get the parent tab id of the tab that the nav event occurs in
    // and add it as an additional key in commitData
    chrome.tabs.get(details.tabId, function(tab) {
      commitData.parentId = tab.openerTabId;
      sessionHandler(commitData);
    });
  }
  console.log(tabStatus);
}

// Session Handler
// take in event commitData, direct it to the correct session
function sessionHandler(commitData) {
  var session = findSessionOf(commitData);

  createPageObject(session, commitData, function(page) {
    if (page.id == 1) {
      recordRoot(page);
    } else {
      recordChild(page);
    }
  });
}

// Session Finder
// does logic to determine which wikipedia 'session' the
// navigation event's commitData belongs to.
// creates new session if needed
// returns id (timestamp) and parentNode of session object
function findSessionOf(commitData) {
  var ret = {
    "id": "",
    "parentNode": "",
  };

  sessions.forEach(function(session) {
    if (session.tabs.indexOf(commitData.tabId) >= 0) {
      ret.id = session.id;
      ret.parentNode = tabStatus[commitData.tabId].id;
    }
    else if(commitData.parentId !== undefined) {
      var index = session.tabs.indexOf(commitData.parentId);
      if (index >= 0) {
        ret.id = session.id;
        ret.parentNode = tabStatus[commitData.parentId].id;
        session.tabs.push(commitData.tabId);
      }
    }
  });

  if (ret.id !== "") {
    return ret;
  }
  else {
    return createNewSession(commitData);
  }
}

// Create New Session
// make a new session object and assign it an ID from its timestamp
// return the id of the created session
function createNewSession(commitData) {
  var session = {
    "id": Date.now(),
    "tabs": [ commitData.tabId ],
    "nodeIndex": 1,
  };
  sessions.push(session);
  return { "id": session.id, "parentNode": "" };
}

// Create Page Object
// create a new page object that will get inserted into
// the tree data structure
function createPageObject(session, commitData, callback) {
  sessions.forEach(function(activeSession) {
    if (activeSession.id == session.id) {
      var page = {
        "id": activeSession.nodeIndex,
        "name": shortenURL(commitData.url),
        "data": {
          "url": commitData.url,
          "date": commitData.timeStamp,
          "sessionId": session.id,
          "tabId": commitData.tabId,
          "parentId": session.parentNode,
        },
        "children": [],
      };
      activeSession.nodeIndex += 1;
      callback(page);
    }
  });
}

function recordRoot(page) {
  // set the tabStatus of this tabId to the page content
  setTabStatus(page.data.tabId, page);
  // record new session and root node to localStorage
  localStorage.setItem(page.data.sessionId, JSON.stringify(page));
}

function recordChild(page) {
  // set the tabStatus of this tabId to the page content
  setTabStatus(page.data.tabId, page);
  // retrieve the tree for this session from localStorage
  var tree = JSON.parse(localStorage.getItem(page.data.sessionId));
  // find the parent node
  var parent = findNode(tree, page.data.parentId);
  // add this node as a child
  parent.children.push(page);
  // record the modified tree to localStorage
  localStorage.setItem(page.data.sessionId, JSON.stringify(tree));
}

// Find Node
// recursively look through a JSON tree for the specified node and return it
function findNode(tree, nodeId) {
   if (tree.id === nodeId) return tree;

   var result;
   var len = tree.children.length;
   for (var i = 0; i < len; i++) {
    result = findNode(tree.children[i], nodeId);
    if (result !== undefined) return result;
   }
}

// Find Node By URL
// recursively look through a JSON tree for first node with a matching url
// TODO: improve this by comparing URLs AND tabIds for a more precise match
function findNodeByURLAndName(tree, url, name) {
  if (tree.data.url == url && tree.name != name) return tree;

  var result;
  var len = tree.children.length;
  for (var i = 0; i < len; i++) {
    result = findNodeByURLAndName(tree.children[i], url, name);
    if (result !== undefined) return result;
  }
}

// Set TabStatus
// function to update the tab status object with the page contents of tabId
function setTabStatus(tabId, page) {
  if (tabStatus[tabId] !== undefined) {
    page.parent = tabStatus[tabId];
  }
  tabStatus[tabId] = page;
}

// Update Name
// function to find the session and node of a page
// and to update the node's name with the proper document name
function updateName(tab, name) {
  sessions.forEach(function(s) {
     if ( s.tabs.indexOf(tab.id) >= 0 ) {
      // i know the session id to go find this node in
      var tree = JSON.parse(localStorage[s.id]);
      var node = findNodeByURLAndName(tree, tab.url, name);
      if (node !== undefined) {
        node.name = name;
      }
      localStorage.setItem(s.id, JSON.stringify(tree));
    }
  });
}

// deletes a specific key from localStorage
function deleteHistoryItem(key) {
  if (localStorage.length == 1) {
    clearHistory();
  }
  else localStorage.removeItem(key);
}

// clears all history including current in-memory tree
function clearHistory() {
  tabStatus = {};
  sessions = [];
  selectedTree = {};
  localStorage.clear();
}

// trim url to just the page name from the url
// this returns a placeholder value for node.name until the
// loaded page DOM can be queried for its real name
function shortenURL(url) {
  return /[^/]*$/.exec(url)[0];
}

// message listener
chrome.runtime.onMessage.addListener(function(request, sender, response) {
  switch (request.payload) {
    case "status":
      if (sessions.length === 0) response();
      else response("active");
    break;

    case "set":
      var recent = 0;
      if (request.key) {
        selectedTree = JSON.parse(localStorage.getItem(request.key));
      }
      else if (sessions.length === 0) selectedTree = {};
      else {
        if (localStorage.length > 0)
          for (var key in localStorage) {
            if (key > recent) recent = key;
          }
          selectedTree = JSON.parse(localStorage.getItem(recent));
      }
      response(selectedTree);
    break;

    // tree page requesting json tree object
    case "load":
      response(selectedTree);
    break;

    case "update":
      updateName(sender.tab, request.name);
    break;

    // history page requesting localStorage object
    case "localStorage":
      response(localStorage);
    break;

    // history page requesting to remove a specific tree by key
    case "delete":
      deleteHistoryItem(request.key);
      response("History Item Deleted");
    break;

    // history page requesting to clear all history
    case "clear":
      clearHistory();
      response("History Cleared");
    break;
  }
});

// Navigation Event Listener
chrome.webNavigation.onCommitted.addListener(function(details) {
  eventFilter(details);
}, { url: [ { urlContains: ".wikipedia.org/wiki" },
            { urlContains: ".wiktionary.org/wiki"}]});

// Listener for when the user clicks on the Wikimapper button
chrome.browserAction.onClicked.addListener(function() {
  chrome.tabs.create({'url': chrome.extension.getURL('html/index.html')}, function() {
  });
});

// Listener for first install
chrome.runtime.onInstalled.addListener(function(details) {
  if(details.reason == "install") {
    chrome.tabs.create({'url': chrome.extension.getURL('html/index.html')});
  }
});
<|endoftext|>


<commit_before>var currentURL = null;
var currentTabID = null;

chrome.tabs.onActivated.addListener(function(info) {
	chrome.tabs.get(info.tabId, function(tab) {
		update(tab);
	});
});

chrome.tabs.onUpdated.addListener(function(tabID, changeInfo, tab) {
	if (tab.active) {
		update(tab);
	}
});

chrome.contextMenus.removeAll(function() {
	chrome.contextMenus.create({
	    "title": "Tiles Options",
	    "documentUrlPatterns": [chrome.extension.getURL("/") + "*"],
	    "contexts": ["page", "link"],
	    "onclick" : function() {
	    	goToOptionsPage(true)
	    }
	});
});

function goToOptionsPage(newTab) {
	var optionsURL = chrome.extension.getURL("options/options.html");

	if (newTab) {
		chrome.tabs.create({
			url: optionsURL
		});
	} else {
		chrome.tabs.getCurrent(function(tab) {
			chrome.tabs.update(tab.id, { url : optionsURL })
		});
	}
}

chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
	console.log('Message received: ' + request.message);

	if (request.message == "getUrl") {
		sendResponse({url: currentURL});

		console.log('Sent URL');
	} else if (request.message == "saved") {
		setPopup(false, false);
		changeIcon(true, null);

		sendResponse({message: "success"});

		console.log('Sent success');
	} else if (request.message == "delete") {
		console.log('Deleting....');

		var url;
		if (request.url == undefined)
			url = currentURL;
		else
			url = request.url;

		deleteSite(url, function() {
			setPopup(true, false);
			changeIcon(false, null);

			sendResponse({message: "deleted"});

			chrome.extension.sendMessage({message: "deleted"}, function(response) { });

			console.log('Sent deleted');
		});
	}

	return true;
});

function setPopup(save, error) {
	var details = {};

	if (error) {
		details.popup = 'browserAction/error.html';
	} else {
		if (save) {
			details.popup = 'browserAction/save.html';
		} else {
			details.popup = 'browserAction/delete.html';
		}
	}

	details.tabId = currentTabID;

	chrome.browserAction.setPopup(details);

	console.log('Popup is set to ' + details.popup);
}

function update(tab) {
	currentTabID = tab.id;
	currentURL = tab.url;

	if (isChromeURL(currentURL)) {
		setPopup(false, true);
	} else {
		siteExists(tab.url, function(exists) {
			setPopup(!exists, false);

			changeIcon(exists, null);
		});
	}
}

function isChromeURL(url) {
	return url.substring(0, 6) == 'chrome';
}

function siteExists(url, callback) {
	getSites(function(sites) {
		if (sites) {
			for (var i = 0; i < sites.length; i++) {
				if (sites[i].url == url) {
					return callback(true);
				}
			}
		}

		return callback(false);
	});
}

function changeIcon(colors, callback) {
	var details = {};

	if (colors) {
		details.path = '../icons/icon-bitty.png';
	} else {
		details.path = '../icons/icon-bitty-gray.png';
	}

	details.tabId = currentTabID;

	chrome.browserAction.setIcon(details, callback);

	console.log('Icon is set to ' + details.path);
}<commit_msg>Update all windows browserActions on changes.<commit_after>var currentURL = null;
var currentTabID = null;

chrome.tabs.onActivated.addListener(function(info) {
	chrome.tabs.get(info.tabId, function(tab) {
		update(tab);
	});
});

chrome.tabs.onUpdated.addListener(function(tabID, changeInfo, tab) {
	if (tab.active) {
		update(tab);
	}
});

chrome.contextMenus.removeAll(function() {
	chrome.contextMenus.create({
	    "title": "Tiles Options",
	    "documentUrlPatterns": [chrome.extension.getURL("/") + "*"],
	    "contexts": ["page", "link"],
	    "onclick" : function() {
	    	goToOptionsPage(true)
	    }
	});
});

function goToOptionsPage(newTab) {
	var optionsURL = chrome.extension.getURL("options/options.html");

	if (newTab) {
		chrome.tabs.create({
			url: optionsURL
		});
	} else {
		chrome.tabs.getCurrent(function(tab) {
			chrome.tabs.update(tab.id, { url : optionsURL })
		});
	}
}

chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
	console.log('Message received: ' + request.message);

	if (request.message == "getUrl") {
		sendResponse({url: currentURL});

		console.log('Sent URL');
	} else if (request.message == "saved") {
		setPopup(false, false);
		changeIcon(true, null);

		sendResponse({message: "success"});

		console.log('Sent success');
	} else if (request.message == "delete") {
		console.log('Deleting....');

		var url;
		if (request.url == undefined)
			url = currentURL;
		else
			url = request.url;

		deleteSite(url, function() {
			setPopup(true, false);
			changeIcon(false, null);

			sendResponse({message: "deleted"});

			chrome.extension.sendMessage({message: "deleted"}, function(response) { });

			console.log('Sent deleted');
		});
	}

	chrome.windows.getAll({ populate: true }, function(windows) {
		for (var i = 0; i < windows.length; i++) {
			var window = windows[i];
			var tabs = window.tabs;

			if (tabs && window.type == "normal") {
				for (var j = 0; j < tabs.length; j++) {
					var tab = tabs[j];

					if (tab.active) {
						update(tab);
						break;
					}
				}
			}
		}
	});

	return true;
});

function setPopup(save, error) {
	var details = {};

	if (error) {
		details.popup = 'browserAction/error.html';
	} else {
		if (save) {
			details.popup = 'browserAction/save.html';
		} else {
			details.popup = 'browserAction/delete.html';
		}
	}

	details.tabId = currentTabID;

	chrome.browserAction.setPopup(details);

	console.log('Popup is set to ' + details.popup);
}

function update(tab) {
	currentTabID = tab.id;
	currentURL = tab.url;

	if (isChromeURL(currentURL)) {
		setPopup(false, true);
	} else {
		siteExists(tab.url, function(exists) {
			setPopup(!exists, false);

			changeIcon(exists, null);
		});
	}
}

function isChromeURL(url) {
	return url.substring(0, 6) == 'chrome';
}

function siteExists(url, callback) {
	getSites(function(sites) {
		if (sites) {
			for (var i = 0; i < sites.length; i++) {
				if (sites[i].url == url) {
					return callback(true);
				}
			}
		}

		return callback(false);
	});
}

function changeIcon(colors, callback) {
	var details = {};

	if (colors) {
		details.path = '../icons/icon-bitty.png';
	} else {
		details.path = '../icons/icon-bitty-gray.png';
	}

	details.tabId = currentTabID;

	chrome.browserAction.setIcon(details, callback);

	console.log('Icon is set to ' + details.path);
}<|endoftext|>


<commit_before>// Define root folder for searches
const FOLDER_NAME = "Searches";
const ILLEGAL_BOOKMARK_PROTOCOLS = ["chrome", "javascript", "data", "file", "about"];
const ILLEGAL_CONTENTSCRIPT_PROTOCOLS = ["view-source", "about", "moz-extension"];
const ILLEGAL_CONTENTSCRIPT_DOMAINS = ["accounts-static.cdn.mozilla.net", "accounts.firefox.com", "addons.cdn.mozilla.net",
                                     "addons.mozilla.org", "api.accounts.firefox.com", "content.cdn.mozilla.net", "content.cdn.mozilla.net",
                                     "discovery.addons.mozilla.org", "input.mozilla.org", "install.mozilla.org", "oauth.accounts.firefox.com",
                                     "profile.accounts.firefox.com", "support.mozilla.org", "sync.services.mozilla.com", "testpilot.firefox.com"];
const HELP_LINK = browser.extension.getURL("/help.html");

var rootFolderId = "";
var fallbackMode = false;
var query = "";
var activeTabId = 0;
var showMultiOption = false;
var showFavicons = false;

function parsePlatformInfo(info) {
  os = info.os;
}

// Check to see if the current tab supports content scripts. If not, use the
// fallback mode where only selected text can be used.
// When switching from a tab that require fallback mode to a tab that doesn't
// (and vice versa), the context menu must be rebuilt so that queries aren't
// carried over. Menu rebuilding is only done when necessary, since it is
// supposedly expensive.
function parseTabUrl(tabId) {
  let gettingTabId = browser.tabs.get(tabId);
  gettingTabId.then((response) => {
    let tabProtocol = getUrlProtocol(response.url);
    let tabHostname = getUrlHostname(response.url);

    // All new tabs start out as about:blank. By ignoring those, this code
    // isn't run unnecessarily, since onUpdated will use this function twice.
    if (response.url != "about:blank") {
      let previousFallbackMode = fallbackMode;
      fallbackMode = false;

      if (ILLEGAL_CONTENTSCRIPT_PROTOCOLS.includes(tabProtocol)) {
        fallbackMode = true;
      }

      if (!tabHostname || ILLEGAL_CONTENTSCRIPT_DOMAINS.includes(tabHostname)) {
        fallbackMode = true;
      }

      if (fallbackMode != previousFallbackMode) {
        rebuildMenu();
      }
    }
  });

  let gettingItem = browser.storage.local.get();
  gettingItem.then((response) => {
    showMultiOption = response.showMultiOption;
    showFavicons = response.showFavicons;

    if (showMultiOption == undefined) {
      showMultiOption = true;
    }
    if (showFavicons == undefined) {
      showFavicons = true;
    }

    console.log("getting items", showMultiOption, showFavicons);
  });
}

// Ampersands (&) are used as modifiers for access keys in menus.
// As this is not desirable in CSO, these must be escaped as &&
function encodeAmpersand(str) {
  return str.replace(/&/g, "&&")
}

// Extract the protocol part of a URL
function getUrlProtocol(url) {
  if (url.indexOf(":") > -1) {
    return url.split(":")[0];
  }
}

// Extract the hostname part of a URL
function getUrlHostname(url) {
  if (url.indexOf("://") > -1) {
    return url.split("/")[2];
  }
}

// Set the "contexts" parameter in browser.menus.create
// If in fallback mode, only selection is allowed,
// else, all applicable contexts are allowed.
function getAllowedContexts() {
  if (!fallbackMode) {
    return ["selection", "link", "image"];
  }
  else {
    return ["selection"];
  }
}

// Error logging
function onSuccess(n) {
  if (browser.runtime.lastError) {
    console.log(`Error: ${browser.runtime.lastError}`);
  }
}

function onError(error) {
  console.log(`Error: ${error}`);
}

// Define how long a string is allowed to be before truncated.
// Used in root menu item. But only working if in fallback mode.
function truncate(val) {
  if (val.length > 20) {
    return val.substr(0, 20) + browser.i18n.getMessage("ellipsis");
  }
  else {
    return val;
  }
}

// Get ID of FOLDER_NAME and the object and pass everything through listBookmarksInTree.
// If no root folder found: Show "Getting Started" help link
function main() {
  let gettingRootFolder = browser.bookmarks.search({title: FOLDER_NAME});
  gettingRootFolder.then((bookmarks) => {
    if (bookmarks.length > 0) {
      rootFolderId = bookmarks[0].id;

      let gettingSubTree = browser.bookmarks.getSubTree(rootFolderId);
      gettingSubTree.then((bookmarkItems) => {
        if (bookmarkItems[0].children.length > 0) {
          listBookmarksInTree(bookmarkItems[0], rootFolderId);
        }
        else {
          createHelpLink();
        }
      });
    }
    else {
      createHelpLink();
    }
  });
}

// Parse through all bookmarks in tree.
// "n" adds 1 for each bookmark in node. If all bookmarks have been created,
// call on createMultiOption
function listBookmarksInTree(bookmarkItem, rootFolderId) {
  populateContextMenu(bookmarkItem.id, bookmarkItem.title, bookmarkItem.url, bookmarkItem.parentId, bookmarkItem.type, rootFolderId);

  if (bookmarkItem.children) {
    let n = 0;
    for (child of bookmarkItem.children) {
      listBookmarksInTree(child, rootFolderId);
      n ++;
    }
    if (n = bookmarkItem.children.length) {
      createMultiOption(bookmarkItem.id);
    }
  }
}

// Some URLs cannot be used as search engines. Check all bookmark URLs
// in the Searches folder if they are valid.
function checkValid(url) {
  let isValidProtocol = false, isValidWildcard = false, isValid = false;

  // Check that URL is not privileged according to
  // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/create
  let protocol = getUrlProtocol(url);
  if (protocol) {
    isValidProtocol = !ILLEGAL_BOOKMARK_PROTOCOLS.includes(protocol);
  }

  // Check that URL is a keyword search (i.e. containing "%s")
  if (url.indexOf("%s") > -1) {
    isValidWildcard = true;
  }

  if (isValidProtocol && isValidWildcard) {
    isValid = true;
  }
  else {
    console.warn(`Non-conforming url: ${url}. Illegal protocol or missing \"%s\".`);
  }

  return isValid;
}

function makeFavicon(url, showFavicons) {
  console.log("showFavicons", showFavicons);
  if (showFavicons) {
    let faviconUrl = "";
    if (url.indexOf("://") > -1) {
      faviconUrl = "https://www.google.com/s2/favicons?domain=" + getUrlProtocol(url) + "://" + getUrlHostname(url);
    }
    return {16: faviconUrl};
  }
  else {
    return undefined;
  }
}

// Show a "Getting Started" link in the context menu if not set up properly
function createHelpLink() {
  browser.menus.create({
    id: HELP_LINK,
    title: "Context Search – " + browser.i18n.getMessage("help_linkText"),
    contexts: ["all"],
    onclick: createTab
  }, onSuccess());
}

// Make the context menu
function populateContextMenu(id, title, url, parent, type, rootFolderId) {
  if (id == rootFolderId) {
    //This is the root folder, make the title what is searched for
    browser.menus.create({
      id: rootFolderId,
      title: browser.i18n.getMessage("menu_rootLabelText", "%s"),
      contexts: getAllowedContexts()
    }, onSuccess());
  }
  else {

    if (!url) {
      // These are the folders
      browser.menus.create({
        parentId: parent,
        id: id,
        title: encodeAmpersand(title),
        icons: {
          16: "icons/folder.svg"
        }
      }, onSuccess());
    }

    else {
      if (type == "separator") {
        // These are the separators
        browser.menus.create({
          parentId: parent,
          id: id,
          type: "separator"
        }, onSuccess());
      }

      if (url && title) {
        // These are the bookmarks
        browser.menus.create({
          parentId: parent,
          id: id + ";" + url,
          title: encodeAmpersand(title),
          icons: makeFavicon(url, showFavicons),
          enabled: checkValid(url),
          onclick: createTab
        }, onSuccess());
      }
    }

  }
}

// Create a separator and a new option to open all bookmarks at once for all folders
function createMultiOption(folderId) {
  if (showMultiOption) {
    browser.menus.create({
      parentId: folderId,
      type: "separator"
    });
    browser.menus.create({
      parentId: folderId,
      id: "folder" + folderId,
      title: browser.i18n.getMessage("menu_openMulti"),
      onclick: createMultiTab
    });
  }
}

function createMultiTab(info, parentTab) {
  let parentDir = info.parentMenuItemId;

  if (query == "%s" || fallbackMode) {
    query = info.selectionText;
  }

  let gettingSubTree = browser.bookmarks.getSubTree(parentDir);
  gettingSubTree.then((bookmarkItems) => {
    for (child of bookmarkItems[0].children) {
      if (child.type == "bookmark" && checkValid(child.url)) {
        let url = child.url.replace("%s", encodeURIComponent(query));
        goTo(url, parentTab.id, false, true);
      }
    }
  });
}

// Create the new tab with the search result.
function createTab(info, parentTab) {
  // If in fallback mode, content scripts cannot be utilized.
  // If so, make sure the selected text is used for search.
  if (query == "%s" || fallbackMode) {
    query = info.selectionText;
  }
  let bookmarkId = info.menuItemId.split(";")[0];
  let url = info.menuItemId.split(";")[1].replace("%s", encodeURIComponent(query));

  // Check which mouse button was clicked and if any modifier keys was held down.
  // button 0 = left, 1 = middle, 2 = right.
  // This code tries to mimic the default behaviors as seen on http://mzl.la/1xKrRYF

  let mouseButton = info.button;
  let modifiers = info.modifiers;

  let invertNewCurrentTab = false;
  let invertForegroundBackgroundTab = false;

  if (mouseButton == 1) {
    invertNewCurrentTab = true;
  }

  if (os = "mac") {
    if (modifiers.includes("Command")) {
      invertNewCurrentTab = true;
    }
  else
    if (modifiers.includes("Ctrl")) {
      invertNewCurrentTab = true;
    }
  }

  if (modifiers.includes("Shift")) {
    invertForegroundBackgroundTab = true;
  }

  // Check options if tab should open as active or in background
  // Replace the browser standard %s for keyword searches with
  // the selected text on the page and make a tab
  let gettingItem = browser.storage.local.get();
  gettingItem.then((response) => {

    // Get default user settings, if none set, use for default behavior
    let openInNewTab = response.openInNewTab;
    let makeTabActive = response.makeTabActive;

    if (openInNewTab == undefined) {
      openInNewTab = true;
    }
    if (makeTabActive == undefined) {
      makeTabActive = true;
    }

    // If modifiers or alternative mouse button clicked, invert settings temporarily
    if (invertNewCurrentTab) {
      openInNewTab = !openInNewTab;
    }

    if (invertForegroundBackgroundTab) {
      makeTabActive = !makeTabActive;
    }

    goTo(url, parentTab.id, makeTabActive, openInNewTab);
  });
}

function goTo(url, openerTabId, active, openInNewTab) {
  if (openInNewTab) {
    // Using openerTabId makes new tabs open as expected. If in a window with
    // toolbars hidden, trying to set an openerTabId will throw an error.
    // If an error is thrown, the new tab is created without openerTabId.
    // For usability reasons the new tab will always get active.
    let creatingTab = browser.tabs.create({
      url: url,
      active: active,
      openerTabId: openerTabId
    });
    creatingTab.catch((e) => {
      browser.tabs.create({
        url: url,
        active: true
      })
    });
  }
  else {
    browser.tabs.update({
      url: url
    });
  }
}

// Rebuild the entire menu.
function rebuildMenu() {
  browser.menus.remove(rootFolderId);
  browser.menus.remove(HELP_LINK);
  browser.menus.refresh();

  main();
}

// Take care of the message sent from query.js. When in fallback mode
// content scripts cannot be utilized. Thus this function should be ignored.
function handleQuery(response) {
  if (!fallbackMode) {
    query = response.query;
    elementType = response.elementType;

    if (!query && (elementType == "IMG" || elementType == "A")) {
      // Remove contextmenu when query is empty but
      // browser.menus will want to show it because of its contexts.
      // The menu must then be rebuilt to recieve future input
      rebuildMenu();
    }
    else if (elementType == "IMG") {
      browser.menus.update(rootFolderId, {
        title: browser.i18n.getMessage("menu_rootLabelImage")
      });
      browser.menus.refresh();
    }
    else {
      browser.menus.update(rootFolderId, {
        title: browser.i18n.getMessage("menu_rootLabelText", encodeAmpersand(truncate(query)))
      });
      browser.menus.refresh();
    }
  }
}

// Add some listeners for when bookmarks in the Searches folder is edited
browser.bookmarks.onCreated.addListener(rebuildMenu);
browser.bookmarks.onRemoved.addListener(rebuildMenu);
browser.bookmarks.onChanged.addListener(rebuildMenu);
browser.bookmarks.onMoved.addListener(rebuildMenu);

browser.tabs.onActivated.addListener(function(info) {
  activeTabId = info.tabId;
  parseTabUrl(info.tabId);
});

browser.tabs.onUpdated.addListener(function(tabId, changeInfo, tabInfo) {
  // Only run this code on the active tab.
  if (changeInfo.status == "loading" && tabId == activeTabId) {
    parseTabUrl(tabId);
  }
});

// Recieve messages from query.js
browser.runtime.onMessage.addListener(handleQuery);

// Parse platform info, used for modifier keys
browser.runtime.getPlatformInfo().then(parsePlatformInfo);

main();
<commit_msg>make sure prefs are loaded after a change<commit_after>// Define root folder for searches
const FOLDER_NAME = "Searches";
const ILLEGAL_BOOKMARK_PROTOCOLS = ["chrome", "javascript", "data", "file", "about"];
const ILLEGAL_CONTENTSCRIPT_PROTOCOLS = ["view-source", "about", "moz-extension"];
const ILLEGAL_CONTENTSCRIPT_DOMAINS = ["accounts-static.cdn.mozilla.net", "accounts.firefox.com", "addons.cdn.mozilla.net",
                                     "addons.mozilla.org", "api.accounts.firefox.com", "content.cdn.mozilla.net", "content.cdn.mozilla.net",
                                     "discovery.addons.mozilla.org", "input.mozilla.org", "install.mozilla.org", "oauth.accounts.firefox.com",
                                     "profile.accounts.firefox.com", "support.mozilla.org", "sync.services.mozilla.com", "testpilot.firefox.com"];
const HELP_LINK = browser.extension.getURL("/help.html");

var rootFolderId = "";
var fallbackMode = false;
var query = "";
var activeTabId = 0;
var showMultiOption = false;
var showFavicons = false;

function parsePlatformInfo(info) {
  os = info.os;
}

// Check to see if the current tab supports content scripts. If not, use the
// fallback mode where only selected text can be used.
// When switching from a tab that require fallback mode to a tab that doesn't
// (and vice versa), the context menu must be rebuilt so that queries aren't
// carried over. Menu rebuilding is only done when necessary, since it is
// supposedly expensive.
function parseTabUrl(tabId) {
  let gettingTabId = browser.tabs.get(tabId);
  gettingTabId.then((response) => {
    let tabProtocol = getUrlProtocol(response.url);
    let tabHostname = getUrlHostname(response.url);

    // All new tabs start out as about:blank. By ignoring those, this code
    // isn't run unnecessarily, since onUpdated will use this function twice.
    if (response.url != "about:blank") {
      let previousFallbackMode = fallbackMode;
      fallbackMode = false;

      if (ILLEGAL_CONTENTSCRIPT_PROTOCOLS.includes(tabProtocol)) {
        fallbackMode = true;
      }

      if (!tabHostname || ILLEGAL_CONTENTSCRIPT_DOMAINS.includes(tabHostname)) {
        fallbackMode = true;
      }

      if (fallbackMode != previousFallbackMode) {
        rebuildMenu();
      }
    }
  });
}

// Get user prefs. If changed since last load, run rebuildMenu()
function getOptions() {
  let gettingItem = browser.storage.local.get();
  gettingItem.then((response) => {
    let previousShowMultiOption = showMultiOption;
    let previousShowFavicons = showFavicons;

    showMultiOption = response.showMultiOption;
    showFavicons = response.showFavicons;

    if (showMultiOption == undefined) {
      showMultiOption = true;
    }

    if (showFavicons == undefined) {
      showFavicons = true;
    }

    if (showMultiOption != previousShowMultiOption || showFavicons != previousShowFavicons) {
      rebuildMenu();
    }
  });
}

// Ampersands (&) are used as modifiers for access keys in menus.
// As this is not desirable in CSO, these must be escaped as &&
function encodeAmpersand(str) {
  return str.replace(/&/g, "&&")
}

// Extract the protocol part of a URL
function getUrlProtocol(url) {
  if (url.indexOf(":") > -1) {
    return url.split(":")[0];
  }
}

// Extract the hostname part of a URL
function getUrlHostname(url) {
  if (url.indexOf("://") > -1) {
    return url.split("/")[2];
  }
}

// Set the "contexts" parameter in browser.menus.create
// If in fallback mode, only selection is allowed,
// else, all applicable contexts are allowed.
function getAllowedContexts() {
  if (!fallbackMode) {
    return ["selection", "link", "image"];
  }
  else {
    return ["selection"];
  }
}

// Error logging
function onSuccess(n) {
  if (browser.runtime.lastError) {
    console.log(`Error: ${browser.runtime.lastError}`);
  }
}

function onError(error) {
  console.log(`Error: ${error}`);
}

// Define how long a string is allowed to be before truncated.
// Used in root menu item. But only working if in fallback mode.
function truncate(val) {
  if (val.length > 20) {
    return val.substr(0, 20) + browser.i18n.getMessage("ellipsis");
  }
  else {
    return val;
  }
}

// Get ID of FOLDER_NAME and the object and pass everything through listBookmarksInTree.
// If no root folder found: Show "Getting Started" help link
function main() {
  let gettingRootFolder = browser.bookmarks.search({title: FOLDER_NAME});
  gettingRootFolder.then((bookmarks) => {
    if (bookmarks.length > 0) {
      rootFolderId = bookmarks[0].id;

      let gettingSubTree = browser.bookmarks.getSubTree(rootFolderId);
      gettingSubTree.then((bookmarkItems) => {
        if (bookmarkItems[0].children.length > 0) {
          listBookmarksInTree(bookmarkItems[0], rootFolderId);
        }
        else {
          createHelpLink();
        }
      });
    }
    else {
      createHelpLink();
    }
  });
}

// Parse through all bookmarks in tree.
// "n" adds 1 for each bookmark in node. If all bookmarks have been created,
// call on createMultiOption
function listBookmarksInTree(bookmarkItem, rootFolderId) {
  populateContextMenu(bookmarkItem.id, bookmarkItem.title, bookmarkItem.url, bookmarkItem.parentId, bookmarkItem.type, rootFolderId);

  if (bookmarkItem.children) {
    let n = 0;
    for (child of bookmarkItem.children) {
      listBookmarksInTree(child, rootFolderId);
      n ++;
    }
    if (n = bookmarkItem.children.length) {
      createMultiOption(bookmarkItem.id);
    }
  }
}

// Some URLs cannot be used as search engines. Check all bookmark URLs
// in the Searches folder if they are valid.
function checkValid(url) {
  let isValidProtocol = false, isValidWildcard = false, isValid = false;

  // Check that URL is not privileged according to
  // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/create
  let protocol = getUrlProtocol(url);
  if (protocol) {
    isValidProtocol = !ILLEGAL_BOOKMARK_PROTOCOLS.includes(protocol);
  }

  // Check that URL is a keyword search (i.e. containing "%s")
  if (url.indexOf("%s") > -1) {
    isValidWildcard = true;
  }

  if (isValidProtocol && isValidWildcard) {
    isValid = true;
  }
  else {
    console.warn(`Non-conforming url: ${url}. Illegal protocol or missing \"%s\".`);
  }

  return isValid;
}

function makeFavicon(url, showFavicons) {
  if (showFavicons) {
    let faviconUrl = "";

    if (url == "FOLDER") {
      faviconUrl = "icons/folder.svg"
    }
    else if (url.indexOf("://") > -1) {
      faviconUrl = "https://www.google.com/s2/favicons?domain=" + getUrlProtocol(url) + "://" + getUrlHostname(url);
    }
    return {16: faviconUrl};
  }
  else {
    return undefined;
  }
}

// Show a "Getting Started" link in the context menu if not set up properly
function createHelpLink() {
  browser.menus.create({
    id: HELP_LINK,
    title: "Context Search – " + browser.i18n.getMessage("help_linkText"),
    contexts: ["all"],
    onclick: createTab
  }, onSuccess());
}

// Make the context menu
function populateContextMenu(id, title, url, parent, type, rootFolderId) {
  if (id == rootFolderId) {
    //This is the root folder, make the title what is searched for
    browser.menus.create({
      id: rootFolderId,
      title: browser.i18n.getMessage("menu_rootLabelText", "%s"),
      contexts: getAllowedContexts()
    }, onSuccess());
  }
  else {

    if (!url) {
      // These are the folders
      browser.menus.create({
        parentId: parent,
        id: id,
        title: encodeAmpersand(title),
        icons: makeFavicon("FOLDER", showFavicons)
      }, onSuccess());
    }

    else {
      if (type == "separator") {
        // These are the separators
        browser.menus.create({
          parentId: parent,
          id: id,
          type: "separator"
        }, onSuccess());
      }

      if (url && title) {
        // These are the bookmarks
        browser.menus.create({
          parentId: parent,
          id: id + ";" + url,
          title: encodeAmpersand(title),
          icons: makeFavicon(url, showFavicons),
          enabled: checkValid(url),
          onclick: createTab
        }, onSuccess());
      }
    }

  }
}

// Create a separator and a new option to open all bookmarks at once for all folders
function createMultiOption(folderId) {
  if (showMultiOption) {
    browser.menus.create({
      parentId: folderId,
      type: "separator"
    });
    browser.menus.create({
      parentId: folderId,
      id: "folder" + folderId,
      title: browser.i18n.getMessage("menu_openMulti"),
      onclick: createMultiTab
    });
  }
}

function createMultiTab(info, parentTab) {
  let parentDir = info.parentMenuItemId;

  if (query == "%s" || fallbackMode) {
    query = info.selectionText;
  }

  let gettingSubTree = browser.bookmarks.getSubTree(parentDir);
  gettingSubTree.then((bookmarkItems) => {
    for (child of bookmarkItems[0].children) {
      if (child.type == "bookmark" && checkValid(child.url)) {
        let url = child.url.replace("%s", encodeURIComponent(query));
        goTo(url, parentTab.id, false, true);
      }
    }
  });
}

// Create the new tab with the search result.
function createTab(info, parentTab) {
  // If in fallback mode, content scripts cannot be utilized.
  // If so, make sure the selected text is used for search.
  if (query == "%s" || fallbackMode) {
    query = info.selectionText;
  }
  let bookmarkId = info.menuItemId.split(";")[0];
  let url = info.menuItemId.split(";")[1].replace("%s", encodeURIComponent(query));

  // Check which mouse button was clicked and if any modifier keys was held down.
  // button 0 = left, 1 = middle, 2 = right.
  // This code tries to mimic the default behaviors as seen on http://mzl.la/1xKrRYF

  let mouseButton = info.button;
  let modifiers = info.modifiers;

  let invertNewCurrentTab = false;
  let invertForegroundBackgroundTab = false;

  if (mouseButton == 1) {
    invertNewCurrentTab = true;
  }

  if (os = "mac") {
    if (modifiers.includes("Command")) {
      invertNewCurrentTab = true;
    }
  else
    if (modifiers.includes("Ctrl")) {
      invertNewCurrentTab = true;
    }
  }

  if (modifiers.includes("Shift")) {
    invertForegroundBackgroundTab = true;
  }

  // Check options if tab should open as active or in background
  // Replace the browser standard %s for keyword searches with
  // the selected text on the page and make a tab
  let gettingItem = browser.storage.local.get();
  gettingItem.then((response) => {

    // Get default user settings, if none set, use for default behavior
    let openInNewTab = response.openInNewTab;
    let makeTabActive = response.makeTabActive;

    if (openInNewTab == undefined) {
      openInNewTab = true;
    }
    if (makeTabActive == undefined) {
      makeTabActive = true;
    }

    // If modifiers or alternative mouse button clicked, invert settings temporarily
    if (invertNewCurrentTab) {
      openInNewTab = !openInNewTab;
    }

    if (invertForegroundBackgroundTab) {
      makeTabActive = !makeTabActive;
    }

    goTo(url, parentTab.id, makeTabActive, openInNewTab);
  });
}

function goTo(url, openerTabId, active, openInNewTab) {
  if (openInNewTab) {
    // Using openerTabId makes new tabs open as expected. If in a window with
    // toolbars hidden, trying to set an openerTabId will throw an error.
    // If an error is thrown, the new tab is created without openerTabId.
    // For usability reasons the new tab will always get active.
    let creatingTab = browser.tabs.create({
      url: url,
      active: active,
      openerTabId: openerTabId
    });
    creatingTab.catch((e) => {
      browser.tabs.create({
        url: url,
        active: true
      })
    });
  }
  else {
    browser.tabs.update({
      url: url
    });
  }
}

// Rebuild the entire menu.
function rebuildMenu() {
  browser.menus.remove(rootFolderId);
  browser.menus.remove(HELP_LINK);
  browser.menus.refresh();

  main();
}

// Take care of the message sent from query.js. When in fallback mode
// content scripts cannot be utilized. Thus this function should be ignored.
function handleQuery(response) {
  if (!fallbackMode) {
    query = response.query;
    elementType = response.elementType;

    if (!query && (elementType == "IMG" || elementType == "A")) {
      // Remove contextmenu when query is empty but
      // browser.menus will want to show it because of its contexts.
      // The menu must then be rebuilt to recieve future input
      rebuildMenu();
    }
    else if (elementType == "IMG") {
      browser.menus.update(rootFolderId, {
        title: browser.i18n.getMessage("menu_rootLabelImage")
      });
      browser.menus.refresh();
    }
    else {
      browser.menus.update(rootFolderId, {
        title: browser.i18n.getMessage("menu_rootLabelText", encodeAmpersand(truncate(query)))
      });
      browser.menus.refresh();
    }
  }
}

// Add some listeners for when bookmarks in the Searches folder is edited
browser.bookmarks.onCreated.addListener(rebuildMenu);
browser.bookmarks.onRemoved.addListener(rebuildMenu);
browser.bookmarks.onChanged.addListener(rebuildMenu);
browser.bookmarks.onMoved.addListener(rebuildMenu);

browser.tabs.onActivated.addListener(function(info) {
  activeTabId = info.tabId;
  parseTabUrl(info.tabId);
  getOptions();
});

browser.tabs.onUpdated.addListener(function(tabId, changeInfo, tabInfo) {
  // Only run this code on the active tab.
  if (changeInfo.status == "loading" && tabId == activeTabId) {
    parseTabUrl(tabId);
    getOptions();
  }
});

// Recieve messages from query.js
browser.runtime.onMessage.addListener(handleQuery);

// Parse platform info, used for modifier keys
browser.runtime.getPlatformInfo().then(parsePlatformInfo);

main();
<|endoftext|>


<commit_before>//The main function
function apply(tabId) {
  "use strict";
  chrome.storage.sync.get(null, function (data) {
    var cssCode = "",
      scriptCode = "function nkchgApply() { 'use strict';";

    if (data.groups === "checked") {

      if (data.people === "checked") {
        cssCode += "div[class^='feed_repost'] {display: none;}";
      } else {
        cssCode +=
          "div[class^='feed_repost'] {display: block;}" +
          "div[class^='feed_repost-'], div[class^='feed_reposts_'] {display: none;}";
      }

      if (data.mygroups === "checked") {
        cssCode += "div[id^=post-].post_copy {display: none;}";
      } else {
        cssCode += "div[id^=post-].post_copy {display: block;}";
      }

    } else {
      cssCode += "div[class^='feed_repost'], div[id^=post-].post_copy {display: block;}";
    }

    scriptCode +=
      "var els = nkchgWall.getElementsByTagName('a');" +
      "for (var i = 0, l = els.length; i < l; i++) {" +
      "  var el = els[i];" +
      "  if (el.href.search(/sprashivai.ru|spring.me|nekto.me|ask.fm/) !== -1)" +
      "    nkchgClosestEl(el, '" + data.links + "', ' nkchgLinks');" +
      "}" +
      "nkchgFind('group_share', '" + data.group_share + "', 'nkchgGroup_share');" +
      "nkchgFind('event_share', '" + data.event_share + "', 'nkchgEvent_share');" +
      "nkchgFind('wall_post_source_default', '" + data.apps + "', 'nkchgApps');" +
      "nkchgFind('wall_post_more', '" + data.wall_post_more + "', 'nkchgWall_post_more');" +
      "nkchgFind('post_like_icon no_likes', '" + data.likes + "', 'nkchgLikes');" +
      "nkchgFind('reply_link', '" + data.comments + "', 'nkchgComments');" +
      "}";

    chrome.tabs.insertCSS(tabId, {code: cssCode});

    chrome.tabs.executeScript(tabId, {code:
      scriptCode +
      "nkchgApply();" +
      "console.log('Чистые новости для VK.com: your wall has been cleaned');"
      });
  });
}

//Do things with the second and the third checkbox:
function checkboxes() {
  "use strict";
  var select = document.settingsForm,
    child;
  chrome.storage.sync.get(null, function (data) {
    //Show/hide the second and the third checkbox depending on the state of the first checkbox ("groups"):
    var dataObj = {},
      i;
    for (i = 2; i < 4; i += 1) {
      child = select.children[i];
      //If the first checkbox ("groups") is unchecked then uncheck the second and the third and reset their settings in storage:
      if (data.groups !== "checked") {
        child.style.display = 'none';
        child = child.children[0];
        child.checked = false;
        dataObj[child.name] = "";
      } else {
        child.style.display = 'block';
      }
    }
    if (Object.keys(dataObj).length !== 0) {
      chrome.storage.sync.set(dataObj);
    }
  });
}

//Catch clicks on checkboxes and remember the values ("checked"), reapply the main function:
function clickHandler() {
  "use strict";
  var name = this.name,
    value = this.value,
    dataObj = {};
  chrome.storage.sync.get(name, function (data) {
    dataObj[name] = data[name] === value ? "" : value;
    chrome.storage.sync.set(dataObj, function () {
      checkboxes();
      apply();
    });
  });
}

document.addEventListener('DOMContentLoaded', function () {
  "use strict";
  //For older versions: convert localStorage to chrome.storage.sync
  var dataObj = {},
    select = document.settingsForm,
    name,
    child,
    i;
  for (i = 0; i < localStorage.length; i += 1) {
    name = localStorage.key(i);
    dataObj[name] = localStorage[name];
  }
  if (Object.keys(dataObj).length !== 0) {
    chrome.storage.sync.set(dataObj, function () {
      localStorage.clear();
    });
  }
  //If there is no saved settings then set them to defaults (check only the first checkbox):
  chrome.storage.sync.get(null, function (data) {
    if (Object.keys(data).length === 0) {
      chrome.storage.sync.set({"groups": "checked"});
    }
  });
  if (select) {
    checkboxes();
    select = select.getElementsByTagName("input");
    chrome.storage.sync.get(null, function (data) {
      for (i = 0; i < select.length; i += 1) {
        child = select[i];
        child.addEventListener('click', clickHandler);
        if (data[child.name] === "checked") {
          child.checked = true;
        }
      }
    });
  }
});

//Launch the main function only on certain pages of VK:
function checkForValidUrl(tabId, changeInfo, tab) {
  "use strict";
  if (changeInfo.status === "loading") {
    var url = tab.url;
    if (url.indexOf('vk.com/feed') > -1) {
      if (url.search(/photos|articles|likes|notifications|comments|updates|replies/) === -1) {
        if (url.search(/\/feed\?[wz]=/) === -1) {
          chrome.pageAction.show(tabId);
          //
s with these classes will be hidden:
          chrome.tabs.insertCSS(tabId, {code:
            ".nkchgGroups, .nkchgPeople, .nkchgMygroups, .nkchgLinks, .nkchgGroup_share, .nkchgEvent_share, .nkchgApps, .nkchgWall_post_more, .nkchgLikes, .nkchgComments {display: none;}"
            });
          chrome.tabs.executeScript(tabId, {code:
            "var nkchgWall = document.getElementById('feed_rows');" +
            "if (!nkchgObserver)" +
            "  var nkchgObserver = new MutationObserver(function (mutations) {" +
            "    'use strict';" +
            "    if (mutations[0].addedNodes.length !== 0) {" +
            "      nkchgApply();" +
            "      console.log('Чистые новости для VK.com: your wall has been cleaned by the MutationObserver');" +
            "    }" +
            "  });" +
            "nkchgObserver.observe(nkchgWall, {childList: true});" +
            "function nkchgClosestEl(elem, action, nkchgClass) {" +
            "  'use strict';" +
            "  var parent = elem.parentNode;" +
            "  while (parent != nkchgWall) {" +
            "    if (parent.className.indexOf('feed_row') !== -1) {" +
            "      var re = new RegExp(nkchgClass, 'g');" +
            "      if (action === 'checked') {" +
            "        if (parent.className.indexOf(nkchgClass) === -1)" +
            "          parent.className += nkchgClass;" +
            "        return;" +
            "      } else {" +
            "        parent.className = parent.className.replace(re, '');" +
            "        return;" +
            "      }" +
            "    }" +
            "    else" +
            "      parent = parent.parentNode;" +
            "  }" +
            "}" +
            "function nkchgFind(className, action, newClassName) {" +
            "  'use strict';" +
            "  var els = nkchgWall.getElementsByClassName(className);" +
            "  for (var i = 0, l = els.length; i < l; i++) {" +
            "    var el = els[i];" +
            "    if (newClassName === 'nkchgApps' && el.href.indexOf('app3698024') !== -1)" +
            "      continue;" +
            "    nkchgClosestEl(el, action, ' ' + newClassName);" +
            "  }" +
            "}"
            });
          apply(tabId);
        }
      } else {
        //Show all the
s that have been hidden, stop observing:
        chrome.tabs.insertCSS(tabId, {code:
          "div[class^='feed_repost'], div[id^=post-].post_copy, .nkchgGroups, .nkchgPeople, .nkchgMygroups, .nkchgLinks, .nkchgGroup_share, .nkchgEvent_share, .nkchgApps, .nkchgWall_post_more, .nkchgLikes, .nkchgComments {display: block;}"
          });
        chrome.tabs.executeScript(tabId, {code:
          "if (nkchgObserver) nkchgObserver.disconnect();" +
          "console.log('Чистые новости для VK.com: cleaning disabled');"
          });
      }
    }
  }
}

chrome.tabs.onUpdated.addListener(checkForValidUrl);
<commit_msg>JSLinted the injectable code<commit_after>//The main function
function apply(tabId) {
  "use strict";
  chrome.storage.sync.get(null, function (data) {
    var cssCode = "",
      scriptCode = "function nkchgApply() { 'use strict';";

    if (data.groups === "checked") {

      if (data.people === "checked") {
        cssCode += "div[class^='feed_repost'] {display: none;}";
      } else {
        cssCode +=
          "div[class^='feed_repost'] {display: block;}" +
          "div[class^='feed_repost-'], div[class^='feed_reposts_'] {display: none;}";
      }

      if (data.mygroups === "checked") {
        cssCode += "div[id^=post-].post_copy {display: none;}";
      } else {
        cssCode += "div[id^=post-].post_copy {display: block;}";
      }

    } else {
      cssCode += "div[class^='feed_repost'], div[id^=post-].post_copy {display: block;}";
    }

    scriptCode +=
      "var els = nkchgWall.getElementsByTagName('a')," +
      "  l = els.length," +
      "  i," +
      "  el;" +
      "for (i = 0; i < l; i += 1) {" +
      "  el = els[i];" +
      "  if (el.href.search(/sprashivai\\.ru|spring\\.me|nekto\\.me|ask\\.fm/) !== -1) {" +
      "    nkchgClosestEl(el, '" + data.links + "', ' nkchgLinks');" +
      "  }" +
      "}" +
      "nkchgFind('group_share', '" + data.group_share + "', 'nkchgGroup_share');" +
      "nkchgFind('event_share', '" + data.event_share + "', 'nkchgEvent_share');" +
      "nkchgFind('wall_post_source_default', '" + data.apps + "', 'nkchgApps');" +
      "nkchgFind('wall_post_more', '" + data.wall_post_more + "', 'nkchgWall_post_more');" +
      "nkchgFind('post_like_icon no_likes', '" + data.likes + "', 'nkchgLikes');" +
      "nkchgFind('reply_link', '" + data.comments + "', 'nkchgComments');" +
      "}";

    chrome.tabs.insertCSS(tabId, {code: cssCode});

    chrome.tabs.executeScript(tabId, {code:
      scriptCode +
      "nkchgApply();" +
      "console.log('Чистые новости для VK.com: your wall has been cleaned');"
      });
  });
}

//Do things with the second and the third checkbox:
function checkboxes() {
  "use strict";
  var select = document.settingsForm,
    child;
  chrome.storage.sync.get(null, function (data) {
    //Show/hide the second and the third checkbox depending on the state of the first checkbox ("groups"):
    var dataObj = {},
      i;
    for (i = 2; i < 4; i += 1) {
      child = select.children[i];
      //If the first checkbox ("groups") is unchecked then uncheck the second and the third and reset their settings in storage:
      if (data.groups !== "checked") {
        child.style.display = 'none';
        child = child.children[0];
        child.checked = false;
        dataObj[child.name] = "";
      } else {
        child.style.display = 'block';
      }
    }
    if (Object.keys(dataObj).length !== 0) {
      chrome.storage.sync.set(dataObj);
    }
  });
}

//Catch clicks on checkboxes and remember the values ("checked"), reapply the main function:
function clickHandler() {
  "use strict";
  var name = this.name,
    value = this.value,
    dataObj = {};
  chrome.storage.sync.get(name, function (data) {
    dataObj[name] = data[name] === value ? "" : value;
    chrome.storage.sync.set(dataObj, function () {
      checkboxes();
      apply();
    });
  });
}

document.addEventListener('DOMContentLoaded', function () {
  "use strict";
  //For older versions: convert localStorage to chrome.storage.sync
  var dataObj = {},
    select = document.settingsForm,
    name,
    child,
    i;
  for (i = 0; i < localStorage.length; i += 1) {
    name = localStorage.key(i);
    dataObj[name] = localStorage[name];
  }
  if (Object.keys(dataObj).length !== 0) {
    chrome.storage.sync.set(dataObj, function () {
      localStorage.clear();
    });
  }
  //If there is no saved settings then set them to defaults (check only the first checkbox):
  chrome.storage.sync.get(null, function (data) {
    if (Object.keys(data).length === 0) {
      chrome.storage.sync.set({"groups": "checked"});
    }
  });
  if (select) {
    checkboxes();
    select = select.getElementsByTagName("input");
    chrome.storage.sync.get(null, function (data) {
      for (i = 0; i < select.length; i += 1) {
        child = select[i];
        child.addEventListener('click', clickHandler);
        if (data[child.name] === "checked") {
          child.checked = true;
        }
      }
    });
  }
});

//Launch the main function only on certain pages of VK:
function checkForValidUrl(tabId, changeInfo, tab) {
  "use strict";
  if (changeInfo.status === "loading") {
    var url = tab.url;
    if (url.indexOf('vk.com/feed') > -1) {
      if (url.search(/photos|articles|likes|notifications|comments|updates|replies/) === -1) {
        if (url.search(/\/feed\?[wz]=/) === -1) {
          chrome.pageAction.show(tabId);
          //
s with these classes will be hidden:
          chrome.tabs.insertCSS(tabId, {code:
            ".nkchgGroups, .nkchgPeople, .nkchgMygroups, .nkchgLinks, .nkchgGroup_share, .nkchgEvent_share, .nkchgApps, .nkchgWall_post_more, .nkchgLikes, .nkchgComments {display: none;}"
            });
          chrome.tabs.executeScript(tabId, {code:
            "var nkchgWall = document.getElementById('feed_rows');" +
            "if (!nkchgObserver) {" +
            "  var nkchgObserver = new window.MutationObserver(function (mutations) {" +
            "    'use strict';" +
            "    if (mutations[0].addedNodes.length !== 0) {" +
            "      nkchgApply();" +
            "      console.log('Чистые новости для VK.com: your wall has been cleaned by the MutationObserver');" +
            "    }" +
            "  });" +
            "}" +
            "nkchgObserver.observe(nkchgWall, {childList: true});" +
            "function nkchgClosestEl(elem, action, nkchgClass) {" +
            "  'use strict';" +
            "  var parent = elem.parentNode," +
            "    re = new RegExp(nkchgClass, 'g');" +
            "  while (parent !== nkchgWall) {" +
            "    if (parent.className.indexOf('feed_row') !== -1) {" +
            "      if (action === 'checked') {" +
            "        if (parent.className.indexOf(nkchgClass) === -1) {" +
            "          parent.className += nkchgClass;" +
            "        }" +
            "        return;" +
            "      } else {" +
            "        parent.className = parent.className.replace(re, '');" +
            "        return;" +
            "      }" +
            "    } else {" +
            "      parent = parent.parentNode;" +
            "    }" +
            "  }" +
            "}" +
            "function nkchgFind(className, action, newClassName) {" +
            "  'use strict';" +
            "  var els = nkchgWall.getElementsByClassName(className)," +
            "    l = els.length," +
            "    i," +
            "    el;" +
            "  for (i = 0; i < l; i += 1) {" +
            "    el = els[i];" +
            "    if (!(newClassName === 'nkchgApps' && el.href.indexOf('app3698024') !== -1)) {" +
            "      nkchgClosestEl(el, action, ' ' + newClassName);" +
            "    }" +
            "  }" +
            "}"
            });
          apply(tabId);
        }
      } else {
        //Show all the
s that have been hidden, stop observing:
        chrome.tabs.insertCSS(tabId, {code:
          "div[class^='feed_repost'], div[id^=post-].post_copy, .nkchgGroups, .nkchgPeople, .nkchgMygroups, .nkchgLinks, .nkchgGroup_share, .nkchgEvent_share, .nkchgApps, .nkchgWall_post_more, .nkchgLikes, .nkchgComments {display: block;}"
          });
        chrome.tabs.executeScript(tabId, {code:
          "if (nkchgObserver) nkchgObserver.disconnect();" +
          "console.log('Чистые новости для VK.com: cleaning disabled');"
          });
      }
    }
  }
}

chrome.tabs.onUpdated.addListener(checkForValidUrl);
<|endoftext|>


<commit_before>// ******************** global variables ********************

// holds a list of blocked urls
var g_urls = [];
// true if data is loaded from local storage
var g_sync_local  = false;
// true if data is loaded from remote storage
var g_sync_remote = false;

// ******************** array functions ********************

function array_uniq(a){
  return a.filter(function (e, i, a) {
    return a.lastIndexOf(e) === i;
  }).sort();
}

function array_merge_uniq(a, b){
  return array_uniq(a).concat(
    array_uniq(b).filter(function (x){
      return a.indexOf(x) < 0;
    })
  ).sort();
}

// ******************** sync functions ********************

// load local data and sync with remote if available by merging them
function url_sync(){
  if (!g_sync_local){
    chrome.storage.local.get(function(ldata){
      if (typeof ldata.urls != "undefined"){
        g_urls = array_merge_uniq(ldata.urls || [], g_urls);
      }
      g_sync_local = true;
      url_store();
      url_sync_remote();
    });
  }
}

function url_sync_remote(){
  if (!g_sync_remote && chrome.storage.sync){
    chrome.storage.sync.get(function(sdata){
      if (sdata && typeof sdata.urls != "undefined"){
        g_urls = array_merge_uniq(sdata.urls, g_urls);
        g_sync_remote = true;
        url_store();
      }
    });
  }
}

function url_store(){
  if (g_sync_local){ chrome.storage.local.set({urls: g_urls}); }
  if (g_sync_remote){ chrome.storage.sync.set({urls: g_urls}); }
}

// ******************** url functions ********************

function url_test(u){
  return g_urls.indexOf(u) > -1;
}

function url_set(u){
  if (!url_test(u)){
    g_urls.push(u);
    url_store();
  }
}

function url_remove(u){
  if (url_test(u)){
    g_urls.splice(g_urls.indexOf(u), 1);
    url_store();
  }
}

function set_icon(flag){
  p = flag ? "icons/icon2.svg" : "icons/icon.svg"
  chrome.browserAction.setIcon({path: p});
}

// check if page is marked untrusted and set icons and return state
//   according to it
function state(url, flag){
  var flag_untrusted = false;
  var flag_icon = false;

  if (url){
    // get domain part of the url
    var u2 = url.match(/:\/\/(.[^/]+)/);
    if (u2){
      var u = u2[1];

      // url exists? if so, it means page is untrusted
      flag_untrusted = url_test(u);
      if (!flag_untrusted){
        if (flag){
          // mark page untrusted if icon was clicked
          url_set(u);
        }
        flag_icon = flag;
      }
      else{
        if (flag){
          // mark page trusted if icon was clicked
          url_remove(u);
        }
        flag_icon = !flag;
      }
    }

    // set toolbar state of icon
    set_icon(flag_icon)
  }

  return flag_untrusted;
}

// ******************** events ********************

// mark page untrusted or trusted when icon is clicked
chrome.browserAction.onClicked.addListener(
  function(details){
    state(details.url, 1);
    chrome.tabs.reload({bypassCache: true});
  }
);

// update icon state when switching tab
chrome.tabs.onActivated.addListener(
  function(details){
    // try to sync remote data from time to time on tab switch
    //   because the user might sign in the Sync service later only
    //   and I need to grab the remote data then
    url_sync_remote();

    chrome.tabs.query({currentWindow: true, active: true},
      function(tab){
        state(tab[0].url);
      }
    );
  }
);

// update icon state when the active page has finished loading
chrome.tabs.onUpdated.addListener(
  function(tabId, changeInfo, tab){
    if (tab.status == "complete" && tab.active) {
      url_sync_remote();
      state(tab.url);
    }
  }
);

// block scripts on page if url is marked untrsuted based on
//   whether url exists in storage
chrome.webRequest.onHeadersReceived.addListener(
  function(details){

    if (state(details.url)){

      // include the original response header too
      //   merging the two arrays here
      // the trick of blocking all scripts for a domain is
      //   adding CSP to the page header
      // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
      var rh = details.responseHeaders.concat(
        [{name: "Content-Security-Policy", value: "script-src 'none'"}]
      );
      return {responseHeaders: rh};
    }

    return {};
  },
  {urls: ["<all_urls>"], types: ["main_frame"]},
  ["blocking", "responseHeaders"]
);

// ******************** init ********************

// sync data on startup
url_sync();
<commit_msg>bug fix: update icon and check status when switching windows too<commit_after>// ******************** global variables ********************

// holds a list of blocked urls
var g_urls = [];
// true if data is loaded from local storage
var g_sync_local  = false;
// true if data is loaded from remote storage
var g_sync_remote = false;

// ******************** array functions ********************

function array_uniq(a){
  return a.filter(function (e, i, a) {
    return a.lastIndexOf(e) === i;
  }).sort();
}

function array_merge_uniq(a, b){
  return array_uniq(a).concat(
    array_uniq(b).filter(function (x){
      return a.indexOf(x) < 0;
    })
  ).sort();
}

// ******************** sync functions ********************

// load local data and sync with remote if available by merging them
function url_sync(){
  if (!g_sync_local){
    chrome.storage.local.get(function(ldata){
      if (typeof ldata.urls != "undefined"){
        g_urls = array_merge_uniq(ldata.urls || [], g_urls);
      }
      g_sync_local = true;
      url_store();
      url_sync_remote();
    });
  }
}

function url_sync_remote(){
  if (!g_sync_remote && chrome.storage.sync){
    chrome.storage.sync.get(function(sdata){
      if (sdata && typeof sdata.urls != "undefined"){
        g_urls = array_merge_uniq(sdata.urls, g_urls);
        g_sync_remote = true;
        url_store();
      }
    });
  }
}

function url_store(){
  if (g_sync_local){ chrome.storage.local.set({urls: g_urls}); }
  if (g_sync_remote){ chrome.storage.sync.set({urls: g_urls}); }
}

// ******************** url functions ********************

function url_test(u){
  return g_urls.indexOf(u) > -1;
}

function url_set(u){
  if (!url_test(u)){
    g_urls.push(u);
    url_store();
  }
}

function url_remove(u){
  if (url_test(u)){
    g_urls.splice(g_urls.indexOf(u), 1);
    url_store();
  }
}

function set_icon(flag){
  p = flag ? "icons/icon2.svg" : "icons/icon.svg"
  chrome.browserAction.setIcon({path: p});
}

// check if page is marked untrusted and set icons and return status
//   according to it
function status(url, flag){
  var flag_untrusted = false;
  var flag_icon = false;

  if (url){
    // get domain part of the url
    var u2 = url.match(/:\/\/(.[^/]+)/);
    if (u2){
      var u = u2[1];

      // url exists? if so, it means page is untrusted
      flag_untrusted = url_test(u);
      if (!flag_untrusted){
        if (flag){
          // mark page untrusted if icon was clicked
          url_set(u);
        }
        flag_icon = flag;
      }
      else{
        if (flag){
          // mark page trusted if icon was clicked
          url_remove(u);
        }
        flag_icon = !flag;
      }
    }

    // set state of toolbar icon
    set_icon(flag_icon)
  }

  return flag_untrusted;
}

// ******************** events ********************

// mark page untrusted or trusted when icon is clicked
chrome.browserAction.onClicked.addListener(
  function(details){
    status(details.url, 1);
    chrome.tabs.reload({bypassCache: true});
  }
);

// update icon and check status when switching tabs
chrome.tabs.onActivated.addListener(
  function(details){
    // try to sync remote data from time to time on tab switch
    //   because the user might sign in the Sync service later only
    //   and I need to grab the remote data then
    url_sync_remote();

    chrome.tabs.query({currentWindow: true, active: true},
      function(tabs){
        status(tabs[0].url);
      }
    );
  }
);

// update icon and check status when switching windows
// this check is necessary for android because it has no windows
if (chrome.windows){
  chrome.windows.onFocusChanged.addListener(
    function(winid){
      if (winid != chrome.windows.WINDOW_ID_NONE){
        chrome.tabs.query({windowId: winid, active: true},
          function(tabs){
            status(tabs[0].url);
          }
        );
      }
    }
  );
}

// update icon and check status when the active page has finished loading
chrome.tabs.onUpdated.addListener(
  function(tabId, changeInfo, tab){
    if (tab.status == "complete" && tab.active) {
      url_sync_remote();
      status(tab.url);
    }
  }
);

// block scripts on page if url is marked untrsuted based on
//   whether url exists in storage
chrome.webRequest.onHeadersReceived.addListener(
  function(details){

    if (status(details.url)){

      // include the original response header too
      //   merging the two arrays here
      // the trick of blocking all scripts for a domain is
      //   adding CSP to the page header
      // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
      var rh = details.responseHeaders.concat(
        [{name: "Content-Security-Policy", value: "script-src 'none'"}]
      );
      return {responseHeaders: rh};
    }

    return {};
  },
  {urls: ["<all_urls>"], types: ["main_frame"]},
  ["blocking", "responseHeaders"]
);

// ******************** init ********************

// sync data on startup
url_sync();
<|endoftext|>