diff --git a/background.js b/background.js index 889c715f..f20248bf 100644 --- a/background.js +++ b/background.js @@ -1,3 +1,11 @@ +var frameIdMessageable; +runTryCatch(function() { + chrome.tabs.sendMessage(0, {}, {frameId: 0}, function() { + var clearError = chrome.runtime.lastError; + frameIdMessageable = true; + }); +}); + // 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. chrome.webNavigation.onCommitted.addListener(webNavigationListener.bind(this, "styleApply")); @@ -7,15 +15,18 @@ function webNavigationListener(method, data) { // 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. - // Skip doing this for frames for now, which can result in flicker. - if (data.frameId != 0) { + // Skip doing this for frames in pre-41 to prevent page flicker. + if (data.frameId != 0 && !frameIdMessageable) { return; } getStyles({matchUrl: data.url, enabled: true, asHash: true}, function(styleHash) { if (method) { - chrome.tabs.sendMessage(data.tabId, {method: method, styles: styleHash}); + 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); } - updateIcon({id: data.tabId, url: data.url}, styleHash); }); } @@ -65,15 +76,17 @@ chrome.commands.onCommand.addListener(function(command) { // 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. +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 }); +}); -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; }); chrome.contextMenus.onClicked.addListener(function(info, tab) { if (info.menuItemId == "disableAll") { disableAllStylesToggle(info.checked); @@ -239,13 +252,12 @@ function sectionAppliesToUrl(section, url) { if (regexp[regexp.length - 1] != "$") { regexp += "$"; } - try { - var re = new RegExp(regexp); - } catch (ex) { + var re = runTryCatch(function() { return new RegExp(regexp) }); + if (re) { + return (re).test(url); + } else { console.log(section.id + "'s regexp '" + regexp + "' is not valid"); - return false; } - return (re).test(url); })) { //console.log(section.id + " applies to " + url + " due to regexp rules"); return true; @@ -391,6 +403,13 @@ function openURL(options) { }); } +// 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) {} +} + var codeMirrorThemes; getCodeMirrorThemes(function(themes) { codeMirrorThemes = themes; diff --git a/edit.js b/edit.js index 8e4b176e..d1cf8b61 100644 --- a/edit.js +++ b/edit.js @@ -58,10 +58,34 @@ var jumpToLineTemplate = t('editGotoLine') + ': 0) { + // give csslint some time to find the issues, e.g. 500 (1/10 of our default 5s) + // by settings its internal delay to 1ms and restoring it back later + var lintOpt = editors[0].state.lint.options; + setTimeout((function(opt, delay) { + opt.delay = delay; + update(this); + }).bind(cm, lintOpt, lintOpt.delay), delay); + lintOpt.delay = 1; + return; + } var state = cm.state.lint; clearTimeout(state.reportTimeout); - state.reportTimeout = setTimeout(update.bind(cm), (state.options.delay || 500) + 500); + state.reportTimeout = setTimeout(update.bind(cm), (state.options.delay || 500) + 4500); function update() { // this == cm - var html = this.state.lint.marked.length == 0 ? "" : "" + - this.state.lint.marked.map(function(mark) { - var info = mark.__annotation; - return "" + - "" + - info.severity + "" + - "" + (info.from.line+1) + "" + - ":" + - "" + (info.from.ch+1) + "" + - "" + info.message.replace(/ at line \d.+$/, "") + ""; - }).join("") + ""; - if (this.state.lint.html != html) { - this.state.lint.html = html; + var scope = this ? [this] : editors; + var changed = false; + scope.forEach(function(cm) { + var html = cm.state.lint.marked.length == 0 ? "" : "" + + cm.state.lint.marked.map(function(mark) { + var info = mark.__annotation; + return "" + + "" + + info.severity + "" + + "" + (info.from.line+1) + "" + + ":" + + "" + (info.from.ch+1) + "" + + "" + info.message.replace(/ at line \d.+$/, "") + ""; + }).join("") + ""; + if (cm.state.lint.html != html) { + cm.state.lint.html = html; + changed = true; + } + }); + if (changed) { renderLintReport(true); } } @@ -949,19 +986,23 @@ function initWithStyle(style) { }); var queue = style.sections.length ? style.sections : [{code: ""}]; var queueStart = new Date().getTime(); - // after 200ms the sections will be added asynchronously - while (new Date().getTime() - queueStart <= 200 && queue.length) { - maximizeCodeHeight(addSection(null, queue.shift()), !queue.length); - } - if (queue.length) { - setTimeout(function processQueue() { - maximizeCodeHeight(addSection(null, queue.shift()), !queue.length); - if (queue.length) { - setTimeout(processQueue, 0); - } - }, 0); + // after 100ms the sections will be added asynchronously + while (new Date().getTime() - queueStart <= 100 && queue.length) { + add(); } + (function processQueue() { + if (queue.length) { + add(); + setTimeout(processQueue, 0); + } + })(); initHooks(); + + function add() { + var sectionDiv = addSection(null, queue.shift()); + maximizeCodeHeight(sectionDiv, !queue.length); + updateLintReport(getCodeMirrorForSection(sectionDiv), 500); + } } function initHooks() { @@ -1071,6 +1112,8 @@ function validate() { } function save() { + updateLintReport(null, 0); + // save the contents of the CodeMirror editors back into the textareas for (var i=0; i < editors.length; i++) { editors[i].save(); diff --git a/localization.js b/localization.js index 18fd9059..e758fdc2 100644 --- a/localization.js +++ b/localization.js @@ -35,13 +35,14 @@ function tNodeList(nodes) { if (node.nodeType != 1) { // not an ELEMENT_NODE continue; } - for (var a = 0; a < node.attributes.length; a++) { - var name = node.attributes[a].nodeName; + for (var a = node.attributes.length - 1; a >= 0; a--) { + var attr = node.attributes[a]; + var name = attr.nodeName; if (name.indexOf("i18n-") != 0) { continue; } name = name.substr(5); // "i18n-".length - var value = t(node.attributes[a].nodeValue); + var value = t(attr.nodeValue); switch (name) { case "text": node.insertBefore(document.createTextNode(value), node.firstChild); @@ -52,6 +53,7 @@ function tNodeList(nodes) { default: node.setAttribute(name, value); } + node.removeAttributeNode(attr); } } } @@ -69,5 +71,6 @@ function tDocLoader() { observer.observe(document, {subtree: true, childList: true}); document.addEventListener("DOMContentLoaded", function() { observer.disconnect(); + tNodeList(document.querySelectorAll("*")); }); } diff --git a/messaging.js b/messaging.js index cfe9fbd8..ea30055b 100644 --- a/messaging.js +++ b/messaging.js @@ -76,14 +76,9 @@ function getActiveTabRealURL(callback) { function getTabRealURL(tab, callback) { if (tab.url != "chrome://newtab/") { callback(tab.url); - return; - } - chrome.webNavigation.getAllFrames({tabId: tab.id}, function(frames) { - frames.some(function(frame) { - if (frame.parentFrameId == -1) { // parentless frame is the main frame - callback(frame.url); - return true; - } + } else { + chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, function(frame) { + frame && callback(frame.url); }); - }); + } } diff --git a/storage.js b/storage.js index 44376022..62332718 100644 --- a/storage.js +++ b/storage.js @@ -271,7 +271,7 @@ function shallowCopy(obj) { } function equal(a, b) { - if (!a || !b || typeof a != "object") { + if (!a || !b || typeof a != "object" || typeof b != "object") { return a === b; } if (Object.keys(a).length != Object.keys(b).length) {