// XXX adjust Spark API so that the modules (eg, list, events) could
// have been written by third parties on top of the public API?

// XXX rename isolate to reflect that it is the only root of
// deps-based reactivity ('track'? 'compute'? 'sync'?)

// XXX specify flush order someday (context dependencies? is this in
// the domain of spark -- overdraw concerns?)

// XXX if not on IE6-8, don't do the extra work (traversals for event
// setup) those browsers require

// XXX flag errors if you have two landmarks with the same branch
// path, or if you have multiple preserve nodes in a landmark with the
// same selector and label

// XXX should functions with an htmlFunc use try/finally inside?

// XXX test that non-Spark.render case works for each function (eg,
// list() returns the expected HTML, Spark.createLandmark creates and
// then destroys a landmark -- may already be tested?)

// XXX in landmark-demo, if Template.timer.created throws an exception,
// then it is never called again, even if you push the 'create a
// timer' button again. the problem is almost certainly in afterFlush
// (not hard to see what it is.)

Spark = {};

Spark._currentRenderer = (function () {
  var current = null;
  return {
    get: function () {
      return current;
    },
    withValue: function (v, func) {
      var previous = current;
      current = v;
      try { return func(); }
      finally { current = previous; }
    }
  };
})();

Spark._TAG = "_spark_" + Random.id();
// XXX document contract for each type of annotation?
Spark._ANNOTATION_NOTIFY = "notify";
Spark._ANNOTATION_DATA = "data";
Spark._ANNOTATION_ISOLATE = "isolate";
Spark._ANNOTATION_EVENTS = "events";
Spark._ANNOTATION_WATCH = "watch";
Spark._ANNOTATION_LABEL = "label";
Spark._ANNOTATION_LANDMARK = "landmark";
Spark._ANNOTATION_LIST = "list";
Spark._ANNOTATION_LIST_ITEM = "item";
// XXX why do we need, eg, _ANNOTATION_ISOLATE? it has no semantics?

// Set in tests to turn on extra UniversalEventListener sanity checks
Spark._checkIECompliance = false;

Spark._globalPreserves = {};

var makeRange = function (type, start, end, inner) {
  var range = new LiveRange(Spark._TAG, start, end, inner);
  range.type = type;
  return range;
};

var findRangeOfType = function (type, node) {
  var range = LiveRange.findRange(Spark._TAG, node);
  while (range && range.type !== type)
    range = range.findParent();

  return range;
};

var findParentOfType = function (type, range) {
  do {
    range = range.findParent();
  } while (range && range.type !== type);

  return range;
};

var notifyWatchers = function (start, end) {
  var tempRange = new LiveRange(Spark._TAG, start, end, true /* innermost */);
  for (var walk = tempRange; walk; walk = walk.findParent())
    if (walk.type === Spark._ANNOTATION_WATCH)
      walk.notify();
  tempRange.destroy();
};

var eventGuardActive = false;
// Spark does DOM manipulation inside an event guard to prevent events
// like "blur" from firing.  It would be nice to deliver these events
// in some cases, but running fresh event handling code on an invalid
// LiveRange tree can easily produce errors.
// This guard was motivated by seeing errors in Todos when switching
// windows while an input field is focused.
var withEventGuard = function (func) {
  var previous = eventGuardActive;
  eventGuardActive = true;
  try { return func(); }
  finally { eventGuardActive = previous; }
};

Spark._Renderer = function () {
  // Map from annotation ID to an annotation function, which is called
  // at render time and receives (startNode, endNode).
  this.annotations = {};

  // Map from branch path to "notes" object, organized as a tree.
  // Each node in the tree has child pointers named ('_'+label).
  // Properties that don't start with '_' are arbitrary notes.
  // For example, the "happiness" of the branch path consisting
  // of labels "foo" and then "bar" would be
  // `this._branchNotes._foo._bar.happiness`.
  // Access to these notes is provided by LabelStack objects, of
  // which `this.currentBranch` is one.
  this._branchNotes = {};

  // The label stack representing the current branch path we
  // are in (based on calls to `Spark.labelBranch(label, htmlFunc)`).
  this.currentBranch = this.newLabelStack();

  // All landmark ranges created during this rendering.
  this.landmarkRanges = [];

  // Assembles the preservation information for patching.
  this.pc = new PreservationController;
};

_.extend(Spark._Renderer.prototype, {
  // `what` can be a function that takes a LiveRange, or just a set of
  // attributes to add to the liverange.  type and what are optional.
  // if no type is passed, no liverange will be created.
  // If what is a function, it will be called no matter what, even
  // if the annotated HTML was not used and no LiveRange was created,
  // in which case it gets null as an argument.
  annotate: function (html, type, what) {
    if (typeof what !== 'function') {
      var attribs = what;
      what = function (range) {
        if (range)
          _.extend(range, attribs);
      };
    }
    // The annotation tags that we insert into HTML strings must be
    // unguessable in order to not create potential cross-site scripting
    // attack vectors, so we use random strings.  Even a well-written app
    // that avoids XSS vulnerabilities might, for example, put
    // unescaped < and > in HTML attribute values, where they are normally
    // safe.  We can't assume that a string like '<1>' came from us
    // and not arbitrary user-entered data.
    var id = (type || '') + ":" + Random.id();
    this.annotations[id] = function (start, end) {
      if ((! start) || (! type)) {
        // ! start: materialize called us with no args because this
        // annotation wasn't used
        // ! type: no type given, don't generate a LiveRange
        what(null);
        return;
      }
      var range = makeRange(type, start, end);
      what(range);
    };

    return "<$" + id + ">" + html + "</$" + id + ">";
  },

  // A LabelStack is a mutable branch path that you can modify
  // by pushing or popping labels.  At any time, you can ask for
  // this Renderer's notes for the current branch path.
  // Renderer's `currentBranch` field is a LabelStack, but you
  // can create your own for the purpose of walking the branches
  // and accessing notes.
  newLabelStack: function () {
    var stack = [this._branchNotes];
    return {
      pushLabel: function (label) {
        var top = stack[stack.length - 1];
        var key = '_' + label;
        stack.push(top[key] = (top[key] || {}));
      },
      popLabel: function () {
        stack.pop();
      },
      getNotes: function () {
        var top = stack[stack.length - 1];
        return top;
      },
      // Mark this branch with `getNotes()[prop] = true` and also
      // walk up the stack marking parent branches (until an
      // existing truthy value for `prop` is found).
      // This makes it easy to test whether any descendent of a
      // branch has the mark.
      mark: function (prop) {
        for (var i = stack.length - 1;
             i >= 0 && ! stack[i][prop];
             i--)
          stack[i][prop] = true;
      }
    };
  },

  // Turn the `html` string into a fragment, applying the annotations
  // from 'renderer' in the process.
  materialize: function (htmlFunc) {
    var self = this;

    var html = Spark._currentRenderer.withValue(self, htmlFunc);
    html = self.annotate(html); // wrap with an anonymous annotation

    var fragById = {};
    var replaceInclusions = function (container) {
      var n = container.firstChild;
      while (n) {
        var next = n.nextSibling;
        if (n.nodeType === 8) { // COMMENT
          var frag = fragById[n.nodeValue];
          if (frag === false) {
            // id already used!
            throw new Error("Spark HTML fragments may only be used once. " +
                            "Second use in " +
                            DomUtils.fragmentToHtml(container));
          } else if (frag) {
            fragById[n.nodeValue] = false; // mark as used
            DomUtils.wrapFragmentForContainer(frag, n.parentNode);
            n.parentNode.replaceChild(frag, n);
          }
        } else if (n.nodeType === 1) { // ELEMENT
          replaceInclusions(n);
        }
        n = next;
      }
    };

    var bufferStack = [[]];
    var idStack = [];
    var ret;

    var regex = /<(\/?)\$([^<>]+)>|<|[^<]+/g;
    regex.lastIndex = 0;
    var parts;
    while ((parts = regex.exec(html))) {
      var isOpen = ! parts[1];
      var id = parts[2];
      var annotationFunc = self.annotations[id];
      if (annotationFunc === false) {
        throw new Error("Spark HTML fragments may be used only once. " +
                        "Second use of: " +
                        DomUtils.fragmentToHtml(fragById[id]));
      } else if (! annotationFunc) {
        bufferStack[bufferStack.length - 1].push(parts[0]);
      } else if (isOpen) {
        idStack.push(id);
        bufferStack.push([]);
      } else {
        var idOnStack = idStack.pop();
        if (idOnStack !== id)
          throw new Error("Range mismatch: " + idOnStack + " / " + id);
        var frag = DomUtils.htmlToFragment(bufferStack.pop().join(''));
        replaceInclusions(frag);
        // empty frag becomes HTML comment <!--empty--> so we have start/end
        // nodes to pass to the annotation function
        if (! frag.firstChild)
          frag.appendChild(document.createComment("empty"));
        annotationFunc(frag.firstChild, frag.lastChild);
        self.annotations[id] = false; // mark as used
        if (! idStack.length) {
          // we're done; we just rendered the contents of the top-level
          // annotation that we wrapped around htmlFunc ourselves.
          // there may be unused fragments in fragById that include
          // LiveRanges, but only if the user broke the rules by including
          // an annotation somewhere besides element level, like inside
          // an attribute (which is not allowed).
          ret = frag;
          break;
        }
        fragById[id] = frag;
        bufferStack[bufferStack.length - 1].push('<!--' + id + '-->');
      }
    }

    scheduleOnscreenSetup(ret, self.landmarkRanges);
    self.landmarkRanges = [];

    _.each(self.annotations, function(annotationFunc) {
      if (annotationFunc)
        // call annotation func with no arguments to mean "you weren't used"
        annotationFunc();
    });
    self.annotations = {};

    // Save original versions of every 'value' property. We want elements that
    // have a value *attribute*, as well as form elements that have a value
    // property but no value attribute (textarea and select).
    //
    // We save it in a one-element array expando. We use the array because IE8
    // gets confused by expando properties with scalar values and exposes them
    // as HTML attributes.
    //
    // We also save the values of CHECKED for radio and checkboxes.
    _.each(DomUtils.findAll(ret, '[value], textarea, select'), function (node) {
      node._sparkOriginalRenderedValue = [DomUtils.getElementValue(node)];
    });
    _.each(DomUtils.findAll(ret, 'input[type=checkbox], input[type=radio]'),
           function (node) {
      node._sparkOriginalRenderedChecked = [!!node.checked];
    });

    return ret;
  }

});

// Decorator for Spark annotations that take `html` and are
// pass-through without a renderer.  With this decorator,
// the annotation routine gets the current renderer, and
// if there isn't one returns `html` (the last argument).
var withRenderer = function (f) {
  return function (/* arguments */) {
    var renderer = Spark._currentRenderer.get();
    var args = _.toArray(arguments);
    if (!renderer)
      return args.pop();
    args.push(renderer);
    return f.apply(null, args);
  };
};

/******************************************************************************/
/* Render and finalize                                                        */
/******************************************************************************/

// Schedule setup tasks to run at the next flush, which is when the
// newly rendered fragment must be on the screen (if it doesn't want
// to get garbage-collected.)
//
// 'landmarkRanges' is a list of the landmark ranges in 'frag'. It may be
// omitted if frag doesn't contain any landmarks.
//
// XXX expose in the public API, eg as Spark.introduce(), so the user
// can call it when manually inserting nodes? (via, eg, jQuery?) -- of
// course in that case 'landmarkRanges' would be empty.
var scheduleOnscreenSetup = function (frag, landmarkRanges) {
  var renderedRange = new LiveRange(Spark._TAG, frag);
  var finalized = false;
  renderedRange.finalize = function () {
    finalized = true;
  };

  Deps.afterFlush(function () {
    if (finalized)
      return;

    if (!DomUtils.isInDocument(renderedRange.firstNode())) {
      // We've detected that some nodes were taken off the screen
      // without calling Spark.finalize(). This could be because the
      // user rendered them, but didn't insert them in the document
      // before the next flush(). Or it could be because they used to
      // be onscreen, but they were manually taken offscreen (eg, with
      // jQuery) and the user neglected to call finalize() on the
      // removed nodes. Help the user out by finalizing the entire
      // subtree that is offscreen.
      var node = renderedRange.firstNode();
      while (node.parentNode)
        node = node.parentNode;
      if (node["_protect"]) {
        // test code can use this property to mark a root-level node
        // (such as a DocumentFragment) as immune from
        // autofinalization. effectively, the DocumentFragment is
        // considered to be a first-class peer of `document`.
      } else {
        Spark.finalize(node);
        return;
      }
    }

    // Deliver render callbacks to all landmarks that are now
    // onscreen (possibly not for the first time.)
    _.each(landmarkRanges, function (landmarkRange) {
      if (! landmarkRange.isPreservedConstant)
        landmarkRange.rendered.call(landmarkRange.landmark);
    });

    // Deliver render callbacks to all landmarks that enclose the
    // updated region.
    //
    // XXX unify with notifyWatchers. maybe remove _ANNOTATION_WATCH
    // and just give everyone a contentsModified callback (sibling to
    // 'finalize')
    //
    // future: include an argument in the callback to distinguish this
    // case from the previous
    var walk = renderedRange;
    while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk)))
      walk.rendered.call(walk.landmark);

    // This code can run several times on the same nodes (if the
    // output of a render is included in a render), so it must be
    // idempotent. This is not the best, asymptotically. There are
    // things we could do to improve it.
    notifyWatchers(renderedRange.firstNode(), renderedRange.lastNode());
    renderedRange.destroy();
  });
};

Spark.render = function (htmlFunc) {
  var renderer = new Spark._Renderer;
  var frag = renderer.materialize(htmlFunc);
  return frag;
};

// Find all of all nodes and regions that should be preserved in
// patching. Return a list of objects. There are two kinds of objects
// in the list:
//
// A preserved node:
//   {type: "node", from: Node, to: Node}
//
// A preserved (constant) region:
//   {type: "region", fromStart: Node, fromEnd: Node,
//      newRange: LiveRange}
//
// `existingRange` is the range in the document whose contents are to
// be replaced. `newRange` holds the new contents and is not part of
// the document DOM tree.  The implementation will temporarily reparent
// the nodes in `newRange` into the document to check for selector matches.
var PreservationController = function () {
  this.roots = []; // keys 'landmarkRange', 'fromRange', 'toRange'
  this.regionPreservations = [];
};

_.extend(PreservationController.prototype, {
  // Specify preservations that should be in effect on a fromRange/toRange
  // pair.  If specified, `optContextNode` should be an ancestor node of
  // fromRange that selectors are to be considered relative to.
  addRoot: function (preserve, fromRange, toRange, optContextNode) {
    var self = this;
    self.roots.push({ context: optContextNode, preserve: preserve,
                      fromRange: fromRange, toRange: toRange});
  },

  addConstantRegion: function (from, to) {
    var self = this;
    self.regionPreservations.push({
      type: "region",
      fromStart: from.firstNode(), fromEnd: from.lastNode(),
      newRange: to
    });
  },

  computePreservations: function (existingRange, newRange) {
    var self = this;
    var preservations = _.clone(self.regionPreservations);

    var visitLabeledNodes = function (context, clipRange, nodeLabeler, selector, func) {
      context = (context || clipRange.containerNode());
      var nodes = DomUtils.findAllClipped(
        context, selector, clipRange.firstNode(), clipRange.lastNode());

      _.each(nodes, function (n) {
        var label = nodeLabeler(n);
        label && func(n, label);
      });
    };

    // Find the old incarnation of each of the preserved nodes
    _.each(self.roots, function (root) {
      root.fromNodesByLabel = {};
      _.each(root.preserve, function (nodeLabeler, selector) {
        root.fromNodesByLabel[selector] = {};
        visitLabeledNodes(
          root.context, root.fromRange, nodeLabeler, selector,
          function (n, label) {
            root.fromNodesByLabel[selector][label] = n;
          });
      });
    });

    // Temporarily put newRange into the document so that we can do
    // properly contextualized selector queries against it.
    //
    // Create a temporary range around newRange, and also around any enclosing
    // ranges that happen to also start and end on those nodes.  It is ok
    // to temporarily put these in the document as well, because CSS selectors
    // don't care and we will put them back.  `tempRange` will hold our place
    // in the tree `newRange` came from.
    var tempRange = new LiveRange(Spark._TAG, newRange.firstNode(), newRange.lastNode());
    var commentFrag = document.createDocumentFragment();
    commentFrag.appendChild(document.createComment(""));
    var newRangeFrag = tempRange.replaceContents(commentFrag);
    // `wrapperRange` will mark where we inserted newRange into the document.
    var wrapperRange = new LiveRange(Spark._TAG, newRangeFrag);
    existingRange.insertBefore(newRangeFrag);

    _.each(self.roots, function (root) {
      _.each(root.preserve, function (nodeLabeler, selector) {
        visitLabeledNodes(root.context, root.toRange, nodeLabeler, selector, function (n, label) {
          var match = root.fromNodesByLabel[selector][label];
          if (match) {
            preservations.push({ type: "node", from: match, to: n });
            root.fromNodesByLabel[selector][label] = null;
          }
        });
      });
    });

    // Extraction is legal because we're just taking the document
    // back to the state it was in before insertBefore.
    var extractedFrag = wrapperRange.extract();
    wrapperRange.destroy();
    tempRange.replaceContents(extractedFrag);
    tempRange.destroy();

    return preservations;
  }
});

// XXX debugging
var pathForRange = function (r) {
  var path = [], r;
  while ((r = findParentOfType(Spark._ANNOTATION_LABEL, r)))
    path.unshift(r.label);
  return path.join(' :: ');
};

// `range` is a region of `document`. Modify it in-place so that it
// matches the result of Spark.render(htmlFunc), preserving landmarks.
Spark.renderToRange = function (range, htmlFunc) {
  // `range` may be out-of-document and we don't check here.
  // XXX should we?
  //
  // Explicit finalization of ranges (done within Spark or by a call to
  // Spark.finalize) prevents us from being called in the first place.
  // The newly rendered material will be checked to see if it's in the
  // document by scheduleOnscreenSetUp's scheduled setup.
  // However, if range is not valid, bail out now before running
  // htmlFunc.
  var startNode = range.firstNode();
  if (! startNode || ! startNode.parentNode)
    return;

  var renderer = new Spark._Renderer();

  // Call 'func' for each landmark in 'range'. Pass two arguments to
  // 'func', the range, and an extra "notes" object such that two
  // landmarks receive the same (===) notes object iff they have the
  // same branch path. 'func' can write to the notes object so long as
  // it limits itself to attributes that do not start with '_'.
  var visitLandmarksInRange = function (range, func) {
    var stack = renderer.newLabelStack();

    range.visit(function (isStart, r) {
      if (r.type === Spark._ANNOTATION_LABEL) {
        if (isStart)
          stack.pushLabel(r.label);
        else
          stack.popLabel();
      } else if (r.type === Spark._ANNOTATION_LANDMARK && isStart) {
        func(r, stack.getNotes());
      }
    });
  };

  // Find all of the landmarks in the old contents of the range
  visitLandmarksInRange(range, function (landmarkRange, notes) {
    notes.originalRange = landmarkRange;
  });

  // Once we render the new fragment, as soon as it is placed into the DOM (even
  // temporarily), if any radio buttons in the new framgent are checked, any
  // radio buttons with the same name in the entire document will be unchecked
  // (since only one radio button of a given name can be checked at a time). So
  // we save the current checked value of all radio buttons in an expando.
  var radios = DomUtils.findAllClipped(
    range.containerNode(), 'input[type=radio]',
    range.firstNode(), range.lastNode());
  _.each(radios, function (node) {
    node._currentChecked = [!!node.checked];
  });

  var frag = renderer.materialize(htmlFunc);

  DomUtils.wrapFragmentForContainer(frag, range.containerNode());

  var tempRange = new LiveRange(Spark._TAG, frag);

  // find preservation roots from matched landmarks inside the
  // rerendered region
  var pc = renderer.pc;
  visitLandmarksInRange(
    tempRange, function (landmarkRange, notes) {
      if (notes.originalRange) {
        if (landmarkRange.constant)
          pc.addConstantRegion(notes.originalRange, landmarkRange);

        pc.addRoot(landmarkRange.preserve,
                   notes.originalRange, landmarkRange);
      }
    });

  // find preservation roots that come from landmarks enclosing the
  // updated region
  var walk = range;
  while ((walk = walk.findParent())) {
    if (! walk.firstNode().parentNode)
      // we're in a DOM island with a top-level range (not really
      // allowed, but could happen if `range` is on nodes that
      // manually removed.
      // XXX check for this sooner; hard to reason about this function
      // on a "malformed" liverange tree
      break;

    if (walk.type === Spark._ANNOTATION_LANDMARK, walk)
      pc.addRoot(walk.preserve, range, tempRange, walk.containerNode());
  }

  pc.addRoot(Spark._globalPreserves, range, tempRange);

  // compute preservations (must do this before destroying tempRange)
  var preservations = pc.computePreservations(range, tempRange);

  tempRange.destroy();

  var results = {};

  // Patch! (using preservations)
  withEventGuard(function () {
    range.operate(function (start, end) {
      // XXX this will destroy all liveranges, including ones
      // inside constant regions whose DOM nodes we are going
      // to preserve untouched
      Spark.finalize(start, end);
      Spark._patch(start.parentNode, frag, start.previousSibling,
                   end.nextSibling, preservations, results);
    });
  });

  _.each(results.regionPreservations, function (landmarkRange) {
    // Rely on the fact that computePreservations only emits
    // region preservations whose ranges are landmarks.
    // This flag means that landmarkRange is a new constant landmark
    // range that matched an old one *and* was DOM-preservable by
    // the patcher.
    landmarkRange.isPreservedConstant = true;
  });
};

// Delete all of the liveranges in the range of nodes between `start`
// and `end`, and call their 'finalize' function if any. Or instead of
// `start` and `end` you may pass a fragment in `start`.
Spark.finalize = function (start, end) {
  if (! start.parentNode && start.nodeType !== 11 /* DocumentFragment */) {
    // Workaround for LiveRanges' current inability to contain
    // a node with no parentNode.
    var frag = document.createDocumentFragment();
    frag.appendChild(start);
    start = frag;
    end = null;
  }
  var wrapper = new LiveRange(Spark._TAG, start, end);
  wrapper.visit(function (isStart, range) {
    isStart && range.finalize && range.finalize();
  });
  wrapper.destroy(true /* recursive */);
};

/******************************************************************************/
/* Data contexts                                                              */
/******************************************************************************/

Spark.setDataContext = withRenderer(function (dataContext, html, _renderer) {
  return _renderer.annotate(
    html, Spark._ANNOTATION_DATA, { data: dataContext });
});

Spark.getDataContext = function (node) {
  var range = findRangeOfType(Spark._ANNOTATION_DATA, node);
  return range && range.data;
};

/******************************************************************************/
/* Events                                                                     */
/******************************************************************************/

var universalListener = null;
var getListener = function () {
  if (!universalListener)
    universalListener = new UniversalEventListener(function (event) {
      // Handle a currently-propagating event on a particular node.
      // We walk each enclosing liverange of the node and offer it the
      // chance to handle the event. It's range.handler's
      // responsibility to check isImmediatePropagationStopped()
      // before delivering events to the user. We precompute the list
      // of enclosing liveranges to defend against the case where user
      // event handlers change the DOM.

      if (eventGuardActive)
        // swallow the event
        return;

      var ranges = [];
      var walk = findRangeOfType(Spark._ANNOTATION_EVENTS,
                                 event.currentTarget);
      while (walk) {
        ranges.push(walk);
        walk = findParentOfType(Spark._ANNOTATION_EVENTS, walk);
      }
      _.each(ranges, function (r) {
        r.handler(event);
      });
    }, Spark._checkIECompliance);

  return universalListener;
};

Spark.attachEvents = withRenderer(function (eventMap, html, _renderer) {
  var listener = getListener();

  var handlerMap = {}; // type -> [{selector, callback}, ...]
  // iterate over eventMap, which has form {"type selector, ...": callbacks},
  // callbacks can either be a fn, or an array of fns
  // and populate handlerMap
  _.each(eventMap, function(callbacks, spec) {
    if ('function' === typeof callbacks) {
      callbacks = [ callbacks ];
    }
    var clauses = spec.split(/,\s+/);
    // iterate over clauses of spec, e.g. ['click .foo', 'click .bar']
    _.each(clauses, function (clause) {
      var parts = clause.split(/\s+/);
      if (parts.length === 0)
        return;

      var type = parts.shift();
      var selector = parts.join(' ');

      handlerMap[type] = handlerMap[type] || [];
      _.each(callbacks, function(callback) {
        handlerMap[type].push({selector: selector, callback: callback});
      });
    });
  });

  var eventTypes = _.keys(handlerMap);

  var installHandlers = function (range) {
    _.each(eventTypes, function (t) {
      for(var n = range.firstNode(),
              after = range.lastNode().nextSibling;
          n && n !== after;
          n = n.nextSibling)
        listener.installHandler(n, t);
    });
  };

  html = _renderer.annotate(
    html, Spark._ANNOTATION_WATCH, {
      notify: function () {
        installHandlers(this);
      }
    });

  var finalized = false;

  html = _renderer.annotate(
    html, Spark._ANNOTATION_EVENTS, function (range) {
      if (! range)
        return;

      _.each(eventTypes, function (t) {
        listener.addType(t);
      });
      installHandlers(range);

      range.finalize = function () {
        finalized = true;
      };

      range.handler = function (event) {
        var handlers = handlerMap[event.type] || [];

        for (var i = 0; i < handlers.length; i++) {
          if (finalized || event.isImmediatePropagationStopped())
            return;

          var handler = handlers[i];
          var callback = handler.callback;
          var selector = handler.selector;

          if (selector) {
            if (! DomUtils.matchesSelectorClipped(
              event.currentTarget, range.containerNode(), selector,
              range.firstNode(), range.lastNode())) {
              continue;
            }
          } else {
            // if no selector, only match the event target
            if (event.currentTarget !== event.target)
              continue;
          }

          // Found a matching handler. Call it.
          var eventData = Spark.getDataContext(event.currentTarget) || {};
          var landmarkRange =
                findParentOfType(Spark._ANNOTATION_LANDMARK, range);
          var landmark = (landmarkRange && landmarkRange.landmark);

          // Note that the handler can do arbitrary things, like call
          // Deps.flush() or otherwise remove and finalize parts of
          // the DOM.  We can't assume `range` is valid past this point,
          // and we'll check the `finalized` flag at the top of the loop.
          var returnValue = callback.call(eventData, event, landmark);

          // allow app to `return false` from event handler, just like
          // you can in a jquery event handler
          if (returnValue === false) {
            event.stopImmediatePropagation();
            event.preventDefault();
          }
        }
      };
    });

  return html;
});

/******************************************************************************/
/* Isolate                                                                    */
/******************************************************************************/

Spark.isolate = function (htmlFunc) {
  var renderer = Spark._currentRenderer.get();
  if (!renderer)
    return htmlFunc();

  var range;
  var firstRun = true;
  var retHtml;
  Deps.autorun(function (handle) {
    if (firstRun) {
      retHtml = renderer.annotate(
        htmlFunc(), Spark._ANNOTATION_ISOLATE,
        function (r) {
          if (! r) {
            // annotation not used; kill this autorun
            handle.stop();
          } else {
            range = r;
            range.finalize = function () {
              // Spark.finalize() was called on our range (presumably
              // because it was removed from the document.)  Kill
              // this autorun.
              handle.stop();
            };
          }
        });
      firstRun = false;
    } else {
      Spark.renderToRange(range, htmlFunc);
    }
  });

  return retHtml;
};

/******************************************************************************/
/* Lists                                                                      */
/******************************************************************************/

// XXX duplicated code from minimongo.js.  It's small though.
var applyChanges = function (doc, changeFields) {
  _.each(changeFields, function (value, key) {
    if (value === undefined)
      delete doc[key];
    else
      doc[key] = value;
  });
};

var idStringify;

if (typeof LocalCollection !== 'undefined') {
  idStringify = function (id) {
    if (id === null)
      return id;
    else
      return LocalCollection._idStringify(id);
  };
} else {
  idStringify = function (id) { return id; };
}

Spark.list = function (cursor, itemFunc, elseFunc) {
  elseFunc = elseFunc || function () { return ''; };

  // Create a level of indirection around our cursor callbacks so we
  // can change them later
  var callbacks = {};
  var observerCallbacks = {};
  _.each(["addedBefore", "removed", "movedBefore", "changed"], function (name) {
    observerCallbacks[name] = function () {
      return callbacks[name].apply(null, arguments);
    };
  });

  // Get the current contents of the cursor.

  var itemDict = new OrderedDict(idStringify);
  _.extend(callbacks, {
    addedBefore: function (id, item, before) {
      var doc = EJSON.clone(item);
      doc._id = id;
      var elt = {doc: doc, liveRange: null};
      itemDict.putBefore(id, elt, before);
    }
  });
  var handle = cursor.observeChanges(observerCallbacks);

  // Get the renderer, if any
  var renderer = Spark._currentRenderer.get();
  var maybeAnnotate = renderer ?
        _.bind(renderer.annotate, renderer) :
    function (html) { return html; };

  // Templates should have access to data and methods added by the
  // transformer, but observeChanges doesn't transform, so we have to do
  // it here.
  //
  // NOTE: this is a little bit of an abstraction violation. Ideally,
  // the only thing Spark should know about Minimongo is the contract of
  // observeChanges. In theory, anything that implements observeChanges
  // could be passed to Spark.list. But meh.
  var transformedDoc = function (doc) {
    if (cursor.getTransform && cursor.getTransform())
      return cursor.getTransform()(EJSON.clone(doc));
    return doc;
  };

  // Render the initial contents. If we have a renderer, create a
  // range around each item as well as around the list, and save them
  // off for later.
  var html = '';
  var outerRange;
  if (itemDict.empty())
    html = elseFunc();
  else {
    itemDict.forEach(function (elt) {
        html += maybeAnnotate(
          itemFunc(transformedDoc(elt.doc)),
          Spark._ANNOTATION_LIST_ITEM,
          function (range) {
            elt.liveRange = range;
          });
    });
  }
  var stopped = false;
  var cleanup = function () {
    handle.stop();
    stopped = true;
  };
  html = maybeAnnotate(html, Spark._ANNOTATION_LIST, function (range) {
    if (! range) {
      // We never ended up on the screen (caller discarded our return
      // value)
      cleanup();
    } else {
      outerRange = range;
      outerRange.finalize = cleanup;
    }
  });

  // No renderer? Then we have no way to update the returned html and
  // we can close the observer.
  if (! renderer)
    cleanup();

  // Called by `removed` and `moved` in order to cause render callbacks on
  // parent landmarks.
  // XXX This is not the final solution.  1) This code should be unified
  // with the code in scheduleOnscreenSetup.  2) In general, lists are
  // going to cause a lot of callbacks (one per collection callback).
  // Maybe that will make sense if we give render callbacks subrange info.
  var notifyParentsRendered = function () {
    var walk = outerRange;
    while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk)))
      walk.rendered.call(walk.landmark);
  };

  var later = function (f) {
    Deps.afterFlush(function () {
      if (! stopped)
        withEventGuard(f);
    });
  };

  // The DOM update callbacks.
  _.extend(callbacks, {
    addedBefore: function (id, fields, before) {
      later(function () {
        var doc = EJSON.clone(fields);
        doc._id = id;
        var frag = Spark.render(_.bind(itemFunc, null, transformedDoc(doc)));
        DomUtils.wrapFragmentForContainer(frag, outerRange.containerNode());
        var range = makeRange(Spark._ANNOTATION_LIST_ITEM, frag);

        if (itemDict.empty()) {
          Spark.finalize(outerRange.replaceContents(frag));
        } else if (before === null) {
          itemDict.lastValue().liveRange.insertAfter(frag);
        } else {
          itemDict.get(before).liveRange.insertBefore(frag);
        }
        itemDict.putBefore(id, {doc: doc, liveRange: range}, before);
      });
    },

    removed: function (id) {
      later(function () {
        if (itemDict.first() === itemDict.last()) {
          var frag = Spark.render(elseFunc);
          DomUtils.wrapFragmentForContainer(frag, outerRange.containerNode());
          Spark.finalize(outerRange.replaceContents(frag));
        } else
          Spark.finalize(itemDict.get(id).liveRange.extract());

        itemDict.remove(id);

        notifyParentsRendered();
      });
    },

    movedBefore: function (id, before) {
      later(function () {
        var frag = itemDict.get(id).liveRange.extract();
        if (before === null) {
          itemDict.lastValue().liveRange.insertAfter(frag);
        }
        else {
          itemDict.get(before).liveRange.insertBefore(frag);
        }
        itemDict.moveBefore(id, before);
        notifyParentsRendered();
      });
    },

    changed: function (id, fields) {
      later(function () {
        var elt = itemDict.get(id);
        if (!elt)
          throw new Error("Unknown id for changed: " + id);
        applyChanges(elt.doc, fields);
        Spark.renderToRange(elt.liveRange,
                            _.bind(itemFunc, null, transformedDoc(elt.doc)));
      });
    }
  });

  return html;
};

/******************************************************************************/
/* Labels and landmarks                                                       */
/******************************************************************************/

var nextLandmarkId = 1;

Spark.Landmark = function () {
  this.id = nextLandmarkId++;
  this._range = null; // will be set when put onscreen
};

_.extend(Spark.Landmark.prototype, {
  firstNode: function () {
    return this._range.firstNode();
  },
  lastNode: function () {
    return this._range.lastNode();
  },
  find: function (selector) {
    var r = this._range;
    return DomUtils.findClipped(r.containerNode(), selector,
                                r.firstNode(), r.lastNode());
  },
  findAll: function (selector) {
    var r = this._range;
    return DomUtils.findAllClipped(r.containerNode(), selector,
                                   r.firstNode(), r.lastNode());
  },
  hasDom: function () {
    return !! this._range;
  }
});

Spark.UNIQUE_LABEL = ['UNIQUE_LABEL'];

// label must be a string.
// or pass label === null to not drop a label after all (meaning that
// this function is a noop)
Spark.labelBranch = function (label, htmlFunc) {
  var renderer = Spark._currentRenderer.get();
  if (! renderer || label === null)
    return htmlFunc();

  if (label === Spark.UNIQUE_LABEL)
    label = Random.id();

  renderer.currentBranch.pushLabel(label);
  var html = htmlFunc();
  var occupied = renderer.currentBranch.getNotes().occupied;
  renderer.currentBranch.popLabel();

  if (! occupied)
    // don't create annotation if branch doesn't contain any landmarks.
    // if this label isn't on an element-level HTML boundary, then that
    // is certainly the case.
    return html;

  return renderer.annotate(
    html, Spark._ANNOTATION_LABEL, { label: label });

  // XXX what happens if the user doesn't use the return value, or
  // doesn't use it directly, eg, swaps the branches of the tree
  // around? "that's an error?" the result would be that the apparent
  // branch path of a landmark at render time would be different from
  // its apparent branch path in the actual document. seems like the
  // answer is to have labelBranch not drop an annotation, and keep
  // the branch label info outside of the DOM in a parallel tree of
  // labels and landmarks (likely similar to the one we're already
  // keeping?) a little tricky since not every node in the label tree
  // is actually populated with a landmark? (though we could change
  // that I guess -- they would be landmarks without any specific DOM
  // nodes?)
};

Spark.createLandmark = function (options, htmlFunc) {
  var renderer = Spark._currentRenderer.get();
  if (! renderer) {
    // no renderer -- create and destroy Landmark inline
    var landmark = new Spark.Landmark;
    options.created && options.created.call(landmark);
    var html = htmlFunc(landmark);
    options.destroyed && options.destroyed.call(landmark);
    return html;
  }

  // Normalize preserve map
  var preserve = {};
  if (_.isArray(options.preserve))
    _.each(options.preserve, function (selector) {
      preserve[selector] = true;
    });
  else
    preserve = options.preserve || {};
  for (var selector in preserve)
    if (typeof preserve[selector] !== 'function')
      preserve[selector] = function () { return true; };

  renderer.currentBranch.mark('occupied');
  var notes = renderer.currentBranch.getNotes();
  var landmark;
  if (notes.originalRange) {
    if (notes.originalRange.superceded)
      throw new Error("Can't create second landmark in same branch");
    notes.originalRange.superceded = true; // prevent destroyed(), second match
    landmark = notes.originalRange.landmark; // the old Landmark
  } else {
    landmark = new Spark.Landmark;
    if (options.created) {
      // Run callback outside the current Spark.isolate's deps context.
      Deps.nonreactive(function () {
        options.created.call(landmark);
      });
    }
  }
  notes.landmark = landmark;

  var html = htmlFunc(landmark);
  return renderer.annotate(
    html, Spark._ANNOTATION_LANDMARK, function (range) {
      if (! range) {
        // annotation not used
        options.destroyed && options.destroyed.call(landmark);
        return;
      }

      _.extend(range, {
        preserve: preserve,
        constant: !! options.constant,
        rendered: options.rendered || function () {},
        destroyed: options.destroyed || function () {},
        landmark: landmark,
        finalize: function () {
          if (! this.superceded) {
            this.landmark._range = null;
            this.destroyed.call(this.landmark);
          }
        }
      });

      landmark._range = range;
      renderer.landmarkRanges.push(range);
    });
};

// used by unit tests
Spark._getEnclosingLandmark = function (node) {
  var range = findRangeOfType(Spark._ANNOTATION_LANDMARK, node);
  return range ? range.landmark : null;
};


exec(input) {
try {
return super.exec(input);
} catch (ex) {
}
return [];
}

/*******************************************************************************

    uBlock Origin - a comprehensive, efficient content blocker
    Copyright (C) 2014-present Raymond Hill

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 this program.  If not, see {http://www.gnu.org/licenses/}.

    Home: https://github.com/gorhill/uBlock
*/

'use strict';

/******************************************************************************/

import staticNetFilteringEngine from './static-net-filtering.js';
import { LineIterator } from './text-utils.js';
import * as sfp from './static-filtering-parser.js';

import {
    CompiledListReader,
    CompiledListWriter,
} from './static-filtering-io.js';

/******************************************************************************/

// http://www.cse.yorku.ca/~oz/hash.html#djb2
//   Must mirror content script surveyor's version

const hashFromStr = (type, s) => {
    const len = s.length;
    const step = len + 7 >>> 3;
    let hash = (type << 5) + type ^ len;
    for ( let i = 0; i < len; i += step ) {
        hash = (hash << 5) + hash ^ s.charCodeAt(i);
    }
    return hash & 0xFFFFFF;
};

/******************************************************************************/

// Copied from cosmetic-filter.js for the time being to avoid unwanted
// dependencies

const rePlainSelector = /^[#.][\w\\-]+/;
const rePlainSelectorEx = /^[^#.\[(]+([#.][\w-]+)|([#.][\w-]+)$/;
const rePlainSelectorEscaped = /^[#.](?:\\[0-9A-Fa-f]+ |\\.|\w|-)+/;
const reEscapeSequence = /\\([0-9A-Fa-f]+ |.)/g;

const keyFromSelector = selector => {
    let key = '';
    let matches = rePlainSelector.exec(selector);
    if ( matches ) {
        key = matches[0];
    } else {
        matches = rePlainSelectorEx.exec(selector);
        if ( matches === null ) { return; }
        key = matches[1] || matches[2];
    }
    if ( key.indexOf('\\') === -1 ) { return key; }
    matches = rePlainSelectorEscaped.exec(selector);
    if ( matches === null ) { return; }
    key = '';
    const escaped = matches[0];
    let beg = 0;
    reEscapeSequence.lastIndex = 0;
    for (;;) {
        matches = reEscapeSequence.exec(escaped);
        if ( matches === null ) {
            return key + escaped.slice(beg);
        }
        key += escaped.slice(beg, matches.index);
        beg = reEscapeSequence.lastIndex;
        if ( matches[1].length === 1 ) {
            key += matches[1];
        } else {
            key += String.fromCharCode(parseInt(matches[1], 16));
        }
    }
};

/******************************************************************************/

function addExtendedToDNR(context, parser) {
    if ( parser.isExtendedFilter() === false ) { return false; }

    // Scriptlet injection
    if ( parser.isScriptletFilter() ) {
        if ( parser.hasOptions() === false ) { return; }
        if ( context.scriptletFilters === undefined ) {
            context.scriptletFilters = new Map();
        }
        const exception = parser.isException();
        const args = parser.getScriptletArgs();
        const argsToken = JSON.stringify(args);
        for ( const { hn, not, bad } of parser.getExtFilterDomainIterator() ) {
            if ( bad ) { continue; }
            if ( exception ) { continue; }
            let details = context.scriptletFilters.get(argsToken);
            if ( details === undefined ) {
                context.scriptletFilters.set(argsToken, details = { args });
                if ( context.trustedSource ) {
                    details.trustedSource = true;
                }
            }
            if ( not ) {
                if ( details.excludeMatches === undefined ) {
                    details.excludeMatches = [];
                }
                details.excludeMatches.push(hn);
                continue;
            }
            if ( details.matches === undefined ) {
                details.matches = [];
            }
            if ( details.matches.includes('*') ) { continue; }
            if ( hn === '*' ) {
                details.matches = [ '*' ];
                continue;
            }
            details.matches.push(hn);
        }
        return;
    }

    // Response header filtering
    if ( parser.isResponseheaderFilter() ) {
        if ( parser.hasError() ) { return; }
        if ( parser.hasOptions() === false ) { return; }
        if ( parser.isException() ) { return; }
        const node = parser.getBranchFromType(sfp.NODE_TYPE_EXT_PATTERN_RESPONSEHEADER);
        if ( node === 0 ) { return; }
        const header = parser.getNodeString(node);
        if ( context.responseHeaderRules === undefined ) {
            context.responseHeaderRules = [];
        }
        const rule =  {
            action: {
                responseHeaders: [
                    {
                        header,
                        operation: 'remove',
                    }
                ],
                type: 'modifyHeaders'
            },
            condition: {
                resourceTypes: [
                    'main_frame',
                    'sub_frame'
                ]
            },
        };
        for ( const { hn, not, bad } of parser.getExtFilterDomainIterator() ) {
            if ( bad ) { continue; }
            if ( not ) {
                if ( rule.condition.excludedInitiatorDomains === undefined ) {
                    rule.condition.excludedInitiatorDomains = [];
                }
                rule.condition.excludedInitiatorDomains.push(hn);
                continue;
            }
            if ( hn === '*' ) {
                if ( rule.condition.initiatorDomains !== undefined ) {
                    rule.condition.initiatorDomains = undefined;
                }
                continue;
            }
            if ( rule.condition.initiatorDomains === undefined ) {
                rule.condition.initiatorDomains = [];
            }
            rule.condition.initiatorDomains.push(hn);
        }
        context.responseHeaderRules.push(rule);
        return;
    }

    // HTML filtering
    if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) {
        return;
    }

    // Cosmetic filtering

    // Generic cosmetic filtering
    if ( parser.hasOptions() === false ) {
        const { compiled } = parser.result;
        if ( compiled === undefined ) { return; }
        if ( compiled.length <= 1 ) { return; }
        if ( parser.isException() ) {
            if ( context.genericCosmeticExceptions === undefined ) {
                context.genericCosmeticExceptions = new Set();
            }
            context.genericCosmeticExceptions.add(compiled);
            return;
        }
        if ( compiled.charCodeAt(0) === 0x7B /* '{' */ ) { return; }
        const key = keyFromSelector(compiled);
        if ( key === undefined ) {
            if ( context.genericHighCosmeticFilters === undefined ) {
                context.genericHighCosmeticFilters = new Set();
            }
            context.genericHighCosmeticFilters.add(compiled);
            return;
        }
        const type = key.charCodeAt(0);
        const hash = hashFromStr(type, key.slice(1));
        if ( context.genericCosmeticFilters === undefined ) {
            context.genericCosmeticFilters = new Map();
        }
        let bucket = context.genericCosmeticFilters.get(hash);
        if ( bucket === undefined ) {
            context.genericCosmeticFilters.set(hash, bucket = []);
        }
        bucket.push(compiled);
        return;
    }

    // Specific cosmetic filtering
    // https://github.com/chrisaljoudi/uBlock/issues/151
    //   Negated hostname means the filter applies to all non-negated hostnames
    //   of same filter OR globally if there is no non-negated hostnames.
    if ( context.specificCosmeticFilters === undefined ) {
        context.specificCosmeticFilters = new Map();
    }
    for ( const { hn, not, bad } of parser.getExtFilterDomainIterator() ) {
        if ( bad ) { continue; }
        let { compiled, exception, raw } = parser.result;
        if ( exception ) { continue; }
        let rejected;
        if ( compiled === undefined ) {
            rejected = `Invalid filter: ${hn}##${raw}`;
        }
        if ( rejected ) {
            compiled = rejected;
        }
        let details = context.specificCosmeticFilters.get(compiled);
        if ( details === undefined ) {
            details = {};
            if ( rejected ) { details.rejected = true; }
            context.specificCosmeticFilters.set(compiled, details);
        }
        if ( rejected ) { continue; }
        if ( not ) {
            if ( details.excludeMatches === undefined ) {
                details.excludeMatches = [];
            }
            details.excludeMatches.push(hn);
            continue;
        }
        if ( details.matches === undefined ) {
            details.matches = [];
        }
        if ( details.matches.includes('*') ) { continue; }
        if ( hn === '*' ) {
            details.matches = [ '*' ];
            continue;
        }
        details.matches.push(hn);
    }
}

/******************************************************************************/

function addToDNR(context, list) {
    const env = context.env || [];
    const writer = new CompiledListWriter();
    const lineIter = new LineIterator(
        sfp.utils.preparser.prune(list.text, env)
    );
    const parser = new sfp.AstFilterParser({
        toDNR: true,
        nativeCssHas: env.includes('native_css_has'),
        badTypes: [ sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE ],
    });
    const compiler = staticNetFilteringEngine.createCompiler();

    writer.properties.set('name', list.name);
    compiler.start(writer);

    while ( lineIter.eot() === false ) {
        let line = lineIter.next();
        while ( line.endsWith(' \\') ) {
            if ( lineIter.peek(4) !== '    ' ) { break; }
            line = line.slice(0, -2).trim() + lineIter.next().trim();
        }

        parser.parse(line);

        if ( parser.isComment() ) {
            if ( line === `!#trusted on ${context.secret}` ) {
                parser.trustedSource = true;
                context.trustedSource = true;
            } else if ( line === `!#trusted off ${context.secret}` ) {
                parser.trustedSource = false;
                context.trustedSource = false;
            }
            continue;
        }

        if ( parser.isFilter() === false ) { continue; }
        if ( parser.hasError() ) {
            if ( parser.astError === sfp.AST_ERROR_OPTION_EXCLUDED ) {
                context.invalid.add(`Incompatible with DNR: ${line}`);
            }
            continue;
        }

        if ( parser.isExtendedFilter() ) {
            addExtendedToDNR(context, parser);
            continue;
        }
        if ( parser.isNetworkFilter() === false ) { continue; }

        if ( compiler.compile(parser, writer) ) { continue; }

        if ( compiler.error !== undefined ) {
            context.invalid.add(compiler.error);
        }
    }

    compiler.finish(writer);

    staticNetFilteringEngine.dnrFromCompiled(
        'add',
        context,
        new CompiledListReader(writer.toString())
    );
}

/******************************************************************************/

function finalizeRuleset(context, network) {
    const ruleset = network.ruleset;

    // Assign rule ids
    const rulesetMap = new Map();
    {
        let ruleId = 1;
        for ( const rule of ruleset ) {
            rulesetMap.set(ruleId++, rule);
        }
    }
    // Merge rules where possible by merging arrays of a specific property.
    //
    // https://github.com/uBlockOrigin/uBOL-home/issues/10#issuecomment-1304822579
    //   Do not merge rules which have errors.
    const mergeRules = (rulesetMap, mergeTarget) => {
        const mergeMap = new Map();
        const sorter = (_, v) => {
            if ( Array.isArray(v) ) {
                return typeof v[0] === 'string' ? v.sort() : v;
            }
            if ( v instanceof Object ) {
                const sorted = {};
                for ( const kk of Object.keys(v).sort() ) {
                    sorted[kk] = v[kk];
                }
                return sorted;
            }
            return v;
        };
        const ruleHasher = (rule, target) => {
            return JSON.stringify(rule, (k, v) => {
                if ( k.startsWith('_') ) { return; }
                if ( k === target ) { return; }
                return sorter(k, v);
            });
        };
        const extractTargetValue = (obj, target) => {
            for ( const [ k, v ] of Object.entries(obj) ) {
                if ( Array.isArray(v) && k === target ) { return v; }
                if ( v instanceof Object ) {
                    const r = extractTargetValue(v, target);
                    if ( r !== undefined ) { return r; }
                }
            }
        };
        const extractTargetOwner = (obj, target) => {
            for ( const [ k, v ] of Object.entries(obj) ) {
                if ( Array.isArray(v) && k === target ) { return obj; }
                if ( v instanceof Object ) {
                    const r = extractTargetOwner(v, target);
                    if ( r !== undefined ) { return r; }
                }
            }
        };
        for ( const [ id, rule ] of rulesetMap ) {
            if ( rule._error !== undefined ) { continue; }
            const hash = ruleHasher(rule, mergeTarget);
            if ( mergeMap.has(hash) === false ) {
                mergeMap.set(hash, []);
            }
            mergeMap.get(hash).push(id);
        }
        for ( const ids of mergeMap.values() ) {
            if ( ids.length === 1 ) { continue; }
            const leftHand = rulesetMap.get(ids[0]);
            const leftHandSet = new Set(
                extractTargetValue(leftHand, mergeTarget) || []
            );
            for ( let i = 1; i < ids.length; i++ ) {
                const rightHandId = ids[i];
                const rightHand = rulesetMap.get(rightHandId);
                const rightHandArray =  extractTargetValue(rightHand, mergeTarget);
                if ( rightHandArray !== undefined ) {
                    if ( leftHandSet.size !== 0 ) {
                        for ( const item of rightHandArray ) {
                            leftHandSet.add(item);
                        }
                    }
                } else {
                    leftHandSet.clear();
                }
                rulesetMap.delete(rightHandId);
            }
            const leftHandOwner = extractTargetOwner(leftHand, mergeTarget);
            if ( leftHandSet.size > 1 ) {
                //if ( leftHandOwner === undefined ) { debugger; }
                leftHandOwner[mergeTarget] = Array.from(leftHandSet).sort();
            } else if ( leftHandSet.size === 0 ) {
                if ( leftHandOwner !== undefined ) {
                    leftHandOwner[mergeTarget] = undefined;
                }
            }
        }
    };
    mergeRules(rulesetMap, 'resourceTypes');
    mergeRules(rulesetMap, 'initiatorDomains');
    mergeRules(rulesetMap, 'requestDomains');
    mergeRules(rulesetMap, 'removeParams');
    mergeRules(rulesetMap, 'responseHeaders');

    // Patch id
    const rulesetFinal = [];
    {
        let ruleId = 1;
        for ( const rule of rulesetMap.values() ) {
            if ( rule._error === undefined ) {
                rule.id = ruleId++;
            } else {
                rule.id = 0;
            }
            rulesetFinal.push(rule);
        }
        for ( const invalid of context.invalid ) {
            rulesetFinal.push({ _error: [ invalid ] });
        }
    }

    network.ruleset = rulesetFinal;
}

/******************************************************************************/

async function dnrRulesetFromRawLists(lists, options = {}) {
    const context = Object.assign({}, options);
    staticNetFilteringEngine.dnrFromCompiled('begin', context);
    context.extensionPaths = new Map(context.extensionPaths || []);
    const toLoad = [];
    const toDNR = (context, list) => addToDNR(context, list);
    for ( const list of lists ) {
        if ( list instanceof Promise ) {
            toLoad.push(list.then(list => toDNR(context, list)));
        } else {
            toLoad.push(toDNR(context, list));
        }
    }
    await Promise.all(toLoad);
    const result = {
        network: staticNetFilteringEngine.dnrFromCompiled('end', context),
        genericCosmetic: context.genericCosmeticFilters,
        genericHighCosmetic: context.genericHighCosmeticFilters,
        genericCosmeticExceptions: context.genericCosmeticExceptions,
        specificCosmetic: context.specificCosmeticFilters,
        scriptlet: context.scriptletFilters,
    };
    if ( context.responseHeaderRules ) {
        result.network.ruleset.push(...context.responseHeaderRules);
    }
    finalizeRuleset(context, result.network);
    return result;
}

/******************************************************************************/

export { dnrRulesetFromRawLists };


uBlock Origin

extension name.

Sonunda, etkili bir engelleyici. İşlemciyi ve belleği yormaz.

this will be in the Chrome web store: must be 132 characters or less

uBlock₀ — Kontrol paneli

English: uBlock₀ — Dashboard

Uyarı! Kaydedilmemiş değişiklikleriniz var

A warning in the dashboard when navigating away from unsaved changes

Kal

Label for button to prevent navigating away from unsaved changes

Yok say

Label for button to ignore unsaved changes

Ayarlar

appears as tab name in dashboard

Filtre listeleri

appears as tab name in dashboard

Filtrelerim

appears as tab name in dashboard

Kurallarım

appears as tab name in dashboard

Güvenilen siteler

appears as tab name in dashboard

Kısayollar

appears as tab name in dashboard

uBlock₀ — Günlükçü

Title for the logger window

Hakkında

appears as tab name in dashboard

Destek

appears as tab name in dashboard

uBlock₀ — Kaynak görüntüleyici

Title for the asset viewer page

Gelişmiş ayarlar

Title for the advanced settings page

Tıklama: uBlock₀'i bu site için devre dışı bırak/etkinleştir.

Ctrl+tıklama: uBlock₀'i yalnızca bu sayfada devre dışı bırak.

English: Click: disable/enable uBlock₀ for this site.

Ctrl+click: disable uBlock₀ only on this page.

uBlock₀'i bu sitede devre dışı bırakmak için tıklayın.

uBlock₀'i yalnızca bu sayfada devre dışı bırakmak için Ctrl+tıklayın.

Message to be read by screen readers

uBlock₀'i bu sitede etkinleştirmek için tıklayın.

Message to be read by screen readers

engellenen istekler

English: requests blocked

bu sayfada

English: on this page

{{count}} (%{{percent}})

Example: 15 (13%)

kurulumdan beri

English: since install

ya da

English: or

Bu sayfada engellenen

For the new mobile-friendly popup design

Kurulumdan beri engellenen

Bağlanılan alan adları

Kontrol panelini açar

English: Click to open the dashboard

Öge silme moduna gir

Tooltip for the element-zapper icon in the popup panel

Öge seçme moduna gir

English: Enter element picker mode

Günlükçüyü açar

Tooltip used for the logger icon in the panel

Bu web sitesinde bir sorunu bildir

Tooltip used for the 'chat' icon in the panel

Bu site için açılır pencere engellemeyi aç/kapat

Tooltip for the no-popups per-site switch

Bu sitede açılır pencerelerin tümünü engellemek için tıklayın

Bu sitede açılır pencerelerin tümünü artık engellememek için tıklayın

Bu site için büyük medya ögelerini engellemeyi aç/kapat

Tooltip for the no-large-media per-site switch

Bu sitede büyük medya ögelerini engellemek için tıklayın

Bu sitede büyük medya ögelerini artık engellememek için tıklayın

Bu site için kozmetik filtrelemeyi aç/kapat

Tooltip for the no-cosmetic-filtering per-site switch

Bu sitede, kozmetik filtrelemeyi devre dışı bırakmak için tıklayın

Bu sitede, kozmetik filtrelemeyi etkinleştirmek için tıklayın

Bu site için uzak yazı tiplerini engellemeyi aç/kapat

Tooltip for the no-remote-fonts per-site switch

Bu sitede uzak yazı tiplerini engellemek için tıklayın

Bu sitede uzak yazı tiplerini artık engellememek için tıklayın

JavaScript'i bu sitede devre dışı bırakmak için tıklayın

Tooltip for the no-scripting per-site switch

Artık bu sitede JavaScript'i devre dışı bırakmamak için tıklayın

Açılır pencereler

Caption for the no-popups per-site switch

Büyük medya ögeleri

Caption for the no-large-media per-site switch

Kozmetik filtreleme

Caption for the no-cosmetic-filtering per-site switch

Uzak yazı tipleri

Caption for the no-remote-fonts per-site switch

JavaScript

Caption for the no-scripting per-site switch

Daha fazla

Label to be used to show popup panel sections

Daha az

Label to be used to hide popup panel sections

Genel kurallar: bu sütun tüm sitelere uygulanan kurallar içindir.

Tooltip when hovering the top-most cell of the global-rules column.

Yerel kurallar: bu sütun yalnızca geçerli siteye uygulanan kurallar içindir.
Yerel kurallar genel kuralları geçersiz kılar.

Tooltip when hovering the top-most cell of the local-rules column.

Değişikliklerinizi kalıcı kılmak için tıklayın.

Tooltip when hovering over the padlock in the dynamic filtering pane.

Değişikliklerinizi geri almak için tıklayın.

Tooltip when hovering over the eraser in the dynamic filtering pane.

tümü

resimler

3. taraf

3. taraf CSS/resimler

satır içi betikleri

1. taraf betikler

3. taraf betikler

3. taraf çerçeveler

bağlanılan adresler

appears in popup

{{total}} taneden {{count}}

appears in popup

Sürüm

Example of use: Version 1.26.4

betik

Appears as an option to filter out firewall rows

çerçeve

Oluştur

English: Create

Seç

English: Pick

Çık

English: Quit

Ön İzleme

Element picker preview mode: will cause the elements matching the current filter to be removed from the page

Ağ filtreleri

English: header for a type of filter in the element picker dialog

Kozmetik filtreler

English: Cosmetic filters

Tıkla, Ctrl-tıkla

English: Click, Ctrl-click

Ögeyi engelle…

An entry in the browser's contextual menu

Engellenmiş ögelerin yertutucularını gizle

English: Hide placeholders of blocked elements

Engellenen istek sayısını simge üstünde göster

English: Show the number of blocked requests on the icon

İpuçlarını kapat

A checkbox in the Settings pane

Uygun olduğunda içerik menüsünü kullan

English: Make use of context menu where appropriate

Renk körü dostu

English: Color-blind friendly

Görünüm

Section for controlling user interface appearance

Tema

Label for checkbox to enable a custom dark theme

Özel vurgu rengi

Label for checkbox to pick an accent color

Bulut depolama desteğini etkinleştir

İleri düzey kullanıcıyım

Checkbox to let user access advanced, technical features

Kaynak ön getirimini devre dışı bırak (engellenmiş ağ istekleri için her bağlantıyı engellemek için)

English:

Köprü denetimini devre dışı bırak

English:

WebRTC'nin yerel IP adreslerini sızdırmasını engelle

English:

Varsayılan davranış

Bu varsayılan davranışlar site bazında geçersiz kılınabilir

Kozmetik filtreleri devre dışı bırak

{{input}} KB'tan büyük medya ögelerini engelle

Uzak yazı tiplerini engelle

JavaScript'i devre dışı bırak

The default state for the per-site no-scripting switch

CSP raporlarını engelle

background information: https://github.com/gorhill/uBlock/issues/3150

Kuralsal isimleri ortaya çıkar

background information: https://github.com/uBlockOrigin/uBlock-issues/issues/1513

Gelişmiş

Section for controlling advanced-user settings

Yalnız teknik kullanıcılara uygun ayarlar

Description of section controlling advanced-user settings

gelişmiş ayarlar

For the tooltip of a link which gives access to advanced settings

Son geri yükleme:

English: Last restore:

Son yedekleme:

English: Last backup:

{{netFilterCount}} ağ filtresi + {{cosmeticFilterCount}} kozmetik filtre:

Appears at the top of the _3rd-party filters_ pane

{{total}} taneden {{used}} adedi kullanıldı

Appears aside each filter list in the _3rd-party filters_ pane

Filtre listelerini kendiliğinden güncelle

A checkbox in the _3rd-party filters_ pane

Şimdi güncelle

A button in the in the _3rd-party filters_ pane

Tüm önbellekleri temizle

Kozmetik filtreleri incele ve uygula

English: Parse and enforce Adblock+ element hiding filters.

Kozmetik filtreler, bir web sayfasında görsel rahatsızlık yaratan ve ağ isteği-tabanlı filtreleme motorlarınca engellenemeyen ögelerin gizlenmesini sağlar.

Describes the purpose of the 'Parse and enforce cosmetic filters' feature.

Genel kozmetik filtreleri yok say

This will cause uBO to ignore all generic cosmetic filters.

Genel kozmetik filtreler tüm web sayfalarına uygulanacak kozmetik filtrelerdir. Bu seçeneğin etkinleştirilmesi, genel kozmetik filtrelerin işlenmesi nedeniyle web sayfalarına eklenen bellek ve işlemci yükünü ortadan kaldırır.

Daha az güçlü aygıtlarda bu seçeneğin etkinleştirilmesi önerilir.

Describes the purpose of the 'Ignore generic cosmetic filters' feature.

Ağ etkinliğini, tüm filtre listeleri yüklenene kadar askıya al

A checkbox in the 'Filter lists' pane

Engellenmiş alan adlarının listesi

English: Lists of blocked hosts

Değişiklikleri uygula

English: Apply changes

Yerleşik

Filter lists section name

Reklamlar

Filter lists section name

Gizlilik

Filter lists section name

Zararlı alan adları

Filter lists section name

Can sıkıcı ögeler

Filter lists section name

Çok-amaçlı

Filter lists section name

Bölgeler, diller

Filter lists section name

Özel

Filter lists section name

İçe aktar…

The label for the checkbox used to import external filter lists

Her satırda bir URL. Geçersiz URL'ler sessizce yok sayılacaktır.

Short information about how to use the textarea to import external filter lists by URL

Güncel değil.

used as a tooltip for the out-of-date icon beside a list

içeriği görüntüle

used as a tooltip for eye icon beside a list

Son güncelleme: {{ago}}.
Güncelleştirmeye zorlamak için tıklayın.

used as a tooltip for the clock icon beside a list

Güncelleniyor…

used as a tooltip for the spinner icon beside a list

Bir ağ hatası kaynağın güncellenmesini engelledi.

used as a tooltip for error icon beside a list

Her satırda bir filtre. Bir filtre yalın bir alan adı veya EasyList-uyumlu bir filtre olabilir. <code>!</code> ile başlayan satırlar yok sayılacaktır.

Short information about how to create custom filters

Güvenilmeyen kaynaklardan filtre eklemeyin.

Warning against copy-pasting filters from random sources

İçe aktar ve ekle

Button in the 'My filters' pane

Dışa aktar

Button in the 'My filters' pane

ublock-statik-filtrelerim_{{datetime}}.txt

English: my-ublock-static-filters_{{datetime}}.txt

Değişiklikleri uygula

English: Apply changes

Kalıcı kurallar

header

Geçici kurallar

header

Geri al

This will remove all temporary rules

İşle

This will persist temporary rules

Düzenle

Will enable manual-edit mode (textarea)

Kaydet

Will save manually-edited content and exit manual-edit mode

İptal et

Will discard manually-edited content and exit manual-edit mode

Dosyadan al…

Dosyaya aktar

Button in the 'My rules' pane

ublock-dinamik-kurallarım_{{datetime}}.txt

default file name to use

Dinamik filtreleme kurallarınızın listesi.

English: List of your dynamic filtering rules.

Kural sözdizimi: kaynak hedefi türü işlemi (bütün belgeler).

English: dynamic rule syntax and full documentation.

Sıralama:

English: label for sort option.

Kural türü

English: a sort option for list of rules.

Kaynak

Hedef

Güvenilen site yönergeleri, uBlock Origin'in devre dışı bırakılması gerektiği web sayfalarını belirler. Her satırda bir girdi.

A concise description of the 'Trusted sites' pane.

İçe aktar ve ekle

Button in the 'Trusted sites' pane

Dışa aktar

Button in the 'Trusted sites' pane

ublock-güvenilen-sitelerim_{{datetime}}.txt

The default filename to use for import/export purpose

Değişiklikleri uygula

English: Apply changes

Tür

English: Type

Alan adı

English: Domain

URL

English: URL

Filtre

English: Filter

Tümü

Appears in the logger's tab selector

Sekmesiz

Pretty name for behind-the-scene network requests

Geçerli sekme

Appears in the logger's tab selector

Sekme içeriğini tekrar yükle

Tooltip for the reload button in the logger page

DOM araştırıcısını aç/kapat

Tooltip for the DOM inspector button in the logger page

Açılır paneli aç/kapat

Tooltip for the popup panel button in the logger page

uBlock Origin viki: Günlükçü

Tooltip for the top-right info label in the logger page

Günlükçüyü temizle

Tooltip for the eraser in the logger page; used to blank the content of the logger

Günlükçüyü duraklat (tüm gelen veriyi yok say)

Tooltip for the pause button in the logger page

Günlükçüyü devam ettir

Tooltip for the play button in the logger page

Günlük filtrelemeyi aç/kapat

Tooltip for the row filterer button in the logger page

günlükçü içeriğini filtrele

Placeholder string for logger output filtering input field

Günlükçü filtreleme seçenekleri

Tooltip for the button to bring up logger output filtering options

Değil

A keyword in the built-in row filtering expression

olaylı

A keyword in the built-in row filtering expression: all items corresponding to uBO doing something (blocked, allowed, redirected, etc.)

engelli

izinli

düzenlendi

1. taraf

3. taraf

Ayrıntılar

Small header to identify the 'Details' pane for a specific logger entry

Filtre

Label to identify a filter field

Filtre listesi

Label to identify a filter list field

Kural

Label to identify a rule field

Bağlam

Label to identify a context field (typically a hostname)

Kök bağlam

Label to identify a root context field (typically a hostname)

Taraf

Label to identify a field providing partyness information

Tür

Label to identify the type of an entry

URL

Label to identify the URL of an entry

URL kuralı

Small header to identify the dynamic URL filtering section

Bağlam:

Label for the context selector

Tür:

Label for the type selector

Statik filtre

Small header to identify the static filtering section

{{url}} URL adresi ile eşleşen {{br}}ve kaynağı {{origin}} olan,{{br}}eşleşen bir istisna filtresi {{importance}} {{br}}{{type}} ağ isteklerini {{action}} {{br}}.

Used in the static filtering wizard

Engelle

Used in the static filtering wizard

İzin ver

Used in the static filtering wizard

“{{type}}” türündeki

Used in the static filtering wizard

her türdeki

Used in the static filtering wizard

“{{origin}}” adresi

Used in the static filtering wizard

her yerden

Used in the static filtering wizard

olmasa da

Used in the static filtering wizard

olsa da

Used in the static filtering wizard

Statik filtrenin <code>{{filter}}</code> bulunduğu listeler:

Below this sentence, the filter list(s) in which the filter was found

Statik filtre, şu anda etkin olan filtre listelerinde bulunamadı

Message to show when a filter cannot be found in any filter lists

Aşağıdaki üç koşulu sağlamayan günlükçü girdileri kendiliğinden yok sayılacaktır:

Logger setting: A sentence to describe the purpose of the settings below

Son {{input}} dakikadaki girdileri sakla

A logger setting

Her sekme için en çok {{input}} sayfa yüklemesi sakla

A logger setting

Her sekme için en çok {{input}} girdi sakla

A logger setting

Dikey genişletilmiş modda, her girdi için {{input}} satır kullan

A logger setting

Sütunları gizle:

Logger settings: a sentence to describe the purpose of the checkboxes below

{{input}} Tarih

A label for the time column

{{input}} Filtre/kural

A label for the filter or rule column

{{input}} Bağlam

A label for the context column

{{input}} Taraf

A label for the partyness column

Liste

Label for radio-button to pick export format

Tablo

Yalın

Label for radio-button to pick export text format

Markdown

Aç

Text for button which open an external webpage in Support pane

Yeni rapor oluştur

Benzer raporları bul

A clickable link in the filter issue reporter section

Belgeler

Header of 'Documentation' section in Support pane

uBlock Origin'in özelliklerinin tamamı hakkında bilgi edinmek için uBlock/wiki adresindeki belgeri okuyun.

First paragraph of 'Documentation' section in Support pane

Sorular ve destek

Header of 'Questions and support' section in Support pane

Sorulara cevaplar ve diğer türlü yardım desteği <code>/r/uBlockOrigin</code> subreddit'inde sunulmaktadır.

First paragraph of 'Questions and support' section in Support pane

Filtre sorunları/web sitesi bozuk

Header of 'Filter issues' section in Support pane

Belirli web siteleriyle ilgili filtre sorunlarını  uBlockOrigin/uAssets sorun izleyicisine bildirin. Github hesabı gerektirir.

First paragraph of 'Filter issues' section in Support pane

<b>Önemli:</b> uBlock Origin'le birlikte, belirli web sitelerinde filtre sorunlarına neden olabileceği için, diğer benzer amaçlı engelleyicileri kullanmaktan kaçının.

Second paragraph of 'Filter issues' section in Support pane

İpuçları: Filtre listenizin güncelliğinden emin olun. Günlükçü filtre bağlantılı sorunları tanılamada temel araçtır.

Third paragraph of 'Filter issues' section in Support pane

Hata raporu

Header of 'Bug report' section in Support pane

uBlock Origin'in kendisiyle ilgili sorunları uBlockOrigin/uBlock-issue sorun izleyicisine bildirin. Github hesabı gerektirir.

First paragraph of 'Bug report' section in Support pane

Hata Giderme Bilgisi

Header of 'Troubleshooting Information' section in Support pane

Aşağıdaki, gönüllüler size bir sorunu çözmede yardım etmeye çalışırken, faydalı olabilecek teknik bilgidir.

First paragraph of 'Troubleshooting Information' section in Support pane

<b>Önemli:</b> Olası gizli ve hassas bilgiler öntanımlı olarak kısaltılır. Kısaltılmış bilgiler bir sorunu çözmeyi zorlaştırabilir.

Second paragraph of 'Troubleshooting Information' section in Support pane

Bir filtre sorunu bildirin

Header of 'Report a filter issue' section in Support pane

Gönüllülere aynı raporlarla sıkıntı vermemek için, lütfen sorunun daha önce bildirilmediğinden emin olun.

A paragraph in the filter issue reporter section

Filtre listeleri günlük olarak güncellenir. Sorununuzun en son filtre listelerinde zaten ele alınıp alınmadığından emin olun.

Sorunlu web sayfasını yeniden yükleyerek sorunun devam ettiğini doğrulayın.

Web sayfasının adresi:

Label for the URL of the page

Web sayfası…

Label for widget to select type of issue

-- Bir girdi seçin --

An entry in the widget used to select the type of issue

Reklam veya reklam artıkları gösteriyor

Kaplamaları veya diğer sıkıntıları var

uBlock Origin’i algılıyor

Gizlilikle ilgili sorunları var

uBlock Origin etkinken bozuluyor

İstenmeyen sekme veya pencereler açıyor

Web sayfasını uygunsuz (“NSFW”) olarak etiketle (“Not Safe For Work”)

A checkbox to use for NSFW sites

Kısalt

Text for 'Redact' button

Kısaltma

Text for 'Unredact' button

Gizlilik ilkesi

Link to privacy policy on GitHub (English)

Değişiklik günlüğü

Kaynak kodu (GPLv3)

English: Source code (GPLv3)

Katkıda bulunanlar

English: Contributors

Kaynak kodu

Link text to source code repo

Çeviriler

Link text to translations repo

Filtre listeleri

Link text to uBO's own filter lists repo

Dış bağımlılıklar (GPLv3-uyumlu):

Shown in the About pane

uBO'nun kendi filtre listeleri ücretsiz olarak şu CDN'lerde barındırılır:

Shown in the About pane

Bir filtre listesinin güncellenmesi gerektiğinde rastgele seçilen bir CDN kullanılır

Shown in the About pane

Dosyaya yedekle…

Text for button to create a backup of all settings

English: my-ublock-backup_{{datetime}}.txt

Dosyadan geri yükle…

English: Restore from file...

Varsayılan ayarlara sıfırla…

English: Reset to default settings...

Tüm ayarlarınız {{time}} tarihinde yedeklenmiş veriler kullanarak değiştirilecek ve uBlock₀ yeniden başlayacak.

Yedeklenmiş verileriniz kullanılarak var olan tüm ayarlarınız değiştirilsin mi?

Message asking user to confirm restore

Veri okunamadı veya geçersiz

Message to display when an error occurred during restore

Tüm ayarlarınızı silinecek, ve uBlock₀ yeniden başlayacak.

uBlock₀ fabrika ayarlarına sıfırlansın mı?

Message asking user to confirm reset

Ağ hatası: {{msg}}

English: Network error: {{msg}}

Bu URL, özel filtre listenize eklensin mi?

Başlık: "{{title}}"
URL: {{url}}

No longer used

Abone ol

For the button used to subscribe to a filter list

bir dakika önce

English: a minute ago

{{value}} dakika önce

English: {{value}} minutes ago

bir saat önce

English: an hour ago

{{value}} saat önce

English: {{value}} hours ago

bir gün önce

English: a day ago

{{value}} gün önce

English: {{value}} days ago

Kontrol Panelini Göster

Firefox/Fennec-specific: Show Dashboard

Günlükçüyü Göster

Firefox/Fennec-specific: Show Logger

kapalı

Firefox-specific: appears as 'uBlock₀ (off)'

Sayfa engellendi

Used as a title for the document-blocked page

uBlock Origin aşağıdaki sayfanın yüklenmesini engelledi:

Used in the strict-blocking page

Bu filtre nedeniyle:

Used in the strict-blocking page

değişkensiz

label to be used for the parameter-less URL: https://cloud.githubusercontent.com/assets/585534/9832014/bfb1b8f0-593b-11e5-8a27-fba472a5529a.png

Bulunduğu liste:

English: List of filter list names follows

Geri git

English: Go back

Bu pencereyi kapat

English: Close this window

Bu site için beni bir daha uyarma

Label for checkbox in document-blocked page

{{hostname}} için katı engellemeyi devre dışı bırak

English: Disable strict blocking for {{hostname}} ...

Geçici olarak

English: Temporarily

Kalıcı olarak

English: Permanently

Devam et

Button text to navigate to the blocked page

Bulut depolamaya aktar

tooltip

Bulut depolamadan içe aktar

tooltip

Bulut depolamadan içe aktar ve şu anki ayarlarla birleştir

tooltip

Bu aygıtın adı:

used as a prompt for the user to provide a custom device name

Uyarı! Bu gelişmiş ayarları değiştirmenin sorumluluğu size aittir.

A warning to users at the top of 'Advanced settings' page

Gönder

for generic 'Submit' buttons

Değişiklikleri uygula

for generic 'Apply changes' buttons

Geri al

for generic 'Revert' buttons

byte

Çerçevedeki ögeyi engelle…

An entry in the browser's contextual menu

Filtre listesine abone ol…

Geçici olarak büyük medya ögelerine izin ver

A context menu entry, present when large media elements have been blocked on the current site

Kaynağı görüntüle…

A context menu entry, to view the source code of the target resource

Bir kısayol girin

Placeholder string for input field used to capture a keyboard shortcut

Kilitli kaydırmayı aç/kapat

Tooltip for the button used to lock scrolling between the views in the 'My rules' pane

Panoya kopyala

Label for buttons used to copy something to the clipboard

Tümünü seç

Label for buttons used to select all text in editor

Kozmetik filtrelemeyi aç/kapat

Label for keyboard shortcut used to toggle cosmetic filtering

Javascript'i Aç/Kapa

Label for keyboard shortcut used to toggle no-scripting switch

Engelleme modunu gevşet

Label for keyboard shortcut used to relax blocking mode

Kullanılan depolama: {{value}} {{unit}}

In Setting pane, renders as (example): Storage used: 13.2 MB

KB

short for 'kilobytes'

MB

short for 'megabytes'

GB

short for 'gigabytes'

Yüklemek için tıkla

Message used in frame placeholders

Hata sayısı: {{count}}

Summary of number of errors as reported by the linter

Tarayıcı açılışında düzgün filtreleme yapılamadı.
Düzgün filtreleme için sayfayı yenileyin.

A warning which will appear in the popup panel if needed

Bu girdi en sonda olmalıdır

so we dont need to deal with comma for last entry