OLD | NEW |
1 /* | 1 /* |
2 * This Source Code is subject to the terms of the Mozilla Public License | 2 * This Source Code is subject to the terms of the Mozilla Public License |
3 * version 2.0 (the "License"). You can obtain a copy of the License at | 3 * version 2.0 (the "License"). You can obtain a copy of the License at |
4 * http://mozilla.org/MPL/2.0/. | 4 * http://mozilla.org/MPL/2.0/. |
5 */ | 5 */ |
6 | 6 |
7 const Cc = Components.classes; | 7 const Cc = Components.classes; |
8 const Ci = Components.interfaces; | 8 const Ci = Components.interfaces; |
9 const Cr = Components.results; | 9 const Cr = Components.results; |
10 const Cu = Components.utils; | 10 const Cu = Components.utils; |
11 | 11 |
12 Cu.import("resource://gre/modules/Services.jsm"); | 12 Cu.import("resource://gre/modules/Services.jsm"); |
13 | 13 |
14 function abprequire(module) | 14 function abprequire(module) |
15 { | 15 { |
16 let result = {}; | 16 let result = {}; |
17 result.wrappedJSObject = result; | 17 result.wrappedJSObject = result; |
18 Services.obs.notifyObservers(result, "adblockplus-require", module); | 18 Services.obs.notifyObservers(result, "adblockplus-require", module); |
19 return result.exports; | 19 return result.exports; |
20 } | 20 } |
21 | 21 |
22 let {Policy} = abprequire("contentPolicy"); | 22 let {Policy} = abprequire("contentPolicy"); |
23 let {RequestNotifier} = abprequire("requestNotifier"); | 23 let {RequestNotifier} = abprequire("requestNotifier"); |
24 let {Filter} = abprequire("filterClasses"); | 24 let {Filter} = abprequire("filterClasses"); |
25 | 25 |
26 let policyGlobal = Cu.getGlobalForObject(Policy); | 26 let origShouldAllow = Policy.shouldAllow; |
27 let PolicyPrivate = null; | 27 if (!origShouldAllow) |
28 | |
29 if ("PolicyImplementation" in policyGlobal) // ABP 2.1+ with scope separation | |
30 PolicyPrivate = policyGlobal.PolicyImplementation; | |
31 else if ("require" in policyGlobal) // ABP 2.1+ without scope sepa
ration | |
32 PolicyPrivate = policyGlobal.require.scopes.contentPolicy.PolicyImplementation
; | |
33 else | |
34 window.close(); | 28 window.close(); |
35 | 29 |
36 let origShouldLoad = PolicyPrivate.shouldLoad; | |
37 let origProcessNode = Policy.processNode; | |
38 | |
39 let currentData = null; | 30 let currentData = null; |
40 let processingQueue = []; | 31 let processingQueue = []; |
41 let notifier = null; | 32 let notifier = null; |
42 | 33 |
43 // Randomize URI to work around bug 719376 | 34 // Randomize URI to work around bug 719376 |
44 let stringBundle = Services.strings.createBundle("chrome://abpwatcher/locale/glo
bal.properties?" + Math.random()); | 35 let stringBundle = Services.strings.createBundle("chrome://abpwatcher/locale/glo
bal.properties?" + Math.random()); |
45 | 36 |
46 let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.
nsIClipboardHelper); | 37 let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.
nsIClipboardHelper); |
47 | 38 |
48 function init() | 39 function init() |
49 { | 40 { |
50 let list = document.getElementById("list"); | 41 let list = document.getElementById("list"); |
51 list.view = treeView; | 42 list.view = treeView; |
52 list.focus(); | 43 list.focus(); |
53 | 44 |
54 treeView.addObserver(updateProcessingTime); | 45 treeView.addObserver(updateProcessingTime); |
55 updateProcessingTime(treeView, "refresh"); | 46 updateProcessingTime(treeView, "refresh"); |
56 | 47 |
57 // Make sure the tree view has correct filters | 48 // Make sure the tree view has correct filters |
58 document.getElementById("ignore-early").doCommand(); | |
59 document.getElementById("filterText").doCommand(); | 49 document.getElementById("filterText").doCommand(); |
60 | 50 |
61 notifier = new RequestNotifier(null, handleFilterHit); | 51 notifier = new RequestNotifier(null, handleFilterHit); |
62 | 52 |
63 PolicyPrivate.shouldLoad = replacementShouldLoad; | 53 Policy.shouldAllow = replacementShouldAllow; |
64 Policy.processNode = replacementProcessNode; | |
65 setInterval(processQueue, 200); | 54 setInterval(processQueue, 200); |
66 } | 55 } |
67 | 56 |
68 function E(id) | 57 function E(id) |
69 { | 58 { |
70 return document.getElementById(id); | 59 return document.getElementById(id); |
71 } | 60 } |
72 | 61 |
73 function replacementShouldLoad(contentType, contentLocation, requestOrigin, node
, mimeTypeGuess, extra) | 62 function replacementShouldAllow({contentType, location, frames, isPrivate}) |
74 { | 63 { |
75 let startTime = null; | 64 let startTime = null; |
76 try | 65 try |
77 { | 66 { |
78 currentData = {internal: false, earlyReturn: true, filters: []}; | 67 currentData = { |
| 68 filters: [], |
| 69 type: contentType, |
| 70 location: location, |
| 71 frames: frames, |
| 72 isPrivate: isPrivate |
| 73 }; |
79 startTime = Date.now(); | 74 startTime = Date.now(); |
80 | |
81 if (contentLocation) | |
82 currentData.location = contentLocation.spec; | |
83 if (requestOrigin) | |
84 currentData.origin = requestOrigin.spec; | |
85 | |
86 currentData.type = contentType; | |
87 } catch(e) {} | |
88 | |
89 let ret; | |
90 try | |
91 { | |
92 ret = origShouldLoad.apply(this, arguments); | |
93 return ret; | |
94 } | |
95 finally | |
96 { | |
97 if (startTime !== null) | |
98 currentData.processingTime = (Date.now() - startTime); | |
99 currentData.result = (ret == Ci.nsIContentPolicy.ACCEPT); | |
100 | |
101 processingQueue.push(currentData); | |
102 currentData = null; | |
103 } | |
104 } | |
105 | |
106 function replacementProcessNode(wnd, node, contentType, location, collapse) | |
107 { | |
108 let startTime = null; | |
109 try | |
110 { | |
111 if (currentData && !("context" in currentData)) | |
112 { | |
113 currentData.earlyReturn = false; | |
114 currentData.context = node; | |
115 currentData.window = wnd; | |
116 currentData.internalType = contentType; | |
117 if (location) | |
118 currentData.internalLocation = location.spec; | |
119 } | |
120 else | |
121 { | |
122 // shouldLoad wasn't called - this isn't being called by content policy | |
123 let locationString = (location instanceof Filter ? location.text : locatio
n.spec); | |
124 | |
125 currentData = { | |
126 internal: true, | |
127 earlyReturn: false, | |
128 filters: [], | |
129 location: locationString, | |
130 internalLocation: locationString, | |
131 context: node, | |
132 window: wnd, | |
133 type: contentType, | |
134 internalType: contentType | |
135 }; | |
136 startTime = Date.now(); | |
137 } | |
138 } | 75 } |
139 catch(e) | 76 catch(e) |
140 { | 77 { |
141 Cu.reportError(e); | 78 Cu.reportError(e); |
142 } | 79 } |
143 | 80 |
144 let ret; | 81 let ret; |
145 try | 82 try |
146 { | 83 { |
147 ret = origProcessNode.apply(this, arguments); | 84 ret = origShouldAllow.apply(this, arguments); |
148 return ret; | 85 return ret; |
149 } | 86 } |
150 finally | 87 finally |
151 { | 88 { |
152 if (startTime !== null) | 89 if (startTime !== null) |
153 { | 90 { |
154 currentData.processingTime = (Date.now() - startTime); | 91 currentData.processingTime = (Date.now() - startTime); |
155 currentData.result = (ret == true); | 92 currentData.result = ret; |
156 | 93 |
157 processingQueue.push(currentData); | 94 processingQueue.push(currentData); |
158 currentData = null; | 95 currentData = null; |
159 } | 96 } |
160 } | 97 } |
161 } | 98 } |
162 | 99 |
163 function destroy() | 100 function destroy() |
164 { | 101 { |
165 if (notifier) | 102 if (notifier) |
166 notifier.shutdown(); | 103 notifier.shutdown(); |
167 if (origShouldLoad) | 104 if (origShouldAllow) |
168 PolicyPrivate.shouldLoad = origShouldLoad; | 105 Policy.shouldAllow = origShouldAllow; |
169 if (origProcessNode) | |
170 Policy.processNode = origProcessNode; | |
171 } | 106 } |
172 | 107 |
173 function handleFilterHit(wnd, node, data) | 108 function handleFilterHit(data) |
174 { | 109 { |
175 if (data.filter && currentData) | 110 if (data.filter && currentData) |
176 currentData.filters.push(data.filter.text); | 111 currentData.filters.push(data.filter); |
177 } | 112 } |
178 | 113 |
179 function processQueue() | 114 function processQueue() |
180 { | 115 { |
181 if (!processingQueue.length) | 116 if (!processingQueue.length) |
182 return; | 117 return; |
183 | 118 |
| 119 function stringify(value) |
| 120 { |
| 121 if (typeof value == "undefined" || value == null) |
| 122 return ""; |
| 123 else |
| 124 return String(value); |
| 125 } |
| 126 |
184 for each (let entry in processingQueue) | 127 for each (let entry in processingQueue) |
185 { | 128 { |
186 entry.cols = {}; | 129 entry.cols = { |
187 if (typeof entry.location != "undefined") | 130 address: stringify(entry.location), |
188 entry.cols.address = String(entry.location); | 131 type: stringify(entry.type), |
189 if (typeof entry.type != "undefined") | 132 result: stringBundle.GetStringFromName(entry.result && entry.result.allow
? "decision.allow" : "decision.block"), |
190 { | 133 origin: stringify(entry.frames && entry.frames[0] && entry.frames[0].locat
ion), |
191 entry.cols.type = String(entry.type); | 134 filters: stringify(entry.filters && entry.filters.join(", ")), |
192 try { | 135 time: stringify(entry.processingTime) |
193 // Nasty hack: try to get type name from ABP | 136 }; |
194 if (entry.type in Policy.localizedDescr) | |
195 entry.cols.type = String(Policy.localizedDescr[entry.type]); | |
196 } catch(e) {} | |
197 } | |
198 entry.cols.result = stringBundle.GetStringFromName(entry.result ? "decision.
allow" : "decision.block"); | |
199 if (typeof entry.context != "undefined") | |
200 entry.cols.context = (entry.context ? getNodeLabel(entry.context) : String
(entry.context)); | |
201 if (typeof entry.window != "undefined") | |
202 entry.cols.document = (entry.window ? getNodeLabel(entry.window) : String(
entry.window)); | |
203 if (typeof entry.origin != "undefined") | |
204 entry.cols.origin = String(entry.origin); | |
205 if (entry.filters.length) | |
206 entry.cols.filter = entry.filters.join(", "); | |
207 if (typeof entry.processingTime != "undefined") | |
208 entry.cols.time = String(entry.processingTime); | |
209 | |
210 let additional = []; | |
211 if (entry.internal) | |
212 additional.push(stringBundle.GetStringFromName("additional.internalInvocat
ion")); | |
213 if (typeof entry.internalType != "undefined" && entry.type != entry.internal
Type) | |
214 { | |
215 let internalType = String(entry.internalType); | |
216 try { | |
217 // Nasty hack: try to get type name from ABP | |
218 if (entry.internalType in Policy.localizedDescr) | |
219 internalType = String(Policy.localizedDescr[entry.internalType]); | |
220 } catch(e) {} | |
221 additional.push(stringBundle.formatStringFromName("additional.typeChanged"
, [internalType], 1)); | |
222 } | |
223 if (typeof entry.internalLocation != "undefined" && entry.location != entry.
internalLocation) | |
224 additional.push(stringBundle.formatStringFromName("additional.locationChan
ged", [String(entry.internalLocation)], 1)); | |
225 | |
226 if (additional.length > 0) | |
227 entry.cols.additional = additional.join(", "); | |
228 | |
229 treeView.add(entry); | 137 treeView.add(entry); |
230 } | 138 } |
231 | 139 |
232 processingQueue = []; | 140 processingQueue = []; |
233 } | 141 } |
234 | 142 |
235 function getNodeLabel(node) | |
236 { | |
237 try | |
238 { | |
239 if (node instanceof Ci.nsIDOMWindow) | |
240 return stringBundle.formatStringFromName("NodeLabel.window", [node.locatio
n.href], 1); | |
241 if (node instanceof Ci.nsIDOMDocument) | |
242 return stringBundle.formatStringFromName("NodeLabel.document", [node.URL],
1); | |
243 else if (node instanceof Ci.nsIDOMXULElement) | |
244 return stringBundle.formatStringFromName("NodeLabel.xulElement", [node.tag
Name], 1); | |
245 else if (node instanceof Ci.nsIDOMHTMLElement) | |
246 return stringBundle.formatStringFromName("NodeLabel.htmlElement", [node.ta
gName], 1); | |
247 else if (node instanceof Ci.nsIDOMSVGElement) | |
248 return stringBundle.formatStringFromName("NodeLabel.svgElement", [node.tag
Name], 1); | |
249 else if (node instanceof Ci.nsIDOMElement) | |
250 return stringBundle.formatStringFromName("NodeLabel.element", [node.tagNam
e], 1); | |
251 else | |
252 return stringBundle.formatStringFromName("NodeLabel.unknown", [String(node
)], 1); | |
253 } | |
254 catch (e) | |
255 { | |
256 Cu.reportError(e); | |
257 return stringBundle.formatStringFromName("NodeLabel.unknown", [""], 1); | |
258 } | |
259 } | |
260 | |
261 function fillInTooltip(event) | 143 function fillInTooltip(event) |
262 { | 144 { |
263 let entry = treeView.getEntryAt(event.clientX, event.clientY); | 145 let entry = treeView.getEntryAt(event.clientX, event.clientY); |
264 if (!entry) | 146 if (!entry) |
265 return false; | 147 return false; |
266 | 148 |
267 let rows = document.getElementById("tooltip-rows"); | 149 let rows = document.getElementById("tooltip-rows"); |
268 while (rows.firstChild) | 150 while (rows.firstChild) |
269 rows.removeChild(rows.firstChild); | 151 rows.removeChild(rows.firstChild); |
270 | 152 |
271 let cols = document.getElementById("list").getElementsByTagName("treecol"); | 153 let cols = document.getElementById("list").getElementsByTagName("treecol"); |
272 for (let i = 0; i < cols.length; i++) | 154 for (let i = 0; i < cols.length; i++) |
273 { | 155 { |
274 let col = cols[i].id; | 156 let col = cols[i].id; |
275 if (col && col in entry.cols) | 157 if (col && col in entry.cols) |
276 { | 158 { |
277 let row = document.createElement("row"); | 159 let row = document.createElement("row"); |
278 | 160 |
279 let label = document.createElement("description"); | 161 let label = document.createElement("description"); |
280 label.setAttribute("class", "tooltip-label"); | 162 label.setAttribute("class", "tooltip-label"); |
281 label.setAttribute("value", cols[i].getAttribute("label")); | 163 label.setAttribute("value", cols[i].getAttribute("label")); |
282 row.appendChild(label); | 164 row.appendChild(label); |
283 | 165 |
284 let value = document.createElement("vbox"); | 166 let value = document.createElement("vbox"); |
285 setMultilineContent(value, entry.cols[col]); | 167 let data = entry.cols[col]; |
| 168 if (col == "origin") |
| 169 data = entry.frames.map(f => f.location).join("\n"); |
| 170 setMultilineContent(value, data); |
286 row.appendChild(value); | 171 row.appendChild(value); |
287 | 172 |
288 rows.appendChild(row); | 173 rows.appendChild(row); |
289 } | 174 } |
290 } | 175 } |
291 | 176 |
292 return true; | 177 return true; |
293 } | 178 } |
294 | 179 |
295 function updateContextMenu(event) | 180 function updateContextMenu(event) |
(...skipping 14 matching lines...) Expand all Loading... |
310 clipboardHelper.copyString(String(entry.location)); | 195 clipboardHelper.copyString(String(entry.location)); |
311 } | 196 } |
312 | 197 |
313 function copyFilters() | 198 function copyFilters() |
314 { | 199 { |
315 let entry = treeView.getCurrentEntry(); | 200 let entry = treeView.getCurrentEntry(); |
316 if (entry && entry.filters.length) | 201 if (entry && entry.filters.length) |
317 clipboardHelper.copyString(entry.filters.join("\n")); | 202 clipboardHelper.copyString(entry.filters.join("\n")); |
318 } | 203 } |
319 | 204 |
320 function setMultilineContent(box, text) { | 205 function setMultilineContent(box, text) |
321 // The following is sufficient in Gecko 1.9 but Gecko 1.8 fails on multiline | 206 { |
322 // text fields in tooltips | 207 let lines = text.split(/\n+/); |
323 // box.textContent = text.replace(/\S{80}(?=\S)/g, "$& "); | 208 for (let line of lines) |
324 | 209 { |
325 for (let i = 0; i < text.length; i += 80) { | |
326 let description = document.createElement("description"); | 210 let description = document.createElement("description"); |
327 description.setAttribute("value", text.substr(i, 80)); | 211 description.textContent = line.replace(/\S{80}(?=\S)/g, "$& "); |
328 box.appendChild(description); | 212 box.appendChild(description); |
329 } | 213 } |
330 } | 214 } |
331 | 215 |
332 var totalProcessingTime = 0; | 216 var totalProcessingTime = 0; |
333 function updateProcessingTime(view, operation, entry) | 217 function updateProcessingTime(view, operation, entry) |
334 { | 218 { |
335 if (operation == "add") | 219 if (operation == "add") |
336 totalProcessingTime += entry.processingTime; | 220 totalProcessingTime += entry.processingTime; |
337 else { | 221 else { |
338 totalProcessingTime = 0; | 222 totalProcessingTime = 0; |
339 for each (let entry in view.displayedItems) | 223 for each (let entry in view.displayedItems) |
340 totalProcessingTime += entry.processingTime; | 224 totalProcessingTime += entry.processingTime; |
341 } | 225 } |
342 | 226 |
343 let numItems = view.displayedItems.length; | 227 let numItems = view.displayedItems.length; |
344 | 228 |
345 let summary = document.getElementById("summary"); | 229 let summary = document.getElementById("summary"); |
346 let template = summary.getAttribute("_template"); | 230 let template = summary.getAttribute("_template"); |
347 summary.textContent = template.replace(/\*NUMITEMS\*/g, numItems).replace(/\*T
IME\*/, (totalProcessingTime / 1000).toFixed(3)); | 231 summary.textContent = template.replace(/\*NUMITEMS\*/g, numItems).replace(/\*T
IME\*/, (totalProcessingTime / 1000).toFixed(3)); |
348 } | 232 } |
349 | 233 |
350 var treeView = { | 234 var treeView = { |
351 currentItems: [], | 235 currentItems: [], |
352 displayedItems: [], | 236 displayedItems: [], |
353 _ignoreEarlyReturns: false, | |
354 _filterString: "", | 237 _filterString: "", |
355 _sortColumn: null, | 238 _sortColumn: null, |
356 _sortDirection: null, | 239 _sortDirection: null, |
357 boxObject: null, | 240 boxObject: null, |
358 atoms: {}, | 241 atoms: {}, |
359 observers: [], | 242 observers: [], |
360 | 243 |
361 // | 244 // |
362 // nsISupports implementation | 245 // nsISupports implementation |
363 // | 246 // |
(...skipping 15 matching lines...) Expand all Loading... |
379 selection: null, | 262 selection: null, |
380 | 263 |
381 setTree: function(boxObject) | 264 setTree: function(boxObject) |
382 { | 265 { |
383 if (!boxObject) | 266 if (!boxObject) |
384 return; | 267 return; |
385 | 268 |
386 this.boxObject = boxObject; | 269 this.boxObject = boxObject; |
387 | 270 |
388 let atomService = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomSer
vice); | 271 let atomService = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomSer
vice); |
389 for each (let col in ["address", "type", "result", "context", "document", "o
rigin", "additional", "filter", "time"]) | 272 for each (let col in ["address", "type", "result", "origin", "filter", "time
"]) |
390 { | 273 { |
391 let atomStr = "col-" + col; | 274 let atomStr = "col-" + col; |
392 this.atoms[atomStr] = atomService.getAtom(atomStr); | 275 this.atoms[atomStr] = atomService.getAtom(atomStr); |
393 } | 276 } |
394 for each (let flag in ["selected", "blocked"]) | 277 for each (let flag in ["selected", "blocked"]) |
395 { | 278 { |
396 let atomStr = flag + "-true"; | 279 let atomStr = flag + "-true"; |
397 this.atoms[atomStr] = atomService.getAtom(atomStr); | 280 this.atoms[atomStr] = atomService.getAtom(atomStr); |
398 | 281 |
399 atomStr = flag + "-false"; | 282 atomStr = flag + "-false"; |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
454 }, | 337 }, |
455 | 338 |
456 getRowProperties: function(row, properties) | 339 getRowProperties: function(row, properties) |
457 { | 340 { |
458 if (row < 0 || row >= this.displayedItems.length) | 341 if (row < 0 || row >= this.displayedItems.length) |
459 return ""; | 342 return ""; |
460 | 343 |
461 let entry = this.displayedItems[row]; | 344 let entry = this.displayedItems[row]; |
462 return this.generateProperties([ | 345 return this.generateProperties([ |
463 "selected-" + this.selection.isSelected(row), | 346 "selected-" + this.selection.isSelected(row), |
464 "blocked-" + !entry.result | 347 "blocked-" + !(entry.result && entry.result.allow) |
465 ], properties); | 348 ], properties); |
466 }, | 349 }, |
467 | 350 |
468 getCellProperties: function(row, col, properties) | 351 getCellProperties: function(row, col, properties) |
469 { | 352 { |
470 return this.getRowProperties(row, properties) + " " + this.getColumnProperti
es(col, properties); | 353 return this.getRowProperties(row, properties) + " " + this.getColumnProperti
es(col, properties); |
471 }, | 354 }, |
472 | 355 |
473 cycleHeader: function(col) | 356 cycleHeader: function(col) |
474 { | 357 { |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
524 cycleCell: function() {}, | 407 cycleCell: function() {}, |
525 performAction: function() {}, | 408 performAction: function() {}, |
526 performActionOnRow: function() {}, | 409 performActionOnRow: function() {}, |
527 performActionOnCell: function() {}, | 410 performActionOnCell: function() {}, |
528 selectionChanged: function() {}, | 411 selectionChanged: function() {}, |
529 | 412 |
530 // | 413 // |
531 // Custom methods | 414 // Custom methods |
532 // | 415 // |
533 | 416 |
534 get ignoreEarlyReturns() | |
535 { | |
536 return this._ignoreEarlyReturns; | |
537 }, | |
538 set ignoreEarlyReturns(value) | |
539 { | |
540 this._ignoreEarlyReturns = value; | |
541 this.refilter(); | |
542 }, | |
543 | |
544 get filterString() | 417 get filterString() |
545 { | 418 { |
546 return this._filterString; | 419 return this._filterString; |
547 }, | 420 }, |
548 set filterString(value) | 421 set filterString(value) |
549 { | 422 { |
550 this._filterString = value.toLowerCase(); | 423 this._filterString = value.toLowerCase(); |
551 this.refilter(); | 424 this.refilter(); |
552 }, | 425 }, |
553 | 426 |
554 filter: function(entry) | 427 filter: function(entry) |
555 { | 428 { |
556 if (this._ignoreEarlyReturns && entry.earlyReturn) | |
557 return false; | |
558 | |
559 if (this._filterString) | 429 if (this._filterString) |
560 { | 430 { |
561 let foundMatch = false; | 431 let foundMatch = false; |
562 for each (let label in entry.cols) | 432 for each (let label in entry.cols) |
563 if (label.toLowerCase().indexOf(this._filterString) >= 0) | 433 if (label.toLowerCase().indexOf(this._filterString) >= 0) |
564 foundMatch = true; | 434 foundMatch = true; |
565 | 435 |
566 if (!foundMatch) | 436 if (!foundMatch) |
567 return false; | 437 return false; |
568 } | 438 } |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
678 for (let i = 0; i < this.observers.length; i++) | 548 for (let i = 0; i < this.observers.length; i++) |
679 if (this.observers[i] == observer) | 549 if (this.observers[i] == observer) |
680 this.observers.splice(i--, 1); | 550 this.observers.splice(i--, 1); |
681 }, | 551 }, |
682 notifyObservers: function(operation, entry) | 552 notifyObservers: function(operation, entry) |
683 { | 553 { |
684 for each (let observer in this.observers) | 554 for each (let observer in this.observers) |
685 observer(this, operation, entry); | 555 observer(this, operation, entry); |
686 } | 556 } |
687 }; | 557 }; |
OLD | NEW |