"use strict";const{Cc,Ci,Cu}=require("chrome");const Services=require("Services");const{XPCOMUtils}=require("resource://gre/modules/XPCOMUtils.jsm");const events=require("sdk/event/core");const protocol=require("devtools/server/protocol");const{method,custom,RetVal,Arg}=protocol;loader.lazyGetter(this,"gDevTools",()=>{return require("resource:///modules/devtools/gDevTools.jsm").gDevTools;});loader.lazyGetter(this,"DOMUtils",()=>{return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils)});loader.lazyGetter(this,"stylesheets",()=>{return require("devtools/server/actors/stylesheets");});loader.lazyGetter(this,"CssLogic",()=>{return require("devtools/styleinspector/css-logic").CssLogic;});const CSSRule=Ci.nsIDOMCSSRule;const MAX_UNUSED_RULES=10000;const l10n=exports.l10n={_URI:"chrome://global/locale/devtools/csscoverage.properties",lookup:function(msg){if(this._stringBundle==null){this._stringBundle=Services.strings.createBundle(this._URI);}
return this._stringBundle.GetStringFromName(msg);}};let CSSUsageActor=protocol.ActorClass({typeName:"cssUsage",events:{"state-change":{type:"stateChange",stateChange:Arg(0,"json")}},initialize:function(conn,tabActor){protocol.Actor.prototype.initialize.call(this,conn);this._tabActor=tabActor;this._running=false;this._onTabLoad=this._onTabLoad.bind(this);this._onChange=this._onChange.bind(this);this._notifyOn=Ci.nsIWebProgress.NOTIFY_STATUS|Ci.nsIWebProgress.NOTIFY_STATE_ALL},destroy:function(){this._tabActor=undefined;delete this._onTabLoad;delete this._onChange;protocol.Actor.prototype.destroy.call(this);},start:method(function(noreload){if(this._running){throw new Error(l10n.lookup("csscoverageRunningError"));}
this._isOneShot=false;this._visitedPages=new Set();this._knownRules=new Map();this._running=true;this._tooManyUnused=false;this._progressListener={QueryInterface:XPCOMUtils.generateQI([Ci.nsIWebProgressListener,Ci.nsISupportsWeakReference]),onStateChange:(progress,request,flags,status)=>{let isStop=flags&Ci.nsIWebProgressListener.STATE_STOP;let isWindow=flags&Ci.nsIWebProgressListener.STATE_IS_WINDOW;if(isStop&&isWindow){this._onTabLoad(progress.DOMWindow.document);}},onLocationChange:()=>{},onProgressChange:()=>{},onSecurityChange:()=>{},onStatusChange:()=>{},destroy:()=>{}};this._progress=this._tabActor.docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);this._progress.addProgressListener(this._progressListener,this._notifyOn);if(noreload){
this._onTabLoad(this._tabActor.window.document);}
else{this._tabActor.window.location.reload();}
events.emit(this,"state-change",{isRunning:true});},{request:{url:Arg(0,"boolean")}}),stop:method(function(){if(!this._running){throw new Error(l10n.lookup("csscoverageNotRunningError"));}
this._progress.removeProgressListener(this._progressListener,this._notifyOn);this._progress=undefined;this._running=false;events.emit(this,"state-change",{isRunning:false});}),toggle:method(function(){return this._running?this.stop():this.start();}),oneshot:method(function(){if(this._running){throw new Error(l10n.lookup("csscoverageRunningError"));}
this._isOneShot=true;this._visitedPages=new Set();this._knownRules=new Map();this._populateKnownRules(this._tabActor.window.document);this._updateUsage(this._tabActor.window.document,false);}),_onTabLoad:function(document){this._populateKnownRules(document);this._updateUsage(document,true);this._observeMutations(document);},_observeMutations:function(document){let MutationObserver=document.defaultView.MutationObserver;let observer=new MutationObserver(mutations=>{
 this._onChange(document);});observer.observe(document,{attributes:true,childList:true,characterData:false,subtree:true});},_onChange:function(document){if(!this._visitedPages.has(getURL(document))){return;}
this._updateUsage(document,false);},_populateKnownRules:function(document){let url=getURL(document);this._visitedPages.add(url);
 for(let rule of getAllSelectorRules(document)){let ruleId=ruleToId(rule);let ruleData=this._knownRules.get(ruleId);if(ruleData==null){ruleData={selectorText:rule.selectorText,cssText:rule.cssText,test:getTestSelector(rule.selectorText),isUsed:false,presentOn:new Set(),preLoadOn:new Set(),isError:false};this._knownRules.set(ruleId,ruleData);}
ruleData.presentOn.add(url);}},_updateUsage:function(document,isLoad){let qsaCount=0; let url=getURL(document);for(let[,ruleData]of this._knownRules){ if(ruleData.isError){continue;}
 
if(!isLoad&&ruleData.isUsed){continue;} 
if(!ruleData.presentOn.has(url)){continue;}
qsaCount++;if(qsaCount>MAX_UNUSED_RULES){console.error("Too many unused rules on "+url+" ");this._tooManyUnused=true;continue;}
try{let match=document.querySelector(ruleData.test);if(match!=null){ruleData.isUsed=true;if(isLoad){ruleData.preLoadOn.add(url);}}}
catch(ex){ruleData.isError=true;}}},createEditorReport:method(function(url){if(this._knownRules==null){return{reports:[]};}
let reports=[];for(let[ruleId,ruleData]of this._knownRules){let{url:ruleUrl,line,column}=deconstructRuleId(ruleId);if(ruleUrl!==url||ruleData.isUsed){continue;}
let ruleReport={selectorText:ruleData.selectorText,start:{line:line,column:column}};if(ruleData.end){ruleReport.end=ruleData.end;}
reports.push(ruleReport);}
return{reports:reports};},{request:{url:Arg(0,"string")},response:{reports:RetVal("array:json")}}),createPageReport:method(function(){if(this._running){throw new Error(l10n.lookup("csscoverageRunningError"));}
if(this._visitedPages==null){throw new Error(l10n.lookup("csscoverageNotRunError"));}
if(this._isOneShot){throw new Error(l10n.lookup("csscoverageOneShotReportError"));} 
const ruleToRuleReport=function(rule,ruleData){return{url:rule.url,shortUrl:rule.url.split("/").slice(-1)[0],start:{line:rule.line,column:rule.column},selectorText:ruleData.selectorText,formattedCssText:CssLogic.prettifyCSS(ruleData.cssText)};} 
let summary={used:0,unused:0,preload:0}; let unusedMap=new Map();for(let[ruleId,ruleData]of this._knownRules){let rule=deconstructRuleId(ruleId);let rules=unusedMap.get(rule.url)
if(rules==null){rules=[];unusedMap.set(rule.url,rules);}
if(!ruleData.isUsed){let ruleReport=ruleToRuleReport(rule,ruleData);rules.push(ruleReport);}
else{summary.unused++;}}
let unused=[];for(let[url,rules]of unusedMap){unused.push({url:url,shortUrl:url.split("/").slice(-1),rules:rules});} 
let preload=[];for(let url of this._visitedPages){let page={url:url,shortUrl:url.split("/").slice(-1),rules:[]};for(let[ruleId,ruleData]of this._knownRules){if(ruleData.preLoadOn.has(url)){let rule=deconstructRuleId(ruleId);let ruleReport=ruleToRuleReport(rule,ruleData);page.rules.push(ruleReport);summary.preload++;}
else{summary.used++;}}
if(page.rules.length>0){preload.push(page);}}
return{summary:summary,preload:preload,unused:unused};},{response:RetVal("json")}),_testOnly_visitedPages:method(function(){return[...this._visitedPages];},{response:{value:RetVal("array:string")}}),});exports.CSSUsageActor=CSSUsageActor;function*getAllSelectorRules(document){for(let rule of getAllRules(document)){if(rule.type===CSSRule.STYLE_RULE&&rule.selectorText!==""){yield rule;}}}
function*getAllRules(document){ let sheets=getAllSheets(document);for(let i=0;i<sheets.length;i++){for(let j=0;j<sheets[i].cssRules.length;j++){yield sheets[i].cssRules[j];}}}
function getAllSheets(document){ let sheets=Array.slice(document.styleSheets); for(let i=0;i<sheets.length;i++){let subSheets=getImportedSheets(sheets[i]);sheets=sheets.concat(...subSheets);}
return sheets;}
function getImportedSheets(stylesheet){let sheets=[];for(let i=0;i<stylesheet.cssRules.length;i++){let rule=stylesheet.cssRules[i];if(rule.type===CSSRule.IMPORT_RULE&&rule.styleSheet!=null){sheets.push(rule.styleSheet);let subSheets=getImportedSheets(rule.styleSheet);sheets=sheets.concat(...subSheets);}}
return sheets;}
function ruleToId(rule){let loc=stylesheets.getRuleLocation(rule);return sheetToUrl(rule.parentStyleSheet)+"|"+loc.line+"|"+loc.column;}
const deconstructRuleId=exports.deconstructRuleId=function(ruleId){let split=ruleId.split("|");if(split.length>3){let replace=split.slice(0,split.length-3+1).join("|");split.splice(0,split.length-3+1,replace);}
let[url,line,column]=split;return{url:url,line:parseInt(line,10),column:parseInt(column,10)};};const getURL=exports.getURL=function(document){let url=new document.defaultView.URL(document.documentURI);return''+url.origin+url.pathname;};const SEL_EXTERNAL=["active","active-drop","current","dir","focus","future","hover","invalid-drop","lang","past","placeholder-shown","target","valid-drop","visited"];const SEL_FORM=["checked","default","disabled","enabled","fullscreen","in-range","indeterminate","invalid","optional","out-of-range","required","valid"];const SEL_ELEMENT=["after","before","first-letter","first-line","selection"];const SEL_STRUCTURAL=["empty","first-child","first-of-type","last-child","last-of-type","nth-column","nth-last-column","nth-child","nth-last-child","nth-last-of-type","nth-of-type","only-child","only-of-type","root"];const SEL_SEMI=["any-link","link","read-only","read-write","scope"];const SEL_COMBINING=["not","has","matches"];const SEL_MEDIA=["blank","first","left","right"];function getTestSelector(selector){let replacement=selector;let replaceSelector=pseudo=>{replacement=replacement.replace(" :"+pseudo," *").replace("(:"+pseudo,"(*").replace(":"+pseudo,"");};SEL_EXTERNAL.forEach(replaceSelector);SEL_FORM.forEach(replaceSelector);SEL_ELEMENT.forEach(replaceSelector); SEL_ELEMENT.forEach(pseudo=>{replacement=replacement.replace("::"+pseudo,"");});return replacement;}
exports.SEL_ALL=[SEL_EXTERNAL,SEL_FORM,SEL_ELEMENT,SEL_STRUCTURAL,SEL_SEMI,SEL_COMBINING,SEL_MEDIA].reduce(function(prev,curr){return prev.concat(curr);},[]);const sheetToUrl=exports.sheetToUrl=function(stylesheet){ if(stylesheet.href){return stylesheet.href;} 
if(stylesheet.ownerNode){let document=stylesheet.ownerNode.ownerDocument;let sheets=[...document.querySelectorAll("style")];let index=sheets.indexOf(stylesheet.ownerNode);return getURL(document)+' → <style> index '+index;}
throw new Error("Unknown sheet source");}
let isRunning=false;let notification;let target;let chromeWindow;const CSSUsageFront=protocol.FrontClass(CSSUsageActor,{initialize:function(client,form){protocol.Front.prototype.initialize.call(this,client,form);this.actorID=form.cssUsageActor;this.manage(this);},_onStateChange:protocol.preEvent("state-change",function(ev){isRunning=ev.isRunning;ev.target=target;if(isRunning){let gnb=chromeWindow.document.getElementById("global-notificationbox");notification=gnb.getNotificationWithValue("csscoverage-running");if(notification==null){let notifyStop=reason=>{if(reason=="removed"){this.stop();}};let msg=l10n.lookup("csscoverageRunningReply");notification=gnb.appendNotification(msg,"csscoverage-running","", gnb.PRIORITY_INFO_HIGH,null, notifyStop);}}
else{if(notification){notification.remove();notification=undefined;}
gDevTools.showToolbox(target,"styleeditor");target=undefined;}}),start:custom(function(newChromeWindow,newTarget,noreload=false){target=newTarget;chromeWindow=newChromeWindow;return this._start(noreload);},{impl:"_start"}),toggle:custom(function(newChromeWindow,newTarget){target=newTarget;chromeWindow=newChromeWindow;return this._toggle();},{impl:"_toggle"}),isRunning:function(){return isRunning;}});exports.CSSUsageFront=CSSUsageFront;exports.register=function(handle){handle.addGlobalActor(CSSUsageActor,"cssUsageActor");handle.addTabActor(CSSUsageActor,"cssUsageActor");};exports.unregister=function(handle){handle.removeGlobalActor(CSSUsageActor,"cssUsageActor");handle.removeTabActor(CSSUsageActor,"cssUsageActor");};const knownFronts=new WeakMap();const getUsage=exports.getUsage=function(target){return target.makeRemote().then(()=>{let front=knownFronts.get(target.client)
if(front==null&&target.form.cssUsageActor!=null){front=new CSSUsageFront(target.client,target.form);knownFronts.set(target.client,front);}
return front;});};