| Index: lib/devtools.js | 
| =================================================================== | 
| new file mode 100644 | 
| --- /dev/null | 
| +++ b/lib/devtools.js | 
| @@ -0,0 +1,267 @@ | 
| +/* | 
| + * This file is part of Adblock Plus <https://adblockplus.org/>, | 
| + * Copyright (C) 2006-2015 Eyeo GmbH | 
| + * | 
| + * Adblock Plus is free software: you can redistribute it and/or modify | 
| + * it under the terms of the GNU General Public License version 3 as | 
| + * published by the Free Software Foundation. | 
| + * | 
| + * Adblock Plus is distributed in the hope that it will be useful, | 
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| + * GNU General Public License for more details. | 
| + * | 
| + * You should have received a copy of the GNU General Public License | 
| + * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
| + */ | 
| + | 
| +let info = require("info"); | 
| +if (info.platform == "chromium") | 
| +{ | 
| +  let {WhitelistFilter, ElemHideFilter} = require("filterClasses"); | 
| +  let {SpecialSubscription} = require("subscriptionClasses"); | 
| +  let {FilterStorage} = require("filterStorage"); | 
| +  let {defaultMatcher} = require("matcher"); | 
| +  let {FilterNotifier} = require("filterNotifier"); | 
| +  let {stringifyURL, isThirdParty} = require("url"); | 
| + | 
| +  // Mapping of inspected tabs to their devpanel page | 
| +  // and recorded items. We can't use a PageMap here, | 
| +  // because data must persist after navigation/reload. | 
| +  let panels = Object.create(null); | 
| + | 
| +  function getRequestInfo(request) | 
| +  { | 
| +    return { | 
| +      url: request.url && stringifyURL(request.url), | 
| +      type: request.type, | 
| +      docDomain: request.docDomain | 
| +    }; | 
| +  } | 
| + | 
| +  function getFilterInfo(filter) | 
| +  { | 
| +    if (!filter) | 
| +      return null; | 
| + | 
| +    let userDefined = false; | 
| +    let subscriptionTitle = null; | 
| + | 
| +    for (let subscription of filter.subscriptions) | 
| +    { | 
| +      if (!subscription.disabled) | 
| +      { | 
| +        if (subscription instanceof SpecialSubscription) | 
| +          userDefined = true; | 
| +        else | 
| +          subscriptionTitle = subscription.title; | 
| +      } | 
| +    } | 
| + | 
| +    return { | 
| +      text: filter.text, | 
| +      whitelisted: filter instanceof WhitelistFilter, | 
| +      userDefined: userDefined, | 
| +      subscription: subscriptionTitle | 
| +    }; | 
| +  } | 
| + | 
| +  function addRecord(panel, request, filter) | 
| +  { | 
| +    panel.page.sendMessage({ | 
| +      type: "add-record", | 
| +      request: getRequestInfo(request), | 
| +      filter: getFilterInfo(filter) | 
| +    }); | 
| + | 
| +    panel.records.push({ | 
| +      request: request, | 
| +      filter: filter | 
| +    }); | 
| +  } | 
| + | 
| +  function matchRequest(request) | 
| +  { | 
| +    return defaultMatcher.matchesAny( | 
| +      stringifyURL(request.url), | 
| +      request.type, | 
| +      request.docDomain, | 
| +      isThirdParty(request.url, request.docDomain), | 
| +      request.sitekey | 
| +    ); | 
| +  } | 
| + | 
| +  /** | 
| +   * Logs a request in the devtools panel. | 
| +   * | 
| +   * @param {Page}   [page]      The page the request occured on | 
| +   * @param {URL]    [url]       The URL of the request | 
| +   * @param {string} [type]      The request type | 
| +   * @param {string} [docDomain] The IDN-decoded hostname of the document | 
| +   * @param {string} [sitekey]   The active sitekey if there is any | 
| +   * @param {filter} [filter]    The matched filter or null if there is no match | 
| +   */ | 
| +  function logRequest(page, url, type, docDomain, sitekey, filter) | 
| +  { | 
| +    let panel = panels[page._id]; | 
| +    if (panel) | 
| +    { | 
| +      let request = { | 
| +        url: url, | 
| +        type: type, | 
| +        docDomain: docDomain, | 
| +        sitekey: sitekey | 
| +      }; | 
| + | 
| +      addRecord(panel, request, filter); | 
| +    } | 
| +  } | 
| +  exports.logRequest = logRequest; | 
| + | 
| +  /** | 
| +   * Logs active element hiding filters in the devtools panel. | 
| +   * | 
| +   * @param {Page}     [page]      The page the elements were hidden on | 
| +   * @param {string[]} [selectors] The CSS selectors of active elemhide filters | 
| +   * @param {string}   [docDomain] The IDN-decoded hostname of the document | 
| +   */ | 
| +  function logHiddenElements(page, selectors, docDomain) | 
| +  { | 
| +    let panel = panels[page._id]; | 
| +    if (panel) | 
| +    { | 
| +      for (let subscription of FilterStorage.subscriptions) | 
| +      { | 
| +        if (subscription.disabled) | 
| +          continue; | 
| + | 
| +        for (let filter of subscription.filters) | 
| +        { | 
| +          if (!(filter instanceof ElemHideFilter)) | 
| +            continue; | 
| +          if (selectors.indexOf(filter.selector) == -1) | 
| +            continue; | 
| +          if (!filter.isActiveOnDomain(docDomain)) | 
| +            continue; | 
| +          if (panel.records.some(record => record.request.type == "ELEMHIDE" && | 
| +                                           record.request.docDomain == docDomain && | 
| +                                           record.filter.selector == filter.selector)) | 
| +            continue; | 
| + | 
| +          addRecord(panel, {type: "ELEMHIDE", docDomain: docDomain}, filter); | 
| +        } | 
| +      } | 
| +    } | 
| +  }; | 
| +  exports.logHiddenElements = logHiddenElements; | 
| + | 
| +  /** | 
| +   * Starts to record items and shows them in the devtools panel. | 
| +   * | 
| +   * @param {Page}   [panelPage]     The page of the the devtools panel | 
| +   * @param {number} [inpectedTabId] The ID of the tab to record item from | 
| +   */ | 
| +  function initDevToolsPanel(panelPage, inpectedTabId) | 
| +  { | 
| +    panels[inpectedTabId] = {page: panelPage, records: []}; | 
| + | 
| +    // Forget about this devtools panel and the items recorded there | 
| +    // so far, when the panel (not the tab it is inspecting) is closed. | 
| +    function onRemoved(tabId) | 
| +    { | 
| +      if (tabId == panelPage._id) | 
| +      { | 
| +        delete panels[inpectedTabId]; | 
| +        chrome.tabs.onRemoved.removeListener(onRemoved); | 
| +      } | 
| +    } | 
| +    chrome.tabs.onRemoved.addListener(onRemoved); | 
| +  } | 
| +  exports.initDevToolsPanel = initDevToolsPanel; | 
| + | 
| +  /** | 
| +   * Checks whether a page is inspected by the devtools panel. | 
| +   * | 
| +   * @param {Page} [page] | 
| +   * @return {Boolean} | 
| +   */ | 
| +  function hasDevToolsPanel(page) | 
| +  { | 
| +    return page._id in panels; | 
| +  } | 
| +  exports.hasDevToolsPanel = hasDevToolsPanel; | 
| + | 
| +  chrome.webNavigation.onBeforeNavigate.addListener(function(details) | 
| +  { | 
| +    let panel = panels[details.tabId]; | 
| +    if (panel && details.frameId == 0) | 
| +    { | 
| +      // We have to flush the in-memory cache on page load. | 
| +      // Otherwise requests answered from the in-memory cache | 
| +      // will not be shown in the devtools panel. | 
| +      chrome.webRequest.handlerBehaviorChanged(); | 
| + | 
| +      panel.records = []; | 
| +      panel.page.sendMessage({type: "reset"}); | 
| +    } | 
| +  }); | 
| + | 
| +  FilterNotifier.addListener(function(action, filter) | 
| +  { | 
| +    if (action != "filter.added" && action != "filter.removed") | 
| +      return; | 
| + | 
| +    for (let tabId in panels) | 
| +    { | 
| +      let panel = panels[tabId]; | 
| + | 
| +      for (let i = 0; i < panel.records.length; i++) | 
| +      { | 
| +        let record = panel.records[i]; | 
| + | 
| +        if (action == "filter.added") | 
| +        { | 
| +          if (record.request.type == "ELEMHIDE") | 
| +            continue; | 
| + | 
| +          if (matchRequest(record.request) != filter) | 
| +            continue; | 
| + | 
| +          record.filter = filter; | 
| +        } | 
| + | 
| +        if (action == "filter.removed") | 
| +        { | 
| +          if (record.filter != filter) | 
| +            continue; | 
| + | 
| +          if (record.request.type == "ELEMHIDE") | 
| +          { | 
| +            panel.page.sendMessage({ | 
| +              type: "remove-record", | 
| +              index: i | 
| +            }); | 
| +            panel.records.splice(i--, 1); | 
| +            continue; | 
| +          } | 
| + | 
| +          record.filter = matchRequest(record.request); | 
| +        } | 
| + | 
| +        panel.page.sendMessage({ | 
| +          type: "update-record", | 
| +          index: i, | 
| +          request: getRequestInfo(record.request), | 
| +          filter: getFilterInfo(record.filter) | 
| +        }); | 
| +      } | 
| +    } | 
| +  }); | 
| +} | 
| +else | 
| +{ | 
| +  exports.logRequest        = () => {}; | 
| +  exports.logHiddenElements = () => {}; | 
| +  exports.initDevToolsPanel = () => {}; | 
| +  exports.hasDevToolsPanel  = () => false; | 
| +} | 
|  |