Browse Source

Add web request monitor and network error based conditions. Fix #6.

FelisCatus 10 years ago
parent
commit
62133c2d7c

+ 22 - 0
omega-locales/cs/LC_MESSAGES/omega-web.po

@@ -273,6 +273,16 @@ msgstr "Proxy Změny"
 msgid "options_revertProxyChanges"
 msgstr "Reverzní proxy změněna jinou aplikací."
 
+msgid "options_group_networkRequests"
+msgstr "Network Requests"
+
+msgid "options_monitorWebRequests"
+msgstr "Show count of failed web requests for resources in the current tab."
+
+msgid "options_monitorWebRequestsHelp"
+msgstr "A yellow badge will be displayed on the icon if some resources fail to load,<br>"
+"and you can set the profile for such resources conveniently via the popup menu."
+
 msgid "options_downloadOptions"
 msgstr "Nastavení stahování"
 
@@ -995,6 +1005,18 @@ msgstr "Report problémů"
 msgid "popup_errorLog"
 msgstr "Protokol chyb"
 
+msgid "popup_requestErrorCount"
+msgstr "$COUNT$ failed resources"
+
+msgid "popup_requestErrorWarning"
+msgstr "A few resources failed to load due to network issues."
+
+msgid "popup_requestErrorAddCondition"
+msgstr "You can review the following domains and use proxy for them when appropriate."
+
+msgid "options_resultProfileForSelectedDomains"
+msgstr "Use this profile for all selected domains"
+
 msgid "popup_issueTemplate"
 msgstr ""
 "\n"

+ 22 - 0
omega-locales/en_US/LC_MESSAGES/omega-web.po

@@ -274,6 +274,16 @@ msgstr "Proxy Changes"
 msgid "options_revertProxyChanges"
 msgstr "Revert proxy changes done by other apps."
 
+msgid "options_group_networkRequests"
+msgstr "Network Requests"
+
+msgid "options_monitorWebRequests"
+msgstr "Show count of failed web requests for resources in the current tab."
+
+msgid "options_monitorWebRequestsHelp"
+msgstr "A yellow badge will be displayed on the icon if some resources fail to load,<br>"
+"and you can set the profile for such resources conveniently via the popup menu."
+
 msgid "options_downloadOptions"
 msgstr "Download Options"
 
@@ -989,6 +999,18 @@ msgstr "Report issues"
 msgid "popup_errorLog"
 msgstr "Error log"
 
+msgid "popup_requestErrorCount"
+msgstr "$COUNT$ failed resources"
+
+msgid "popup_requestErrorWarning"
+msgstr "A few resources failed to load due to network issues."
+
+msgid "popup_requestErrorAddCondition"
+msgstr "You can review the following domains and use proxy for them when appropriate."
+
+msgid "options_resultProfileForSelectedDomains"
+msgstr "Use this profile for all selected domains"
+
 msgid "popup_issueTemplate"
 msgstr ""
 "\n"

+ 22 - 0
omega-locales/tr/LC_MESSAGES/omega-web.po

@@ -277,6 +277,16 @@ msgstr "Proxy Changes"
 msgid "options_revertProxyChanges"
 msgstr "Revert proxy changes done by other apps."
 
+msgid "options_group_networkRequests"
+msgstr "Network Requests"
+
+msgid "options_monitorWebRequests"
+msgstr "Show count of failed web requests for resources in the current tab."
+
+msgid "options_monitorWebRequestsHelp"
+msgstr "A yellow badge will be displayed on the icon if some resources fail to load,<br>"
+"and you can set the profile for such resources conveniently via the popup menu."
+
 msgid "options_downloadOptions"
 msgstr "Download Options"
 
@@ -1003,6 +1013,18 @@ msgstr "Report issues"
 msgid "popup_errorLog"
 msgstr "Error log"
 
+msgid "popup_requestErrorCount"
+msgstr "$COUNT$ failed resources"
+
+msgid "popup_requestErrorWarning"
+msgstr "A few resources failed to load due to network issues."
+
+msgid "popup_requestErrorAddCondition"
+msgstr "You can review the following domains and use proxy for them when appropriate."
+
+msgid "options_resultProfileForSelectedDomains"
+msgstr "Use this profile for all selected domains"
+
 msgid "popup_issueTemplate"
 msgstr ""
 "\n"

+ 22 - 0
omega-locales/zh_CN/LC_MESSAGES/omega-web.po

@@ -247,6 +247,16 @@ msgstr "代理设置变化"
 msgid "options_revertProxyChanges"
 msgstr "撤消其他扩展对代理的更改。"
 
+msgid "options_group_networkRequests"
+msgstr "网络请求"
+
+msgid "options_monitorWebRequests"
+msgstr "在图标上显示当前页面中由于网络原因而未加载的资源数量。"
+
+msgid "options_monitorWebRequestsHelp"
+msgstr "启用此选项后,如有资源加载失败,则图标上会显示数字提示。<br>"
+"此时,您可以通过弹出菜单一次设置这些资源使用的情景模式,操作十分便捷。"
+
 msgid "options_downloadOptions"
 msgstr "下载选项"
 
@@ -868,6 +878,18 @@ msgstr "反馈问题"
 msgid "popup_errorLog"
 msgstr "错误日志"
 
+msgid "popup_requestErrorCount"
+msgstr "$COUNT$个资源未加载"
+
+msgid "popup_requestErrorWarning"
+msgstr "由于网络原因,此页面部分资源加载失败。"
+
+msgid "popup_requestErrorAddCondition"
+msgstr "您可以查看以下域名,并根据实际情况确定是否对其使用代理。"
+
+msgid "options_resultProfileForSelectedDomains"
+msgstr "对所有选中域名使用此情景模式:"
+
 msgid "popup_issueTemplate"
 msgstr ""
 "\n"

+ 22 - 0
omega-locales/zh_TW/LC_MESSAGES/omega-web.po

@@ -247,6 +247,16 @@ msgstr "代理設定變化"
 msgid "options_revertProxyChanges"
 msgstr "撤消其他擴展對代理的更改。"
 
+msgid "options_group_networkRequests"
+msgstr "網路請求"
+
+msgid "options_monitorWebRequests"
+msgstr "在圖示上顯示當前頁面中由於網路原因而未載入的資源數量。"
+
+msgid "options_monitorWebRequestsHelp"
+msgstr "啟用此選項後,如有資源載入失敗,則圖示上會顯示數字提示。<br>"
+"此時,您可以通過彈出選單一次設定這些資源使用的情景模式,操作十分便捷。"
+
 msgid "options_downloadOptions"
 msgstr "下載選項"
 
@@ -868,6 +878,18 @@ msgstr "反饋問題"
 msgid "popup_errorLog"
 msgstr "錯誤日誌"
 
+msgid "popup_requestErrorCount"
+msgstr "$COUNT$個資源未載入"
+
+msgid "popup_requestErrorWarning"
+msgstr "由於網路原因,此頁面部分資源載入失敗。"
+
+msgid "popup_requestErrorAddCondition"
+msgstr "您可以檢視以下域名,並根據實際情況確定是否對其使用代理。"
+
+msgid "options_resultProfileForSelectedDomains"
+msgstr "對所有選中域名使用此情景模式:"
+
 msgid "popup_issueTemplate"
 msgstr ""
 "\n"

+ 2 - 0
omega-target-chromium-extension/grunt-po2crx.coffee

@@ -32,6 +32,8 @@ module.exports = (grunt) ->
             for i in [0...refs.length]
               placeholder = refs[i] ? ('_unused_' + i)
               placeholders[placeholder] = {content: '$' + i}
+          if message == ' '
+            message = ''
           result[key] =
             message: message
             placeholders: placeholders

+ 1 - 0
omega-target-chromium-extension/index.coffee

@@ -4,6 +4,7 @@ module.exports =
   ChromeTabs: require('./src/tabs')
   SwitchySharp: require('./src/switchysharp')
   ExternalApi: require('./src/external_api.coffee')
+  WebRequestMonitor: require('./src/web_request_monitor')
   Inspect: require('./src/inspect')
   Url: require('url')
 

+ 11 - 0
omega-target-chromium-extension/omega_target_web.coffee

@@ -29,10 +29,16 @@ angular.module('omegaTarget', []).factory 'omegaTarget', ($q) ->
         d.resolve(response.result)
     )
     return d.promise
+  connectBackground = (name, message, callback) ->
+    port = chrome.runtime.connect({name: name})
+    port.postMessage(message)
+    port.onMessage.addListener(callback)
+    return
 
   isChromeUrl = (url) -> url.substr(0, 6) == 'chrome'
 
   optionsChangeCallback = []
+  requestInfoCallback = null
   prefix = 'omega.local.'
   urlParser = document.createElement('a')
   omegaTarget =
@@ -115,6 +121,9 @@ angular.module('omegaTarget', []).factory 'omegaTarget', ($q) ->
         if not tabs[0]?.url
           d.resolve(undefined)
           return
+        if tabs[0].id and requestInfoCallback
+          connectBackground('tabRequestInfo', {tabId: tabs[0].id},
+            requestInfoCallback)
         getBadge = $q['defer']()
         chrome.browserAction.getBadgeText {tabId: tabs[0]?.id}, (result) ->
           getBadge.resolve(result)
@@ -148,5 +157,7 @@ angular.module('omegaTarget', []).factory 'omegaTarget', ($q) ->
       chrome.tabs.create url: 'chrome://extensions/configureCommands'
     setOptionsSync: (enabled, args) ->
       callBackground('setOptionsSync', enabled, args)
+    setRequestInfoCallback: (callback) ->
+      requestInfoCallback = callback
 
   return omegaTarget

+ 1 - 0
omega-target-chromium-extension/package.json

@@ -20,6 +20,7 @@
     "po2json": "^0.3.2"
   },
   "dependencies": {
+    "heap": "^0.2.6",
     "omega-target": "../omega-target",
     "omega-web": "../omega-web",
     "xhr": "^1.16.0"

+ 35 - 0
omega-target-chromium-extension/src/options.coffee

@@ -7,6 +7,7 @@ chromeApiPromisifyAll = require('./chrome_api')
 proxySettings = chromeApiPromisifyAll(chrome.proxy.settings)
 parseExternalProfile = require('./parse_external_profile')
 ProxyAuth = require('./proxy_auth')
+WebRequestMonitor = require('./web_request_monitor')
 
 class ChromeOptions extends OmegaTarget.Options
   _inspect: null
@@ -188,6 +189,40 @@ class ChromeOptions extends OmegaTarget.Options
         @_inspect.disable()
     return Promise.resolve()
 
+  _requestMonitor: null
+  _monitorWebRequests: false
+  _tabRequestInfoPorts: null
+  setMonitorWebRequests: (enabled) ->
+    @_monitorWebRequests = enabled
+    if enabled and not @_requestMonitor?
+      @_tabRequestInfoPorts = {}
+      @_requestMonitor = new WebRequestMonitor()
+      @_requestMonitor.watchTabs (tabId, info, req, event) =>
+        return unless @_monitorWebRequests
+        if info.errorCount > 0
+          badge = {text: info.errorCount.toString(), color: '#f0ad4e'}
+          chrome.browserAction.setBadgeText(text: badge.text, tabId: tabId)
+          chrome.browserAction.setBadgeBackgroundColor(
+            color: badge.color
+            tabId: tabId
+          )
+        else
+          chrome.browserAction.setBadgeText(text: '', tabId: tabId)
+        @_tabRequestInfoPorts[tabId]?.postMessage(
+          @_requestMonitor.summarizeErrors(info))
+
+      chrome.runtime.onConnect.addListener (port) =>
+        return unless port.name == 'tabRequestInfo'
+        return unless @_monitorWebRequests
+        tabId = null
+        port.onMessage.addListener (msg) =>
+          tabId = msg.tabId
+          @_tabRequestInfoPorts[tabId] = port
+          info = @_requestMonitor.tabInfo[tabId]
+          port.postMessage(@_requestMonitor.summarizeErrors(info)) if info
+        port.onDisconnect.addListener =>
+          delete @_tabRequestInfoPorts[tabId] if tabId?
+
   _alarms: null
   schedule: (name, periodInMinutes, callback) ->
     name = 'omega.' + name

+ 173 - 0
omega-target-chromium-extension/src/web_request_monitor.coffee

@@ -0,0 +1,173 @@
+Heap = require('heap')
+Url = require('url')
+
+module.exports = class WebRequestMonitor
+  constructor: ->
+    @_requests = {}
+    @_recentRequests = new Heap((a, b) -> a._startTime - b._startTime)
+    @_callbacks = []
+    @_tabCallbacks = []
+    @tabInfo = {}
+
+  _callbacks: null
+  watching: false
+  timer: null
+  watch: (callback) ->
+    @_callbacks.push(callback)
+    return if @watching
+    if not chrome.webRequest
+      console.log('Request monitor disabled! No webRequest permission.')
+      return
+    chrome.webRequest.onBeforeRequest.addListener(
+      @_requestStart.bind(this)
+      {urls: ['<all_urls>']}
+    )
+    chrome.webRequest.onHeadersReceived.addListener(
+      @_requestHeadersReceived.bind(this)
+      {urls: ['<all_urls>']}
+    )
+    chrome.webRequest.onBeforeRedirect.addListener(
+      @_requestRedirected.bind(this)
+      {urls: ['<all_urls>']}
+    )
+    chrome.webRequest.onCompleted.addListener(
+      @_requestDone.bind(this)
+      {urls: ['<all_urls>']}
+    )
+    chrome.webRequest.onErrorOccurred.addListener(
+      @_requestError.bind(this)
+      {urls: ['<all_urls>']}
+    )
+    @watching = true
+
+  _requests: null
+  _recentRequests: null
+
+  _requestStart: (req) ->
+    return if req.tabId < 0
+    req._startTime = Date.now()
+    @_requests[req.requestId] = req
+    @_recentRequests.push(req)
+    @timer ?= setInterval(@_tick.bind(this), 1000)
+    for callback in @_callbacks
+      callback('start', req)
+
+  _tick: ->
+    now = Date.now()
+    while (req = @_recentRequests.peek())
+      reqInfo = @_requests[req.requestId]
+      if reqInfo and not reqInfo.noTimeout
+        if now - req._startTime < 5000
+          break
+        else
+          reqInfo.timeoutCalled = true
+          for callback in @_callbacks
+            callback('timeout', reqInfo)
+      @_recentRequests.pop()
+
+  _requestHeadersReceived: (req) ->
+    reqInfo = @_requests[req.requestId]
+    return unless reqInfo
+    reqInfo.noTimeout = true
+    if reqInfo.timeoutCalled
+      for callback in @_callbacks
+        callback('ongoing', req)
+
+  _requestRedirected: (req) ->
+    if req.url.indexOf('data:') == 0
+      @_requestDone(req)
+
+  _requestError: (req) ->
+    reqInfo = @_requests[req.requestId]
+    delete @_requests[req.requestId]
+
+    return if req.tabId < 0
+    return if req.error.indexOf('BLOCKED') >= 0
+    return unless reqInfo
+    if req.error == 'net::ERR_ABORTED'
+      if reqInfo.timeoutCalled and not reqInfo.noTimeout
+        for callback in @_callbacks
+          callback('timeoutAbort', req)
+      return
+    for callback in @_callbacks
+      callback('error', req)
+
+  _requestDone: (req) ->
+    for callback in @_callbacks
+      callback('done', req)
+    delete @_requests[req.requestId]
+
+  eventCategory:
+    start: 'ongoing'
+    ongoing: 'ongoing'
+    timeout: 'error'
+    error: 'error'
+    timeoutAbort: 'error'
+    done: 'done'
+
+  tabsWatching: false
+  _tabCallbacks: null
+
+  watchTabs: (callback) ->
+    @_tabCallbacks.push(callback)
+    return if @tabsWatching
+    @watch(@setTabRequestInfo.bind(this))
+    @tabsWatching = true
+    chrome.tabs.onCreated.addListener (tab) =>
+      return unless tab.id
+      @tabInfo[tab.id] = @_newTabInfo()
+    chrome.tabs.onRemoved.addListener (tab) =>
+      delete @tabInfo[tab.id]
+    chrome.tabs.onReplaced?.addListener (added, removed) =>
+      @tabInfo[added] ?= @_newTabInfo()
+      delete @tabInfo[removed]
+    chrome.tabs.query {}, (tabs) =>
+      for tab in tabs
+        @tabInfo[tab.id] ?= @_newTabInfo()
+
+  _newTabInfo: -> {
+    requests: {}
+    requestCount: 0
+    requestStatus: {}
+
+    ongoingCount: 0
+    errorCount: 0
+    doneCount: 0
+  }
+
+  setTabRequestInfo: (status, req) ->
+    info = @tabInfo[req.tabId]
+    if info
+      if status == 'start' and req.type == 'main_frame'
+        info = @tabInfo[req.tabId] = @_newTabInfo()
+      info.requests[req.requestId] = req
+      if (oldStatus = info.requestStatus[req.requestId])
+        info[@eventCategory[oldStatus] + 'Count']--
+      else
+        info.requestCount++
+      info.requestStatus[req.requestId] = status
+      info[@eventCategory[status] + 'Count']++
+      for callback in @_tabCallbacks
+        callback(req.tabId, info, req, status)
+
+  summarizeErrors: (info) ->
+    domains = []
+    domainInfoByName = {}
+    for reqId, req of info.requests
+      if @eventCategory[info.requestStatus[reqId]] == 'error'
+        domain = Url.parse(req.url).hostname
+        domainInfo = domainInfoByName[domain]
+        if not domainInfo
+          domainInfo = domainInfoByName[domain] = {
+            domain: domain
+            errorCount: 0
+            type: 'other'
+          }
+          domains.push(domainInfo)
+        domainInfo.errorCount++
+        domainInfo.type = req.type
+    domains.sort (a, b) -> b.errorCount - a.errorCount
+    return {
+      errorCount: info.errorCount
+      domains: domains
+    }

+ 30 - 11
omega-target/src/options.coffee

@@ -368,6 +368,12 @@ class Options
           showMenu = true
           @_setOptions({'-showInspectMenu': true}, {persist: true})
         @setInspect(showMenu: showMenu)
+      if changes['-monitorWebRequests']? or changes == @_options
+        monitorWebRequests = @_options['-monitorWebRequests']
+        if not monitorWebRequests?
+          monitorWebRequests = true
+          @_setOptions({'-monitorWebRequests': true}, {persist: true})
+        @setMonitorWebRequests(monitorWebRequests)
 
     handler()
     @_storage.watch null, handler
@@ -395,6 +401,14 @@ class Options
   ###
   setInspect: -> Promise.resolve()
 
+  ###*
+  # Apply the settings related to web request monitoring.
+  # In base class, this method is not implemented and will not do anything.
+  # @param {boolean} enabled Whether network shall be monitored or not
+  # @returns {Promise} A promise which is fulfilled when the settings apply
+  ###
+  setMonitorWebRequests: -> Promise.resolve()
+
   ###*
   # @callback watchCallback
   # @param {Object.<string, {}>} changes A map from keys to values.
@@ -806,18 +820,23 @@ class Options
     target = OmegaPac.Profiles.byName(profileName, @_options)
     if not target?
       return Promise.reject new ProfileNotExistError(profileName)
-    # Try to remove rules with the same condition first.
-    tag = OmegaPac.Conditions.tag(condition)
-    for i in [0...profile.rules.length]
-      if OmegaPac.Conditions.tag(profile.rules[i].condition) == tag
-        profile.rules.splice(i, 1)
-        break
+    if not Array.isArray(condition)
+      condition = [condition]
+
+    for cond in condition
+      # Try to remove rules with the same condition first.
+      tag = OmegaPac.Conditions.tag(cond)
+      for i in [0...profile.rules.length]
+        if OmegaPac.Conditions.tag(profile.rules[i].condition) == tag
+          profile.rules.splice(i, 1)
+          break
+
+      # Add the new rule to the start so that it won't be shadowed by others.
+      profile.rules.unshift({
+        condition: cond
+        profileName: profileName
+      })
 
-    # Add the new rule to the beginning so that it won't be shadowed by others.
-    profile.rules.unshift({
-      condition: condition
-      profileName: profileName
-    })
     OmegaPac.Profiles.updateRevision(profile)
     changes = {}
     changes[OmegaPac.Profiles.nameAsKey(profile)] = profile

+ 46 - 9
omega-web/src/coffee/popup.coffee

@@ -8,13 +8,16 @@ module.filter 'dispName', (omegaTarget) ->
       name = name.name
     omegaTarget.getMessage('profile_' + name) || name
 
+moveUp = (activeIndex, items) ->
+  i = activeIndex - 1
+  if i >= 0
+    items.eq(i)[0]?.focus()
+moveDown = (activeIndex, items) -> items.eq(activeIndex + 1)[0]?.focus()
 shortcutKeys =
-  38: (activeIndex, items) -> # Up
-    i = activeIndex - 1
-    if i >= 0
-      items.eq(i)[0]?.focus()
-  40: (activeIndex, items) -> # Down
-    items.eq(activeIndex + 1)[0]?.focus()
+  38: moveUp # Up
+  40: moveDown # Down
+  74: moveDown # j
+  75: moveUp # k
   48: '+direct' # 0
   83: '+system' # s
   191: 'help' # /
@@ -27,6 +30,7 @@ shortcutKeys =
   79: 'option' # o
   73: 'issue' # i
   76: 'log' # l
+  82: 'requestInfo' # r
 
 for i in [1..9]
   shortcutKeys[48 + i] = i
@@ -61,6 +65,7 @@ jQuery(document).on 'keydown', (e) ->
             'option': 'O'
             'issue': 'I'
             'log': 'L'
+            'requestInfo': 'R'
           for shortcut, key of keys
             showHelp(shortcut, key)
           customProfiles().each (i, el) ->
@@ -148,6 +153,7 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
   $scope.addTempRule = (domain, profileName) ->
     $scope.tempRuleMenu.open = false
     omegaTarget.addTempRule(domain, profileName).then ->
+      omegaTarget.state('lastProfileNameForCondition', profileName)
       refresh()
 
   $scope.setDefaultProfile = (profileName, defaultProfileName) ->
@@ -156,6 +162,16 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
   
   $scope.addCondition = (condition, profileName) ->
     omegaTarget.addCondition(condition, profileName).then ->
+      omegaTarget.state('lastProfileNameForCondition', profileName)
+      refresh()
+
+  $scope.addConditionForDomains = (domains, profileName) ->
+    conditions = Object.keys(domains).map (domain) -> {
+      conditionType: 'HostWildcardCondition'
+      pattern: '*.' + domain
+    }
+    omegaTarget.addCondition(conditions, profileName).then ->
+      omegaTarget.state('lastProfileNameForCondition', profileName)
       refresh()
   
   $scope.validateProfileName =
@@ -170,13 +186,15 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
         omegaTarget.applyProfile(name).then ->
           refresh()
 
+  preselectedProfileNameForCondition = 'direct'
+
   omegaTarget.state([
     'availableProfiles', 'currentProfileName', 'isSystemProfile',
     'validResultProfiles', 'refreshOnProfileChange', 'externalProfile',
-    'proxyNotControllable'
+    'proxyNotControllable', 'lastProfileNameForCondition'
   ]).then ([availableProfiles, currentProfileName, isSystemProfile,
     validResultProfiles, refresh, externalProfile,
-    proxyNotControllable]) ->
+    proxyNotControllable, lastProfileNameForCondition]) ->
     $scope.proxyNotControllable = proxyNotControllable
     return if proxyNotControllable
     $scope.availableProfiles = availableProfiles
@@ -198,6 +216,11 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
 
     $scope.validResultProfiles = profilesByNames(validResultProfiles)
 
+    if lastProfileNameForCondition
+      for profile in $scope.validResultProfiles
+        if profile.name == lastProfileNameForCondition
+          preselectedProfileNameForCondition = lastProfileNameForCondition
+
     $scope.builtinProfiles = []
     $scope.customProfiles = []
     for own key, profile of availableProfiles
@@ -211,9 +234,21 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
 
     $scope.customProfiles.sort(profileOrder)
 
+  $scope.domainsForCondition = {}
+  $scope.requestInfoProvided = null
+  omegaTarget.setRequestInfoCallback (info) ->
+    $scope.$apply ->
+      $scope.requestInfo = info
+      $scope.requestInfoProvided ?= (info?.domains.length > 0)
+      for domain in info.domains
+        $scope.domainsForCondition[domain.domain] ?= true
+      $scope.profileForDomains ?= preselectedProfileNameForCondition
+
   omegaTarget.getActivePageInfo().then((info) ->
     if info
       $scope.currentTempRuleProfile = info.tempRuleProfileName
+      if $scope.currentTempRuleProfile
+        preselectedProfileNameForCondition = $scope.currentTempRuleProfile
       $scope.currentDomain = info.domain
     else
       $q.reject()
@@ -234,6 +269,8 @@ module.controller 'PopupCtrl', ($scope, $window, $q, omegaTarget,
         condition:
           conditionType: 'HostWildcardCondition'
           pattern: conditionSuggestion['HostWildcardCondition']
-        profileName: $scope.currentTempRuleProfile ? 'direct'
+        profileName: preselectedProfileNameForCondition
       $scope.$watch 'rule.condition.conditionType', (type) ->
         $scope.rule.condition.pattern = conditionSuggestion[type]
+    else
+      $scope.requestInfoProvided = false

+ 4 - 0
omega-web/src/less/popup.less

@@ -152,3 +152,7 @@ legend,
     margin-bottom: 0;
   }
 }
+
+.request-info-details {
+  min-width: 360px;
+}

+ 7 - 0
omega-web/src/partials/general.jade

@@ -6,6 +6,13 @@ section.settings-group
     label
       input#revert-proxy-changes(type='checkbox' ng-model='options["-revertProxyChanges"]')
       span {{'options_revertProxyChanges' | tr}}
+section.settings-group
+  h3 {{'options_group_networkRequests' | tr}}
+  div.checkbox
+    label
+      input#revert-proxy-changes(type='checkbox' ng-model='options["-monitorWebRequests"]')
+      span {{'options_monitorWebRequests' | tr}}
+    p.help-block(omega-html="'options_monitorWebRequestsHelp' | tr")
 section.settings-group
   h3 {{'options_downloadOptions' | tr}}
   p.help-block {{'options_downloadOptionsHelp' | tr}}

+ 32 - 2
omega-web/src/popup.jade

@@ -24,12 +24,12 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp)
     link(rel='stylesheet' href='lib/bootstrap/css/bootstrap.min.css')
     link(rel='stylesheet' href='css/popup.css')
   body(ng-class='{"with-condition-form": showConditionForm}')
-    ul.popup-menu-nav.nav.nav-pills.nav-stacked(ng-hide='showConditionForm || proxyNotControllable')
+    ul.popup-menu-nav.nav.nav-pills.nav-stacked(ng-hide='showConditionForm || proxyNotControllable || showRequestInfo')
       li.profile(ng-repeat='profile in builtinProfiles' ng-class='{active: isActive(profile.name), "bg-info": isEffective(profile.name)}')
         a(href='#' role='button' ng-attr-tabindex='{{100 + $index}}' ng-click='applyProfile(profile)' title='{{getProfileTitle(profile)}}'
           data-shortcut='+{{profile.name}}')
           span(omega-profile-inline='profile' icon='getIcon(profile)' options='availableProfiles' disp-name='dispNameFilter')
-      li.profile.external-profile(ng-show='!!externalProfile' ng-class='{active: isActive(""), "bg-info": isEffective("")}')
+      li.profile.external-profile(ng-show='!requestInfoProvided && !!externalProfile' ng-class='{active: isActive(""), "bg-info": isEffective("")}')
         a(href='#' role='button' ng-click='nameExternal.open = true' title='{{getProfileTitle(externalProfile)}}' data-shortcut='external')
           form(name='nameExternalForm' ng-submit='nameExternalForm.$valid && saveExternal()')
             span(omega-profile-icon='externalProfile' icon='getIcon(externalProfile, "normal")' options='availableProfiles' disp-name='dispNameFilter')
@@ -37,6 +37,11 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp)
             span(ng-show='!nameExternal.open') {{'popup_externalProfile' | tr}}
             input.form-control(ng-show='!!nameExternal.open' ng-model='externalProfile.name' ng-blur='nameExternalForm.submit()'
               placeholder='{{"popup_externalProfileName" | tr}}' ui-validate='validateProfileName')
+      li.request-info(ng-show='!!requestInfoProvided' class='bg-warning')
+        a(href='#' role='button' ng-click='showRequestInfo = true' data-shortcut='requestInfo')
+          span.glyphicon.glyphicon-warning-sign.text-warning
+          = ' '
+          | {{'popup_requestErrorCount' | tr:[requestInfo.errorCount]}}
       li.divider
       li.profile.custom-profile(ng-repeat='profile in customProfiles' ng-class='{active: isActive(profile.name), "bg-info": isEffective(profile.name)}'
         dropdown)
@@ -126,6 +131,31 @@ html(lang='en' ng-app='omegaPopup' ng-controller='PopupCtrl' ng-csp)
       p.proxy-not-controllable-controls
         button.btn.btn-default(ng-click='closePopup()') {{'dialog_cancel' | tr}}
         button.btn.btn-primary(ng-click='openManage()') {{'popup_proxyNotControllableManage' | tr}}
+    div.request-info-details(ng-show='showRequestInfo')
+    form.request-info-details(style='display: none;'
+      ng-style='{display: showRequestInfo ? "block" : "none"}'
+      ng-submit='addConditionForDomains(domainsForCondition, profileForDomains)')
+      fieldset
+        legend
+          | {{'popup_addConditionTo' | tr}}
+          = ' '
+          span.profile-inline
+            span(omega-profile-inline='currentProfile' options='availableProfiles' disp-name='dispNameFilter')
+        p.text-warning {{'popup_requestErrorWarning' | tr}}
+        p.help-block {{'popup_requestErrorAddCondition' | tr}} 
+        .checkbox(ng-repeat='domain in requestInfo.domains')
+          label
+            input(type='checkbox' ng-model='domainsForCondition[domain.domain]')
+            span.label.label-warning {{domain.errorCount}}
+            =' *.{{domain.domain}}'
+        div.form-group
+          label {{'options_resultProfileForSelectedDomains' | tr}}
+          div(omega-profile-select='validResultProfiles' ng-model='profileForDomains'
+            disp-name='dispNameFilter' options='availableProfiles')
+        div.condition-controls
+          button.btn.btn-default(type='button' ng-click='showRequestInfo = false')
+            | {{'dialog_cancel' | tr}}
+          button.btn.btn-primary(type='submit') {{'popup_addCondition' | tr}}
 
     script(src='js/log_error.js')
     script(src='lib/FileSaver/FileSaver.js')