@@ -20,6 +20,7 @@ var uuid4 = utils.uuid4;
20
20
var htmlTreeAsString = utils . htmlTreeAsString ;
21
21
var parseUrl = utils . parseUrl ;
22
22
var isString = utils . isString ;
23
+ var fill = utils . fill ;
23
24
24
25
var wrapConsoleMethod = require ( './console' ) . wrapMethod ;
25
26
@@ -30,6 +31,7 @@ function now() {
30
31
return + new Date ( ) ;
31
32
}
32
33
34
+
33
35
// First, check for JSON support
34
36
// If there is no JSON, we no-op the core features of Raven
35
37
// since JSON is required to encode the payload
@@ -52,7 +54,8 @@ function Raven() {
52
54
crossOrigin : 'anonymous' ,
53
55
collectWindowErrors : true ,
54
56
maxMessageLength : 0 ,
55
- stackTraceLimit : 50
57
+ stackTraceLimit : 50 ,
58
+ autoBreadcrumbs : true
56
59
} ;
57
60
this . _ignoreOnError = 0 ;
58
61
this . _isRavenInstalled = false ;
@@ -138,6 +141,21 @@ Raven.prototype = {
138
141
this . _globalOptions . includePaths = joinRegExp ( this . _globalOptions . includePaths ) ;
139
142
this . _globalOptions . maxBreadcrumbs = Math . max ( 0 , Math . min ( this . _globalOptions . maxBreadcrumbs || 100 , 100 ) ) ; // default and hard limit is 100
140
143
144
+ var autoBreadcrumbDefaults = {
145
+ xhr : true ,
146
+ console : true ,
147
+ dom : true ,
148
+ location : true
149
+ } ;
150
+
151
+ var autoBreadcrumbs = this . _globalOptions . autoBreadcrumbs ;
152
+ if ( { } . toString . call ( autoBreadcrumbs ) === '[object Object]' ) {
153
+ autoBreadcrumbs = objectMerge ( autoBreadcrumbDefaults , autoBreadcrumbs ) ;
154
+ } else if ( autoBreadcrumbs !== false ) {
155
+ autoBreadcrumbs = autoBreadcrumbDefaults ;
156
+ }
157
+ this . _globalOptions . autoBreadcrumbs = autoBreadcrumbs ;
158
+
141
159
this . _globalKey = uri . user ;
142
160
this . _globalSecret = uri . pass && uri . pass . substr ( 1 ) ;
143
161
this . _globalProject = uri . path . substr ( lastSlash + 1 ) ;
@@ -167,7 +185,9 @@ Raven.prototype = {
167
185
TraceKit . report . subscribe ( function ( ) {
168
186
self . _handleOnErrorStackInfo . apply ( self , arguments ) ;
169
187
} ) ;
170
- this . _wrapBuiltIns ( ) ;
188
+ this . _instrumentTryCatch ( ) ;
189
+ if ( self . _globalOptions . autoBreadcrumbs )
190
+ this . _instrumentBreadcrumbs ( ) ;
171
191
172
192
// Install all of the plugins
173
193
this . _drainPlugins ( ) ;
@@ -742,16 +762,10 @@ Raven.prototype = {
742
762
/**
743
763
* Install any queued plugins
744
764
*/
745
- _wrapBuiltIns : function ( ) {
765
+ _instrumentTryCatch : function ( ) {
746
766
var self = this ;
747
767
748
- function fill ( obj , name , replacement , noUndo ) {
749
- var orig = obj [ name ] ;
750
- obj [ name ] = replacement ( orig ) ;
751
- if ( ! noUndo ) {
752
- self . _wrappedBuiltIns . push ( [ obj , name , orig ] ) ;
753
- }
754
- }
768
+ var wrappedBuiltIns = self . _wrappedBuiltIns ;
755
769
756
770
function wrapTimeFn ( orig ) {
757
771
return function ( fn , t ) { // preserve arity
@@ -777,6 +791,8 @@ Raven.prototype = {
777
791
} ;
778
792
}
779
793
794
+ var autoBreadcrumbs = this . _globalOptions . autoBreadcrumbs ;
795
+
780
796
function wrapEventTarget ( global ) {
781
797
var proto = window [ global ] && window [ global ] . prototype ;
782
798
if ( proto && proto . hasOwnProperty && proto . hasOwnProperty ( 'addEventListener' ) ) {
@@ -790,10 +806,10 @@ Raven.prototype = {
790
806
// can sometimes get 'Permission denied to access property "handle Event'
791
807
}
792
808
793
-
794
- // TODO: more than just click
809
+ // More breadcrumb DOM capture ... done here and not in `_instrumentBreadcrumbs`
810
+ // so that we don't have more than one wrapper function
795
811
var before ;
796
- if ( global === 'EventTarget' || global === 'Node' ) {
812
+ if ( autoBreadcrumbs && autoBreadcrumbs . dom && ( global === 'EventTarget' || global === 'Node' ) ) {
797
813
if ( evtName === 'click' ) {
798
814
before = self . _breadcrumbEventHandler ( evtName ) ;
799
815
} else if ( evtName === 'keypress' ) {
@@ -802,46 +818,24 @@ Raven.prototype = {
802
818
}
803
819
return orig . call ( this , evtName , self . wrap ( fn , undefined , before ) , capture , secure ) ;
804
820
} ;
805
- } ) ;
821
+ } , wrappedBuiltIns ) ;
806
822
fill ( proto , 'removeEventListener' , function ( orig ) {
807
823
return function ( evt , fn , capture , secure ) {
808
824
fn = fn && ( fn . __raven_wrapper__ ? fn . __raven_wrapper__ : fn ) ;
809
825
return orig . call ( this , evt , fn , capture , secure ) ;
810
826
} ;
811
- } ) ;
827
+ } , wrappedBuiltIns ) ;
812
828
}
813
829
}
814
830
815
- function wrapProp ( prop , xhr ) {
816
- if ( prop in xhr && isFunction ( xhr [ prop ] ) ) {
817
- fill ( xhr , prop , function ( orig ) {
818
- return self . wrap ( orig ) ;
819
- } , true /* noUndo */ ) ; // don't track filled methods on XHR instances
820
- }
821
- }
822
-
823
- fill ( window , 'setTimeout' , wrapTimeFn ) ;
824
- fill ( window , 'setInterval' , wrapTimeFn ) ;
831
+ fill ( window , 'setTimeout' , wrapTimeFn , wrappedBuiltIns ) ;
832
+ fill ( window , 'setInterval' , wrapTimeFn , wrappedBuiltIns ) ;
825
833
if ( window . requestAnimationFrame ) {
826
834
fill ( window , 'requestAnimationFrame' , function ( orig ) {
827
835
return function ( cb ) {
828
836
return orig ( self . wrap ( cb ) ) ;
829
837
} ;
830
- } ) ;
831
- }
832
-
833
- // Capture breadcrubms from any click that is unhandled / bubbled up all the way
834
- // to the document. Do this before we instrument addEventListener.
835
- if ( this . _hasDocument ) {
836
- if ( document . addEventListener ) {
837
- document . addEventListener ( 'click' , self . _breadcrumbEventHandler ( 'click' ) , false ) ;
838
- document . addEventListener ( 'keypress' , self . _keypressEventHandler ( ) , false ) ;
839
- }
840
- else {
841
- // IE8 Compatibility
842
- document . attachEvent ( 'onclick' , self . _breadcrumbEventHandler ( 'click' ) ) ;
843
- document . attachEvent ( 'onkeypress' , self . _keypressEventHandler ( ) ) ;
844
- }
838
+ } , wrappedBuiltIns ) ;
845
839
}
846
840
847
841
// event targets borrowed from bugsnag-js:
@@ -851,7 +845,41 @@ Raven.prototype = {
851
845
wrapEventTarget ( eventTargets [ i ] ) ;
852
846
}
853
847
854
- if ( 'XMLHttpRequest' in window ) {
848
+ var $ = window . jQuery || window . $ ;
849
+ if ( $ && $ . fn && $ . fn . ready ) {
850
+ fill ( $ . fn , 'ready' , function ( orig ) {
851
+ return function ( fn ) {
852
+ return orig . call ( this , self . wrap ( fn ) ) ;
853
+ } ;
854
+ } , wrappedBuiltIns ) ;
855
+ }
856
+ } ,
857
+
858
+
859
+ /**
860
+ * Instrument browser built-ins w/ breadcrumb capturing
861
+ * - XMLHttpRequests
862
+ * - DOM interactions (click/typing)
863
+ * - window.location changes
864
+ * - console
865
+ *
866
+ * Can be disabled or individually configured via the `autoBreadcrumbs` config option
867
+ */
868
+ _instrumentBreadcrumbs : function ( ) {
869
+ var self = this ;
870
+ var autoBreadcrumbs = this . _globalOptions . autoBreadcrumbs ;
871
+
872
+ var wrappedBuiltIns = self . _wrappedBuiltIns ;
873
+
874
+ function wrapProp ( prop , xhr ) {
875
+ if ( prop in xhr && isFunction ( xhr [ prop ] ) ) {
876
+ fill ( xhr , prop , function ( orig ) {
877
+ return self . wrap ( orig ) ;
878
+ } ) ; // intentionally don't track filled methods on XHR instances
879
+ }
880
+ }
881
+
882
+ if ( autoBreadcrumbs . xhr && 'XMLHttpRequest' in window ) {
855
883
var xhrproto = XMLHttpRequest . prototype ;
856
884
fill ( xhrproto , 'open' , function ( origOpen ) {
857
885
return function ( method , url ) { // preserve arity
@@ -867,7 +895,7 @@ Raven.prototype = {
867
895
868
896
return origOpen . apply ( this , arguments ) ;
869
897
} ;
870
- } ) ;
898
+ } , wrappedBuiltIns ) ;
871
899
872
900
fill ( xhrproto , 'send' , function ( origSend ) {
873
901
return function ( data ) { // preserve arity
@@ -896,7 +924,7 @@ Raven.prototype = {
896
924
if ( 'onreadystatechange' in xhr && isFunction ( xhr . onreadystatechange ) ) {
897
925
fill ( xhr , 'onreadystatechange' , function ( orig ) {
898
926
return self . wrap ( orig , undefined , onreadystatechangeHandler ) ;
899
- } , true /* noUndo */ ) ;
927
+ } /* intentionally don't track this instrumentation */ ) ;
900
928
} else {
901
929
// if onreadystatechange wasn't actually set by the page on this xhr, we
902
930
// are free to set our own and capture the breadcrumb
@@ -905,7 +933,21 @@ Raven.prototype = {
905
933
906
934
return origSend . apply ( this , arguments ) ;
907
935
} ;
908
- } ) ;
936
+ } , wrappedBuiltIns ) ;
937
+ }
938
+
939
+ // Capture breadcrumbs from any click that is unhandled / bubbled up all the way
940
+ // to the document. Do this before we instrument addEventListener.
941
+ if ( autoBreadcrumbs . dom && this . _hasDocument ) {
942
+ if ( document . addEventListener ) {
943
+ document . addEventListener ( 'click' , self . _breadcrumbEventHandler ( 'click' ) , false ) ;
944
+ document . addEventListener ( 'keypress' , self . _keypressEventHandler ( ) , false ) ;
945
+ }
946
+ else {
947
+ // IE8 Compatibility
948
+ document . attachEvent ( 'onclick' , self . _breadcrumbEventHandler ( 'click' ) ) ;
949
+ document . attachEvent ( 'onkeypress' , self . _keypressEventHandler ( ) ) ;
950
+ }
909
951
}
910
952
911
953
// record navigation (URL) changes
@@ -915,7 +957,7 @@ Raven.prototype = {
915
957
var chrome = window . chrome ;
916
958
var isChromePackagedApp = chrome && chrome . app && chrome . app . runtime ;
917
959
var hasPushState = ! isChromePackagedApp && window . history && history . pushState ;
918
- if ( hasPushState ) {
960
+ if ( autoBreadcrumbs . location && hasPushState ) {
919
961
// TODO: remove onpopstate handler on uninstall()
920
962
var oldOnPopState = window . onpopstate ;
921
963
window . onpopstate = function ( ) {
@@ -930,7 +972,7 @@ Raven.prototype = {
930
972
fill ( history , 'pushState' , function ( origPushState ) {
931
973
// note history.pushState.length is 0; intentionally not declaring
932
974
// params to preserve 0 arity
933
- return function ( /* state, title, url */ ) {
975
+ return function ( /* state, title, url */ ) {
934
976
var url = arguments . length > 2 ? arguments [ 2 ] : undefined ;
935
977
936
978
// url argument is optional
@@ -941,32 +983,24 @@ Raven.prototype = {
941
983
942
984
return origPushState . apply ( this , arguments ) ;
943
985
} ;
944
- } ) ;
986
+ } , wrappedBuiltIns ) ;
945
987
}
946
988
947
- // console
948
- var consoleMethodCallback = function ( msg , data ) {
949
- self . captureBreadcrumb ( {
950
- message : msg ,
951
- level : data . level ,
952
- category : 'console'
953
- } ) ;
954
- } ;
989
+ if ( autoBreadcrumbs . console && 'console' in window && console . log ) {
990
+ // console
991
+ var consoleMethodCallback = function ( msg , data ) {
992
+ self . captureBreadcrumb ( {
993
+ message : msg ,
994
+ level : data . level ,
995
+ category : 'console'
996
+ } ) ;
997
+ } ;
955
998
956
- if ( 'console' in window && console . log ) {
957
999
each ( [ 'debug' , 'info' , 'warn' , 'error' , 'log' ] , function ( _ , level ) {
958
1000
wrapConsoleMethod ( console , level , consoleMethodCallback ) ;
959
1001
} ) ;
960
1002
}
961
1003
962
- var $ = window . jQuery || window . $ ;
963
- if ( $ && $ . fn && $ . fn . ready ) {
964
- fill ( $ . fn , 'ready' , function ( orig ) {
965
- return function ( fn ) {
966
- return orig . call ( this , self . wrap ( fn ) ) ;
967
- } ;
968
- } ) ;
969
- }
970
1004
} ,
971
1005
972
1006
_restoreBuiltIns : function ( ) {
0 commit comments