2015-07-24 11:42:46 +00:00
var frameIdMessageable ;
2015-08-01 12:06:47 +00:00
runTryCatch ( function ( ) {
2015-07-24 11:42:46 +00:00
chrome . tabs . sendMessage ( 0 , { } , { frameId : 0 } , function ( ) {
var clearError = chrome . runtime . lastError ;
frameIdMessageable = true ;
} ) ;
2015-08-01 12:06:47 +00:00
} ) ;
2015-07-24 11:42:46 +00:00
2015-01-30 16:36:46 +00:00
// This happens right away, sometimes so fast that the content script isn't even ready. That's
// why the content script also asks for this stuff.
2015-02-23 22:48:27 +00:00
chrome . webNavigation . onCommitted . addListener ( webNavigationListener . bind ( this , "styleApply" ) ) ;
chrome . webNavigation . onHistoryStateUpdated . addListener ( webNavigationListener . bind ( this , "styleReplaceAll" ) ) ;
2015-05-14 21:24:10 +00:00
chrome . webNavigation . onBeforeNavigate . addListener ( webNavigationListener . bind ( this , null ) ) ;
2015-02-23 22:48:27 +00:00
function webNavigationListener ( method , data ) {
2015-02-17 18:52:32 +00:00
// Until Chrome 41, we can't target a frame with a message
// (https://developer.chrome.com/extensions/tabs#method-sendMessage)
// so a style affecting a page with an iframe will affect the main page as well.
2015-07-24 11:42:46 +00:00
// Skip doing this for frames in pre-41 to prevent page flicker.
if ( data . frameId != 0 && ! frameIdMessageable ) {
2015-02-17 18:52:32 +00:00
return ;
}
2015-01-30 16:36:46 +00:00
getStyles ( { matchUrl : data . url , enabled : true , asHash : true } , function ( styleHash ) {
2015-05-14 21:24:10 +00:00
if ( method ) {
2015-07-24 11:42:46 +00:00
chrome . tabs . sendMessage ( data . tabId , { method : method , styles : styleHash } ,
frameIdMessageable ? { frameId : data . frameId } : undefined ) ;
}
if ( data . frameId == 0 ) {
updateIcon ( { id : data . tabId , url : data . url } , styleHash ) ;
2015-01-30 16:36:46 +00:00
}
} ) ;
2015-02-23 22:48:27 +00:00
}
2015-01-30 16:36:46 +00:00
2015-01-29 18:41:45 +00:00
chrome . extension . onMessage . addListener ( function ( request , sender , sendResponse ) {
switch ( request . method ) {
case "getStyles" :
2015-05-14 21:24:10 +00:00
var styles = getStyles ( request , sendResponse ) ;
2015-05-21 09:34:44 +00:00
// check if this is a main content frame style enumeration
if ( request . matchUrl && ! request . id && sender && sender . tab && sender . frameId == 0 ) {
2015-05-14 21:24:10 +00:00
updateIcon ( sender . tab , styles ) ;
}
2015-01-29 18:41:45 +00:00
return true ;
case "saveStyle" :
saveStyle ( request , sendResponse ) ;
return true ;
case "styleChanged" :
cachedStyles = null ;
break ;
case "healthCheck" :
getDatabase ( function ( ) { sendResponse ( true ) ; } , function ( ) { sendResponse ( false ) ; } ) ;
break ;
2015-03-13 22:45:38 +00:00
case "openURL" :
openURL ( request ) ;
break ;
2015-03-25 17:59:10 +00:00
case "styleDisableAll" :
chrome . contextMenus . update ( "disableAll" , { checked : request . disableAll } ) ;
break ;
case "prefChanged" :
if ( request . prefName == "show-badge" ) {
chrome . contextMenus . update ( "show-badge" , { checked : request . value } ) ;
}
break ;
2015-01-29 18:41:45 +00:00
}
} ) ;
2015-03-24 14:07:59 +00:00
chrome . commands . onCommand . addListener ( function ( command ) {
switch ( command ) {
case "openManage" :
openURL ( { url : chrome . extension . getURL ( "manage.html" ) } ) ;
break ;
case "styleDisableAll" :
2015-03-25 17:59:10 +00:00
disableAllStylesToggle ( ) ;
chrome . contextMenus . update ( "disableAll" , { checked : prefs . getPref ( "disableAll" ) } ) ;
2015-03-24 14:07:59 +00:00
break ;
}
} ) ;
2015-04-22 17:13:21 +00:00
// contextMenus API is present in ancient Chrome but it throws an exception
// upon encountering the unsupported parameter value "browser_action", so we have to catch it.
2015-08-01 12:06:47 +00:00
runTryCatch ( function ( ) {
chrome . contextMenus . create ( {
id : "show-badge" , title : chrome . i18n . getMessage ( "menuShowBadge" ) ,
type : "checkbox" , contexts : [ "browser_action" ] , checked : prefs . getPref ( "show-badge" )
} , function ( ) { var clearError = chrome . runtime . lastError } ) ;
chrome . contextMenus . create ( {
id : "disableAll" , title : chrome . i18n . getMessage ( "disableAllStyles" ) ,
type : "checkbox" , contexts : [ "browser_action" ] , checked : prefs . getPref ( "disableAll" )
} , function ( ) { var clearError = chrome . runtime . lastError } ) ;
} ) ;
2015-03-25 17:59:10 +00:00
chrome . contextMenus . onClicked . addListener ( function ( info , tab ) {
if ( info . menuItemId == "disableAll" ) {
disableAllStylesToggle ( info . checked ) ;
} else {
prefs . setPref ( info . menuItemId , info . checked ) ;
}
} ) ;
function disableAllStylesToggle ( newState ) {
if ( newState === undefined || newState === null ) {
newState = ! prefs . getPref ( "disableAll" ) ;
}
prefs . setPref ( "disableAll" , newState ) ;
notifyAllTabs ( { method : "styleDisableAll" , disableAll : newState } ) ;
}
2015-01-29 18:41:45 +00:00
function getStyles ( options , callback ) {
var enabled = fixBoolean ( options . enabled ) ;
var url = "url" in options ? options . url : null ;
var id = "id" in options ? options . id : null ;
var matchUrl = "matchUrl" in options ? options . matchUrl : null ;
2015-01-30 16:36:46 +00:00
// Return as a hash from style to applicable sections? Can only be used with matchUrl.
var asHash = "asHash" in options ? options . asHash : false ;
2015-01-29 18:41:45 +00:00
var callCallback = function ( ) {
2015-03-17 18:03:20 +00:00
var styles = asHash ? { disableAll : prefs . getPref ( "disableAll" , false ) } : [ ] ;
2015-01-30 16:36:46 +00:00
cachedStyles . forEach ( function ( style ) {
2015-01-29 18:41:45 +00:00
if ( enabled != null && fixBoolean ( style . enabled ) != enabled ) {
2015-01-30 16:36:46 +00:00
return ;
2015-01-29 18:41:45 +00:00
}
if ( url != null && style . url != url ) {
2015-01-30 16:36:46 +00:00
return ;
2015-01-29 18:41:45 +00:00
}
if ( id != null && style . id != id ) {
2015-01-30 16:36:46 +00:00
return ;
2015-01-29 18:41:45 +00:00
}
2015-01-30 16:36:46 +00:00
if ( matchUrl != null ) {
var applicableSections = getApplicableSections ( style , matchUrl ) ;
if ( applicableSections . length > 0 ) {
if ( asHash ) {
styles [ style . id ] = applicableSections ;
} else {
styles . push ( style )
}
}
} else {
styles . push ( style ) ;
2015-01-29 18:41:45 +00:00
}
2015-01-30 16:36:46 +00:00
} ) ;
callback ( styles ) ;
2015-05-14 21:24:10 +00:00
return styles ;
2015-01-29 18:41:45 +00:00
}
if ( cachedStyles ) {
2015-05-14 21:24:10 +00:00
return callCallback ( ) ;
2015-01-29 18:41:45 +00:00
}
getDatabase ( function ( db ) {
db . readTransaction ( function ( t ) {
var where = "" ;
var params = [ ] ;
t . executeSql ( 'SELECT DISTINCT s.*, se.id section_id, se.code, sm.name metaName, sm.value metaValue FROM styles s LEFT JOIN sections se ON se.style_id = s.id LEFT JOIN section_meta sm ON sm.section_id = se.id WHERE 1' + where + ' ORDER BY s.id, se.id, sm.id' , params , function ( t , r ) {
cachedStyles = [ ] ;
var currentStyle = null ;
var currentSection = null ;
for ( var i = 0 ; i < r . rows . length ; i ++ ) {
var values = r . rows . item ( i ) ;
var metaName = null ;
switch ( values . metaName ) {
case null :
break ;
case "url" :
metaName = "urls" ;
break ;
case "url-prefix" :
metaName = "urlPrefixes" ;
break ;
case "domain" :
var metaName = "domains" ;
break ;
case "regexps" :
var metaName = "regexps" ;
break ;
default :
var metaName = values . metaName + "s" ;
}
var metaValue = values . metaValue ;
if ( currentStyle == null || currentStyle . id != values . id ) {
currentStyle = { id : values . id , url : values . url , updateUrl : values . updateUrl , md5Url : values . md5Url , name : values . name , enabled : values . enabled , originalMd5 : values . originalMd5 , sections : [ ] } ;
cachedStyles . push ( currentStyle ) ;
}
2015-01-30 19:18:12 +00:00
if ( values . section _id != null ) {
if ( currentSection == null || currentSection . id != values . section _id ) {
currentSection = { id : values . section _id , code : values . code } ;
currentStyle . sections . push ( currentSection ) ;
}
if ( metaName && metaValue ) {
if ( currentSection [ metaName ] ) {
currentSection [ metaName ] . push ( metaValue ) ;
} else {
currentSection [ metaName ] = [ metaValue ] ;
}
2015-01-29 18:41:45 +00:00
}
}
}
callCallback ( ) ;
} , reportError ) ;
} , reportError ) ;
} , reportError ) ;
}
function fixBoolean ( b ) {
if ( typeof b != "undefined" ) {
return b != "false" ;
}
return null ;
}
2015-04-22 11:29:52 +00:00
var namespacePattern = /^\s*(@namespace[^;]+;\s*)+$/ ;
2015-01-29 18:41:45 +00:00
function getApplicableSections ( style , url ) {
var sections = style . sections . filter ( function ( section ) {
return sectionAppliesToUrl ( section , url ) ;
} ) ;
2015-01-30 19:09:56 +00:00
// ignore if it's just namespaces
2015-01-29 18:41:45 +00:00
if ( sections . length == 1 && namespacePattern . test ( sections [ 0 ] . code ) ) {
return [ ] ;
}
return sections ;
}
function sectionAppliesToUrl ( section , url ) {
2015-02-09 04:25:35 +00:00
// only http, https, file, and chrome-extension allowed
if ( url . indexOf ( "http" ) != 0 && url . indexOf ( "file" ) != 0 && url . indexOf ( "chrome-extension" ) != 0 ) {
2015-01-29 18:41:45 +00:00
return false ;
}
if ( ! section . urls && ! section . domains && ! section . urlPrefixes && ! section . regexps ) {
2015-02-17 19:57:17 +00:00
//console.log(section.id + " is global");
2015-01-29 18:41:45 +00:00
return true ;
}
if ( section . urls && section . urls . indexOf ( url ) != - 1 ) {
2015-02-17 19:57:17 +00:00
//console.log(section.id + " applies to " + url + " due to URL rules");
2015-01-29 18:41:45 +00:00
return true ;
}
if ( section . urlPrefixes && section . urlPrefixes . some ( function ( prefix ) {
return url . indexOf ( prefix ) == 0 ;
} ) ) {
2015-02-17 19:57:17 +00:00
//console.log(section.id + " applies to " + url + " due to URL prefix rules");
2015-01-29 18:41:45 +00:00
return true ;
}
if ( section . domains && getDomains ( url ) . some ( function ( domain ) {
return section . domains . indexOf ( domain ) != - 1 ;
} ) ) {
2015-02-17 19:57:17 +00:00
//console.log(section.id + " applies due to " + url + " due to domain rules");
2015-01-29 18:41:45 +00:00
return true ;
}
if ( section . regexps && section . regexps . some ( function ( regexp ) {
// we want to match the full url, so add ^ and $ if not already present
if ( regexp [ 0 ] != "^" ) {
regexp = "^" + regexp ;
}
if ( regexp [ regexp . length - 1 ] != "$" ) {
regexp += "$" ;
}
2015-08-01 12:06:47 +00:00
var re = runTryCatch ( function ( ) { return new RegExp ( regexp ) } ) ;
if ( re ) {
return ( re ) . test ( url ) ;
} else {
2015-01-29 18:41:45 +00:00
console . log ( section . id + "'s regexp '" + regexp + "' is not valid" ) ;
}
} ) ) {
2015-02-17 19:57:17 +00:00
//console.log(section.id + " applies to " + url + " due to regexp rules");
2015-01-29 18:41:45 +00:00
return true ;
}
2015-02-17 19:57:17 +00:00
//console.log(section.id + " does not apply due to " + url);
2015-01-29 18:41:45 +00:00
return false ;
}
var cachedStyles = null ;
function saveStyle ( o , callback ) {
getDatabase ( function ( db ) {
db . transaction ( function ( t ) {
if ( o . id ) {
// update whatever's been passed
if ( "name" in o ) {
t . executeSql ( 'UPDATE styles SET name = ? WHERE id = ?;' , [ o . name , o . id ] ) ;
}
if ( "enabled" in o ) {
t . executeSql ( 'UPDATE styles SET enabled = ? WHERE id = ?;' , [ o . enabled , o . id ] ) ;
}
if ( "url" in o ) {
t . executeSql ( 'UPDATE styles SET url = ? WHERE id = ?;' , [ o . url , o . id ] ) ;
}
if ( "updateUrl" in o ) {
t . executeSql ( 'UPDATE styles SET updateUrl = ? WHERE id = ?;' , [ o . updateUrl , o . id ] ) ;
}
if ( "md5Url" in o ) {
t . executeSql ( 'UPDATE styles SET md5Url = ? WHERE id = ?;' , [ o . md5Url , o . id ] ) ;
}
if ( "originalMd5" in o ) {
t . executeSql ( 'UPDATE styles SET originalMd5 = ? WHERE id = ?;' , [ o . originalMd5 , o . id ] ) ;
}
} else {
// create a new record
// set optional things to null if they're undefined
[ "updateUrl" , "md5Url" , "url" , "originalMd5" ] . filter ( function ( att ) {
return ! ( att in o ) ;
} ) . forEach ( function ( att ) {
o [ att ] = null ;
} ) ;
t . executeSql ( 'INSERT INTO styles (name, enabled, url, updateUrl, md5Url, originalMd5) VALUES (?, ?, ?, ?, ?, ?);' , [ o . name , true , o . url , o . updateUrl , o . md5Url , o . originalMd5 ] ) ;
}
if ( "sections" in o ) {
if ( o . id ) {
// clear existing records
t . executeSql ( 'DELETE FROM section_meta WHERE section_id IN (SELECT id FROM sections WHERE style_id = ?);' , [ o . id ] ) ;
t . executeSql ( 'DELETE FROM sections WHERE style_id = ?;' , [ o . id ] ) ;
}
o . sections . forEach ( function ( section ) {
if ( o . id ) {
t . executeSql ( 'INSERT INTO sections (style_id, code) VALUES (?, ?);' , [ o . id , section . code ] ) ;
} else {
t . executeSql ( 'INSERT INTO sections (style_id, code) SELECT id, ? FROM styles ORDER BY id DESC LIMIT 1;' , [ section . code ] ) ;
}
if ( section . urls ) {
section . urls . forEach ( function ( u ) {
t . executeSql ( "INSERT INTO section_meta (section_id, name, value) SELECT id, 'url', ? FROM sections ORDER BY id DESC LIMIT 1;" , [ u ] ) ;
} ) ;
}
if ( section . urlPrefixes ) {
section . urlPrefixes . forEach ( function ( u ) {
t . executeSql ( "INSERT INTO section_meta (section_id, name, value) SELECT id, 'url-prefix', ? FROM sections ORDER BY id DESC LIMIT 1;" , [ u ] ) ;
} ) ;
}
if ( section . domains ) {
section . domains . forEach ( function ( u ) {
t . executeSql ( "INSERT INTO section_meta (section_id, name, value) SELECT id, 'domain', ? FROM sections ORDER BY id DESC LIMIT 1;" , [ u ] ) ;
} ) ;
}
if ( section . regexps ) {
section . regexps . forEach ( function ( u ) {
t . executeSql ( "INSERT INTO section_meta (section_id, name, value) SELECT id, 'regexp', ? FROM sections ORDER BY id DESC LIMIT 1;" , [ u ] ) ;
} ) ;
}
} ) ;
}
} , reportError , function ( ) { saveFromJSONComplete ( o . id , callback ) } ) ;
} , reportError ) ;
}
function saveFromJSONComplete ( id , callback ) {
cachedStyles = null ;
if ( id ) {
getStyles ( { method : "getStyles" , id : id } , function ( styles ) {
saveFromJSONStyleReloaded ( "styleUpdated" , styles [ 0 ] , callback ) ;
} ) ;
return ;
}
// we need to load the id for new ones
getDatabase ( function ( db ) {
db . readTransaction ( function ( t ) {
t . executeSql ( 'SELECT id FROM styles ORDER BY id DESC LIMIT 1' , [ ] , function ( t , r ) {
var id = r . rows . item ( 0 ) . id ;
getStyles ( { method : "getStyles" , id : id } , function ( styles ) {
saveFromJSONStyleReloaded ( "styleAdded" , styles [ 0 ] , callback ) ;
} ) ;
} , reportError )
} , reportError )
} ) ;
}
function saveFromJSONStyleReloaded ( updateType , style , callback ) {
2015-02-09 04:02:08 +00:00
notifyAllTabs ( { method : updateType , style : style } ) ;
2015-01-29 18:41:45 +00:00
if ( callback ) {
callback ( style ) ;
}
}
// Get the DB so that any first run actions will be performed immediately when the background page loads.
getDatabase ( function ( ) { } , reportError ) ;
2015-01-30 18:35:37 +00:00
// When an edit page gets attached or detached, remember its state so we can do the same to the next one to open.
var editFullUrl = chrome . extension . getURL ( "edit.html" ) ;
chrome . tabs . onAttached . addListener ( function ( tabId , data ) {
chrome . tabs . get ( tabId , function ( tabData ) {
if ( tabData . url . indexOf ( editFullUrl ) == 0 ) {
chrome . windows . get ( tabData . windowId , { populate : true } , function ( win ) {
// If there's only one tab in this window, it's been dragged to new window
2015-03-03 23:04:27 +00:00
prefs . setPref ( 'openEditInWindow' , win . tabs . length == 1 ) ;
2015-01-30 18:35:37 +00:00
} ) ;
}
} ) ;
} ) ;
2015-03-13 22:45:38 +00:00
function openURL ( options ) {
chrome . tabs . query ( { currentWindow : true , url : options . url } , function ( tabs ) {
// switch to an existing tab with the requested url
if ( tabs . length ) {
chrome . tabs . highlight ( { windowId : tabs [ 0 ] . windowId , tabs : tabs [ 0 ] . index } , function ( window ) { } ) ;
} else {
delete options . method ;
2015-05-21 09:34:44 +00:00
getActiveTab ( function ( tab ) {
2015-03-13 22:45:38 +00:00
// re-use an active new tab page
2015-05-21 09:34:44 +00:00
chrome . tabs [ tab . url == "chrome://newtab/" ? "update" : "create" ] ( options ) ;
2015-03-13 22:45:38 +00:00
} ) ;
}
} ) ;
}
2015-04-11 10:28:25 +00:00
2015-08-01 12:06:47 +00:00
// js engine can't optimize the entire function if it contains try-catch
// so we should keep it isolated from normal code in a minimal wrapper
function runTryCatch ( func ) {
try { return func ( ) }
catch ( e ) { }
}
2015-05-05 15:21:45 +00:00
var codeMirrorThemes ;
getCodeMirrorThemes ( function ( themes ) {
codeMirrorThemes = themes ;
2015-04-11 10:28:25 +00:00
} ) ;