Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: extensions/renderer/resources/event.js

Issue 482603002: Unify logic of stack trace generation for extension errors (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add $Error and $String.indexOf to safe builtins Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 var exceptionHandler = require('uncaught_exception_handler'); 5 var exceptionHandler = require('uncaught_exception_handler');
6 var eventNatives = requireNative('event_natives'); 6 var eventNatives = requireNative('event_natives');
7 var logging = requireNative('logging'); 7 var logging = requireNative('logging');
8 var schemaRegistry = requireNative('schema_registry'); 8 var schemaRegistry = requireNative('schema_registry');
9 var sendRequest = require('sendRequest').sendRequest; 9 var sendRequest = require('sendRequest').sendRequest;
10 var utils = require('utils'); 10 var utils = require('utils');
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 this.event_ = event; 95 this.event_ = event;
96 this.listenerMap_ = {}; 96 this.listenerMap_ = {};
97 }; 97 };
98 98
99 FilteredAttachmentStrategy.idToEventMap = {}; 99 FilteredAttachmentStrategy.idToEventMap = {};
100 100
101 FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) { 101 FilteredAttachmentStrategy.prototype.onAddedListener = function(listener) {
102 var id = eventNatives.AttachFilteredEvent(this.event_.eventName, 102 var id = eventNatives.AttachFilteredEvent(this.event_.eventName,
103 listener.filters || {}); 103 listener.filters || {});
104 if (id == -1) 104 if (id == -1)
105 throw new Error("Can't add listener"); 105 throw new $Error.self("Can't add listener");
not at google - send to devlin 2014/08/19 16:45:55 new Error()
106 listener.id = id; 106 listener.id = id;
107 this.listenerMap_[id] = listener; 107 this.listenerMap_[id] = listener;
108 FilteredAttachmentStrategy.idToEventMap[id] = this.event_; 108 FilteredAttachmentStrategy.idToEventMap[id] = this.event_;
109 }; 109 };
110 110
111 FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) { 111 FilteredAttachmentStrategy.prototype.onRemovedListener = function(listener) {
112 this.detachListener(listener, true); 112 this.detachListener(listener, true);
113 }; 113 };
114 114
115 FilteredAttachmentStrategy.prototype.detachListener = 115 FilteredAttachmentStrategy.prototype.detachListener =
116 function(listener, manual) { 116 function(listener, manual) {
117 if (listener.id == undefined) 117 if (listener.id == undefined)
118 throw new Error("listener.id undefined - '" + listener + "'"); 118 throw new $Error.self("listener.id undefined - '" + listener + "'");
119 var id = listener.id; 119 var id = listener.id;
120 delete this.listenerMap_[id]; 120 delete this.listenerMap_[id];
121 delete FilteredAttachmentStrategy.idToEventMap[id]; 121 delete FilteredAttachmentStrategy.idToEventMap[id];
122 eventNatives.DetachFilteredEvent(id, manual); 122 eventNatives.DetachFilteredEvent(id, manual);
123 }; 123 };
124 124
125 FilteredAttachmentStrategy.prototype.detach = function(manual) { 125 FilteredAttachmentStrategy.prototype.detach = function(manual) {
126 for (var i in this.listenerMap_) 126 for (var i in this.listenerMap_)
127 this.detachListener(this.listenerMap_[i], manual); 127 this.detachListener(this.listenerMap_[i], manual);
128 }; 128 };
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
193 // extension event rather than a <webview> event. 193 // extension event rather than a <webview> event.
194 var EventImpl = function(opt_eventName, opt_argSchemas, opt_eventOptions, 194 var EventImpl = function(opt_eventName, opt_argSchemas, opt_eventOptions,
195 opt_webViewInstanceId) { 195 opt_webViewInstanceId) {
196 this.eventName = opt_eventName; 196 this.eventName = opt_eventName;
197 this.argSchemas = opt_argSchemas; 197 this.argSchemas = opt_argSchemas;
198 this.listeners = []; 198 this.listeners = [];
199 this.eventOptions = parseEventOptions(opt_eventOptions); 199 this.eventOptions = parseEventOptions(opt_eventOptions);
200 this.webViewInstanceId = opt_webViewInstanceId || 0; 200 this.webViewInstanceId = opt_webViewInstanceId || 0;
201 201
202 if (!this.eventName) { 202 if (!this.eventName) {
203 if (this.eventOptions.supportsRules) 203 if (this.eventOptions.supportsRules)
not at google - send to devlin 2014/08/19 16:45:55 Oh man some of these are so subtle. Errors that we
robwu 2014/08/19 17:35:57 Now I'm a bit confused. Why should $Error.self be
not at google - send to devlin 2014/08/19 17:54:38 There are 2 scenarios here: (1) The error is the
robwu 2014/08/19 20:51:37 I have reverted every use of "throw new $Error.sel
204 throw new Error("Events that support rules require an event name."); 204 throw new $Error.self(
not at google - send to devlin 2014/08/19 16:45:55 new Error()
205 "Events that support rules require an event name.");
205 // Events without names cannot be managed by the browser by definition 206 // Events without names cannot be managed by the browser by definition
206 // (the browser has no way of identifying them). 207 // (the browser has no way of identifying them).
207 this.eventOptions.unmanaged = true; 208 this.eventOptions.unmanaged = true;
208 } 209 }
209 210
210 // Track whether the event has been destroyed to help track down the cause 211 // Track whether the event has been destroyed to help track down the cause
211 // of http://crbug.com/258526. 212 // of http://crbug.com/258526.
212 // This variable will eventually hold the stack trace of the destroy call. 213 // This variable will eventually hold the stack trace of the destroy call.
213 // TODO(kalman): Delete this and replace with more sound logic that catches 214 // TODO(kalman): Delete this and replace with more sound logic that catches
214 // when events are used without being *attached*. 215 // when events are used without being *attached*.
215 this.destroyed = null; 216 this.destroyed = null;
216 217
217 if (this.eventOptions.unmanaged) 218 if (this.eventOptions.unmanaged)
218 this.attachmentStrategy = new NullAttachmentStrategy(this); 219 this.attachmentStrategy = new NullAttachmentStrategy(this);
219 else if (this.eventOptions.supportsFilters) 220 else if (this.eventOptions.supportsFilters)
220 this.attachmentStrategy = new FilteredAttachmentStrategy(this); 221 this.attachmentStrategy = new FilteredAttachmentStrategy(this);
221 else 222 else
222 this.attachmentStrategy = new UnfilteredAttachmentStrategy(this); 223 this.attachmentStrategy = new UnfilteredAttachmentStrategy(this);
223 }; 224 };
224 225
225 // callback is a function(args, dispatch). args are the args we receive from 226 // callback is a function(args, dispatch). args are the args we receive from
226 // dispatchEvent(), and dispatch is a function(args) that dispatches args to 227 // dispatchEvent(), and dispatch is a function(args) that dispatches args to
227 // its listeners. 228 // its listeners.
228 function registerArgumentMassager(name, callback) { 229 function registerArgumentMassager(name, callback) {
229 if (eventArgumentMassagers[name]) 230 if (eventArgumentMassagers[name])
230 throw new Error("Massager already registered for event: " + name); 231 throw new $Error.self("Massager already registered for event: " + name);
231 eventArgumentMassagers[name] = callback; 232 eventArgumentMassagers[name] = callback;
232 } 233 }
233 234
234 // Dispatches a named event with the given argument array. The args array is 235 // Dispatches a named event with the given argument array. The args array is
235 // the list of arguments that will be sent to the event callback. 236 // the list of arguments that will be sent to the event callback.
236 function dispatchEvent(name, args, filteringInfo) { 237 function dispatchEvent(name, args, filteringInfo) {
237 var listenerIDs = []; 238 var listenerIDs = [];
238 239
239 if (filteringInfo) 240 if (filteringInfo)
240 listenerIDs = eventNatives.MatchAgainstEventFilter(name, filteringInfo); 241 listenerIDs = eventNatives.MatchAgainstEventFilter(name, filteringInfo);
(...skipping 11 matching lines...) Expand all
252 253
253 if (eventArgumentMassagers[name]) 254 if (eventArgumentMassagers[name])
254 eventArgumentMassagers[name](args, dispatchArgs); 255 eventArgumentMassagers[name](args, dispatchArgs);
255 else 256 else
256 dispatchArgs(args); 257 dispatchArgs(args);
257 } 258 }
258 259
259 // Registers a callback to be called when this event is dispatched. 260 // Registers a callback to be called when this event is dispatched.
260 EventImpl.prototype.addListener = function(cb, filters) { 261 EventImpl.prototype.addListener = function(cb, filters) {
261 if (!this.eventOptions.supportsListeners) 262 if (!this.eventOptions.supportsListeners)
262 throw new Error("This event does not support listeners."); 263 throw new $Error.self("This event does not support listeners.");
not at google - send to devlin 2014/08/19 16:45:54 new Error()
263 if (this.eventOptions.maxListeners && 264 if (this.eventOptions.maxListeners &&
264 this.getListenerCount_() >= this.eventOptions.maxListeners) { 265 this.getListenerCount_() >= this.eventOptions.maxListeners) {
265 throw new Error("Too many listeners for " + this.eventName); 266 throw new $Error.self("Too many listeners for " + this.eventName);
not at google - send to devlin 2014/08/19 16:45:55 new Error()
266 } 267 }
267 if (filters) { 268 if (filters) {
268 if (!this.eventOptions.supportsFilters) 269 if (!this.eventOptions.supportsFilters)
269 throw new Error("This event does not support filters."); 270 throw new $Error.self("This event does not support filters.");
270 if (filters.url && !(filters.url instanceof Array)) 271 if (filters.url && !(filters.url instanceof Array))
271 throw new Error("filters.url should be an array."); 272 throw new $Error.self("filters.url should be an array.");
272 if (filters.serviceType && 273 if (filters.serviceType &&
273 !(typeof filters.serviceType === 'string')) { 274 !(typeof filters.serviceType === 'string')) {
274 throw new Error("filters.serviceType should be a string.") 275 throw new $Error.self("filters.serviceType should be a string.");
not at google - send to devlin 2014/08/19 16:45:55 new Error() for all of these
275 } 276 }
276 } 277 }
277 var listener = {callback: cb, filters: filters}; 278 var listener = {callback: cb, filters: filters};
278 this.attach_(listener); 279 this.attach_(listener);
279 $Array.push(this.listeners, listener); 280 $Array.push(this.listeners, listener);
280 }; 281 };
281 282
282 EventImpl.prototype.attach_ = function(listener) { 283 EventImpl.prototype.attach_ = function(listener) {
283 this.attachmentStrategy.onAddedListener(listener); 284 this.attachmentStrategy.onAddedListener(listener);
284 285
285 if (this.listeners.length == 0) { 286 if (this.listeners.length == 0) {
286 allAttachedEvents[allAttachedEvents.length] = this; 287 allAttachedEvents[allAttachedEvents.length] = this;
287 if (this.eventName) { 288 if (this.eventName) {
288 if (attachedNamedEvents[this.eventName]) { 289 if (attachedNamedEvents[this.eventName]) {
289 throw new Error("Event '" + this.eventName + 290 throw new $Error.self("Event '" + this.eventName +
290 "' is already attached."); 291 "' is already attached.");
291 } 292 }
292 attachedNamedEvents[this.eventName] = this; 293 attachedNamedEvents[this.eventName] = this;
293 } 294 }
294 } 295 }
295 }; 296 };
296 297
297 // Unregisters a callback. 298 // Unregisters a callback.
298 EventImpl.prototype.removeListener = function(cb) { 299 EventImpl.prototype.removeListener = function(cb) {
299 if (!this.eventOptions.supportsListeners) 300 if (!this.eventOptions.supportsListeners)
300 throw new Error("This event does not support listeners."); 301 throw new $Error.self("This event does not support listeners.");
not at google - send to devlin 2014/08/19 16:45:54 new Error()
301 302
302 var idx = this.findListener_(cb); 303 var idx = this.findListener_(cb);
303 if (idx == -1) 304 if (idx == -1)
304 return; 305 return;
305 306
306 var removedListener = $Array.splice(this.listeners, idx, 1)[0]; 307 var removedListener = $Array.splice(this.listeners, idx, 1)[0];
307 this.attachmentStrategy.onRemovedListener(removedListener); 308 this.attachmentStrategy.onRemovedListener(removedListener);
308 309
309 if (this.listeners.length == 0) { 310 if (this.listeners.length == 0) {
310 var i = $Array.indexOf(allAttachedEvents, this); 311 var i = $Array.indexOf(allAttachedEvents, this);
311 if (i >= 0) 312 if (i >= 0)
312 delete allAttachedEvents[i]; 313 delete allAttachedEvents[i];
313 if (this.eventName) { 314 if (this.eventName) {
314 if (!attachedNamedEvents[this.eventName]) { 315 if (!attachedNamedEvents[this.eventName]) {
315 throw new Error( 316 throw new $Error.self(
316 "Event '" + this.eventName + "' is not attached."); 317 "Event '" + this.eventName + "' is not attached.");
317 } 318 }
318 delete attachedNamedEvents[this.eventName]; 319 delete attachedNamedEvents[this.eventName];
319 } 320 }
320 } 321 }
321 }; 322 };
322 323
323 // Test if the given callback is registered for this event. 324 // Test if the given callback is registered for this event.
324 EventImpl.prototype.hasListener = function(cb) { 325 EventImpl.prototype.hasListener = function(cb) {
325 if (!this.eventOptions.supportsListeners) 326 if (!this.eventOptions.supportsListeners)
326 throw new Error("This event does not support listeners."); 327 throw new $Error.self("This event does not support listeners.");
not at google - send to devlin 2014/08/19 16:45:55 new Error()
327 return this.findListener_(cb) > -1; 328 return this.findListener_(cb) > -1;
328 }; 329 };
329 330
330 // Test if any callbacks are registered for this event. 331 // Test if any callbacks are registered for this event.
331 EventImpl.prototype.hasListeners = function() { 332 EventImpl.prototype.hasListeners = function() {
332 return this.getListenerCount_() > 0; 333 return this.getListenerCount_() > 0;
333 }; 334 };
334 335
335 // Returns the number of listeners on this event. 336 // Returns the number of listeners on this event.
336 EventImpl.prototype.getListenerCount_ = function() { 337 EventImpl.prototype.getListenerCount_ = function() {
337 if (!this.eventOptions.supportsListeners) 338 if (!this.eventOptions.supportsListeners)
338 throw new Error("This event does not support listeners."); 339 throw new $Error.self("This event does not support listeners.");
not at google - send to devlin 2014/08/19 16:45:55 new Error()
339 return this.listeners.length; 340 return this.listeners.length;
340 }; 341 };
341 342
342 // Returns the index of the given callback if registered, or -1 if not 343 // Returns the index of the given callback if registered, or -1 if not
343 // found. 344 // found.
344 EventImpl.prototype.findListener_ = function(cb) { 345 EventImpl.prototype.findListener_ = function(cb) {
345 for (var i = 0; i < this.listeners.length; i++) { 346 for (var i = 0; i < this.listeners.length; i++) {
346 if (this.listeners[i].callback == cb) { 347 if (this.listeners[i].callback == cb) {
347 return i; 348 return i;
348 } 349 }
349 } 350 }
350 351
351 return -1; 352 return -1;
352 }; 353 };
353 354
354 EventImpl.prototype.dispatch_ = function(args, listenerIDs) { 355 EventImpl.prototype.dispatch_ = function(args, listenerIDs) {
355 if (this.destroyed) { 356 if (this.destroyed) {
356 throw new Error(this.eventName + ' was already destroyed at: ' + 357 throw new $Error.self(this.eventName + ' was already destroyed at: ' +
357 this.destroyed); 358 this.destroyed);
358 } 359 }
359 if (!this.eventOptions.supportsListeners) 360 if (!this.eventOptions.supportsListeners)
360 throw new Error("This event does not support listeners."); 361 throw new $Error.self("This event does not support listeners.");
not at google - send to devlin 2014/08/19 16:45:55 new Error()
361 362
362 if (this.argSchemas && logging.DCHECK_IS_ON()) { 363 if (this.argSchemas && logging.DCHECK_IS_ON()) {
363 try { 364 try {
364 validate(args, this.argSchemas); 365 validate(args, this.argSchemas);
365 } catch (e) { 366 } catch (e) {
366 e.message += ' in ' + this.eventName; 367 e.message += ' in ' + this.eventName;
367 throw e; 368 throw e;
368 } 369 }
369 } 370 }
370 371
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
407 }; 408 };
408 409
409 EventImpl.prototype.destroy_ = function() { 410 EventImpl.prototype.destroy_ = function() {
410 this.listeners.length = 0; 411 this.listeners.length = 0;
411 this.detach_(); 412 this.detach_();
412 this.destroyed = exceptionHandler.getStackTrace(); 413 this.destroyed = exceptionHandler.getStackTrace();
413 }; 414 };
414 415
415 EventImpl.prototype.addRules = function(rules, opt_cb) { 416 EventImpl.prototype.addRules = function(rules, opt_cb) {
416 if (!this.eventOptions.supportsRules) 417 if (!this.eventOptions.supportsRules)
417 throw new Error("This event does not support rules."); 418 throw new $Error.self("This event does not support rules.");
not at google - send to devlin 2014/08/19 16:45:55 new Error()
418 419
419 // Takes a list of JSON datatype identifiers and returns a schema fragment 420 // Takes a list of JSON datatype identifiers and returns a schema fragment
420 // that verifies that a JSON object corresponds to an array of only these 421 // that verifies that a JSON object corresponds to an array of only these
421 // data types. 422 // data types.
422 function buildArrayOfChoicesSchema(typesList) { 423 function buildArrayOfChoicesSchema(typesList) {
423 return { 424 return {
424 'type': 'array', 425 'type': 'array',
425 'items': { 426 'items': {
426 'choices': typesList.map(function(el) {return {'$ref': el};}) 427 'choices': typesList.map(function(el) {return {'$ref': el};})
427 } 428 }
(...skipping 10 matching lines...) Expand all
438 function validateRules(rules, conditions, actions) { 439 function validateRules(rules, conditions, actions) {
439 var conditionsSchema = buildArrayOfChoicesSchema(conditions); 440 var conditionsSchema = buildArrayOfChoicesSchema(conditions);
440 var actionsSchema = buildArrayOfChoicesSchema(actions); 441 var actionsSchema = buildArrayOfChoicesSchema(actions);
441 $Array.forEach(rules, function(rule) { 442 $Array.forEach(rules, function(rule) {
442 validate([rule.conditions], [conditionsSchema]); 443 validate([rule.conditions], [conditionsSchema]);
443 validate([rule.actions], [actionsSchema]); 444 validate([rule.actions], [actionsSchema]);
444 }); 445 });
445 }; 446 };
446 447
447 if (!this.eventOptions.conditions || !this.eventOptions.actions) { 448 if (!this.eventOptions.conditions || !this.eventOptions.actions) {
448 throw new Error('Event ' + this.eventName + ' misses ' + 449 throw new $Error.self('Event ' + this.eventName + ' misses ' +
449 'conditions or actions in the API specification.'); 450 'conditions or actions in the API specification.');
450 } 451 }
451 452
452 validateRules(rules, 453 validateRules(rules,
453 this.eventOptions.conditions, 454 this.eventOptions.conditions,
454 this.eventOptions.actions); 455 this.eventOptions.actions);
455 456
456 ensureRuleSchemasLoaded(); 457 ensureRuleSchemasLoaded();
457 // We remove the first parameter from the validation to give the user more 458 // We remove the first parameter from the validation to give the user more
458 // meaningful error messages. 459 // meaningful error messages.
459 validate([this.webViewInstanceId, rules, opt_cb], 460 validate([this.webViewInstanceId, rules, opt_cb],
460 $Array.splice( 461 $Array.splice(
461 $Array.slice(ruleFunctionSchemas.addRules.parameters), 1)); 462 $Array.slice(ruleFunctionSchemas.addRules.parameters), 1));
462 sendRequest( 463 sendRequest(
463 "events.addRules", 464 "events.addRules",
464 [this.eventName, this.webViewInstanceId, rules, opt_cb], 465 [this.eventName, this.webViewInstanceId, rules, opt_cb],
465 ruleFunctionSchemas.addRules.parameters); 466 ruleFunctionSchemas.addRules.parameters);
466 } 467 }
467 468
468 EventImpl.prototype.removeRules = function(ruleIdentifiers, opt_cb) { 469 EventImpl.prototype.removeRules = function(ruleIdentifiers, opt_cb) {
469 if (!this.eventOptions.supportsRules) 470 if (!this.eventOptions.supportsRules)
470 throw new Error("This event does not support rules."); 471 throw new $Error.self("This event does not support rules.");
not at google - send to devlin 2014/08/19 16:45:55 new Error()
471 ensureRuleSchemasLoaded(); 472 ensureRuleSchemasLoaded();
472 // We remove the first parameter from the validation to give the user more 473 // We remove the first parameter from the validation to give the user more
473 // meaningful error messages. 474 // meaningful error messages.
474 validate([this.webViewInstanceId, ruleIdentifiers, opt_cb], 475 validate([this.webViewInstanceId, ruleIdentifiers, opt_cb],
475 $Array.splice( 476 $Array.splice(
476 $Array.slice(ruleFunctionSchemas.removeRules.parameters), 1)); 477 $Array.slice(ruleFunctionSchemas.removeRules.parameters), 1));
477 sendRequest("events.removeRules", 478 sendRequest("events.removeRules",
478 [this.eventName, 479 [this.eventName,
479 this.webViewInstanceId, 480 this.webViewInstanceId,
480 ruleIdentifiers, 481 ruleIdentifiers,
481 opt_cb], 482 opt_cb],
482 ruleFunctionSchemas.removeRules.parameters); 483 ruleFunctionSchemas.removeRules.parameters);
483 } 484 }
484 485
485 EventImpl.prototype.getRules = function(ruleIdentifiers, cb) { 486 EventImpl.prototype.getRules = function(ruleIdentifiers, cb) {
486 if (!this.eventOptions.supportsRules) 487 if (!this.eventOptions.supportsRules)
487 throw new Error("This event does not support rules."); 488 throw new $Error.self("This event does not support rules.");
not at google - send to devlin 2014/08/19 16:45:55 new Error()
488 ensureRuleSchemasLoaded(); 489 ensureRuleSchemasLoaded();
489 // We remove the first parameter from the validation to give the user more 490 // We remove the first parameter from the validation to give the user more
490 // meaningful error messages. 491 // meaningful error messages.
491 validate([this.webViewInstanceId, ruleIdentifiers, cb], 492 validate([this.webViewInstanceId, ruleIdentifiers, cb],
492 $Array.splice( 493 $Array.splice(
493 $Array.slice(ruleFunctionSchemas.getRules.parameters), 1)); 494 $Array.slice(ruleFunctionSchemas.getRules.parameters), 1));
494 495
495 sendRequest( 496 sendRequest(
496 "events.getRules", 497 "events.getRules",
497 [this.eventName, this.webViewInstanceId, ruleIdentifiers, cb], 498 [this.eventName, this.webViewInstanceId, ruleIdentifiers, cb],
(...skipping 19 matching lines...) Expand all
517 'removeRules', 518 'removeRules',
518 'getRules' 519 'getRules'
519 ] }); 520 ] });
520 521
521 // NOTE: Event is (lazily) exposed as chrome.Event from dispatcher.cc. 522 // NOTE: Event is (lazily) exposed as chrome.Event from dispatcher.cc.
522 exports.Event = Event; 523 exports.Event = Event;
523 524
524 exports.dispatchEvent = dispatchEvent; 525 exports.dispatchEvent = dispatchEvent;
525 exports.parseEventOptions = parseEventOptions; 526 exports.parseEventOptions = parseEventOptions;
526 exports.registerArgumentMassager = registerArgumentMassager; 527 exports.registerArgumentMassager = registerArgumentMassager;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698