chrome extension modify CSS in document_start - google-chrome-extension

The doc here http://archive.is/m7For#selection-5667.63-5669.3 says:
In the case of "document_start", the files are injected after any files from css, but before any other DOM is constructed or any other script is run.
However in a comment here I saw:
I'v realized now. That "CSS" does not refer to <style> and <link rel>
but refers only to CSS injected by manifest
I'm confused about this ..
Is it OK to modify the CSS of the page I inject into in document_start ?

The comment is correct and it is not OK to modify the CSS of the page you inject into in document_start.
When a script is injected with run_at = document_start it can modify only the CSS it itself injected. It does not have access to the DOM including CSS until some point later (probably after the head is created).
However you can modify the CSS of the page before it is shown by using an observer like this:
const convertCSS = () => {
if (!convertCSS.nSheets) convertCSS.nSheets=0;
if (convertCSS.nSheets===window.document.styleSheets.length) return;
convertCSS.nSheets=window.document.styleSheets.length;
for (const styleSheet of window.document.styleSheets) {
const classes = styleSheet.rules || styleSheet.cssRules;
if (!classes) continue;
for (const cssRule of classes) {
if (cssRule.type !== 1 || !cssRule.style) continue;
const selector = cssRule.selectorText, style=cssRule.style;
if (!selector || !style.cssText) continue;
for (let i=0; i<style.length; i++) {
const propertyName=style.item(i), propertyValue=style.getPropertyValue(propertyName);
// YOUR LOGIC HERE ie:
// if (propertyName==='background-color') cssRule.style.setProperty(propertyName, 'yellow', style.getPropertyPriority(propertyName));
}
}
}
}
const observer =new MutationObserver((mutations, observer) => convertCSS());
observer.observe(document, { childList: true, subtree:true });
If you don't need to modify the CSS on new elements once the page is loaded add :
document.addEventListener("DOMContentLoaded", e => observer.disconnect());
Also you probably want "all_frames": true in your manifest.

Related

How to copy files from node_modules with Vite / SvelteKit?

I have a node module dependency for theming content at node_modules/foo-styles.
For most css that I want to pull in, I can simply use an import 'foo-styles/dist/controls/button.css' in the page/component and vite does its magic, shoving the css where it fits best.
For theming however, I need to switch the included css variables file based on active theme in my <svelte:head> , e.g. reference foo-styles/dist/themes/{$theme}/color-variables.css in the markup.
To do so, I assume that vite must copy those files from node_modules/foo-styles to my apps asset directory without import magic, renaming of files etc.
I can only find examples for doing this with webpack or rollup using copy plugins ... is this something that vite can do out of the box? Is there a plugin available? How do you copy assets from node_module dependencies to the bundle?
You can make Vite copy assets and return a URL by adding ?url to the import. With this you then can just dynamically add a link to the <head> with said URL.
E.g.
import themeUrl from 'some-module/css/theme.css?url'
onMount(() => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = themeUrl;
document.head.appendChild(link);
return () => link.remove();
});
If you do not want to manually list all theme imports, you can use Vite's glob import feature. Here the URL has to be written differently as it must start with / or ./:
const styles = import.meta.glob(
'/node_modules/some-module/css/*.css',
{ query: 'url', eager: true },
);
let selectValue = 'some-theme';
$: selectedStyle = `/node_modules/some-module/css/${selectedValue}.css`;
let link = null;
onMount(() => {
link = document.createElement('link');
link.rel = 'stylesheet';
document.head.appendChild(link);
return () => link?.remove();
});
$: if (link) {
const { default: href } = styles[selectedStyle];
link.href = href;
}
You can also use svelte:head, which can be simpler and will add the style reference during SSR, however the order of the stylesheets can be inconsistent as the style may be added before other styles on first load but added after these styles on navigation, so rules may be accidentally overridden.
<script>
const styles = import.meta.glob(
'/node_modules/some-module/css/*.css',
{ query: 'url', eager: true },
);
let selectValue = 'some-theme';
$: href = styles[`/node_modules/some-module/css/${selectedValue}.css`].default;
</script>
<svelte:head>
<link rel="stylesheet" {href} />
</svelte:head>

How to inject Material-UI stylesheets into a jest/react-testing-library test?

It seems that if you don't inject Material-UI stylesheets into a jest/react-testing-library test then jsdom will fail to get the correct styles from your components (e.g. running getComputedStyle(component) will return the incorrect styles for the component).
How you properly setup a jest/react-testing-library test so that the styles are correctly injected into the test? I've already wrapped the components in a theme provider, which works fine.
As a workaround reinserting the whole head (or the element where JSS styles are injected) before assertion seems to apply styles correctly with both getComputedStyle() and react testing library's toHaveStyle():
import React from "react";
import "#testing-library/jest-dom/extend-expect";
import { render } from "#testing-library/react";
test("test my styles", () => {
const { getByTestId } = render(
<div data-testid="wrapper">
<MyButtonStyledWithJSS/>
</div>
);
const button = getByTestId("wrapper").firstChild;
document.head.innerHTML = document.head.innerHTML;
expect(button).toHaveStyle(`border-radius: 4px;`);
});
This will still fail though when you're using dynamic styles, like:
myButton: {
padding: props => props.spacing,
...
}
That's because JSS uses CSSStyleSheet.insertRule method to inject these styles, and it won't appear as a style node in the head. One solution to this issue is to hook into the browser's insertRule method and add incoming rules to the head as style tags. To extract all this into a function:
function mockStyleInjection() {
const defaultInsertRule = window.CSSStyleSheet.prototype.insertRule;
window.CSSStyleSheet.prototype.insertRule = function (rule, index) {
const styleElement = document.createElement("style");
const textNode = document.createTextNode(rule);
styleElement.appendChild(textNode);
document.head.appendChild(styleElement);
return defaultInsertRule.bind(this)(rule, index);
};
// cleanup function, which reinserts the head and cleans up method overwrite
return function applyJSSRules() {
window.CSSStyleSheet.prototype.insertRule = defaultInsertRule;
document.head.innerHTML = document.head.innerHTML;
};
}
Example usage of this function in our previous test:
import React from "react";
import "#testing-library/jest-dom/extend-expect";
import { render } from "#testing-library/react";
test("test my styles", () => {
const applyJSSRules = mockStyleInjection();
const { getByTestId } = render(
<div data-testid="wrapper">
<MyButtonStyledWithJSS spacing="8px"/>
</div>
);
const button = getByTestId("wrapper").firstChild;
applyJSSRules();
expect(button).toHaveStyle("border-radius: 4px;");
expect(button).toHaveStyle("padding: 8px;");
});
This ultimately seems like an issue with JSS and various browser implementations like jsdom and and Blink (at least in Chrome). You can see it in Chrome when trying to modify/enable/disable these style rules (you can't).
The behavior appears to be a result of the JSS library using the CSSOM insertRule API. There's a stylesheet generated in the DOM for the styles we expect in our component, but the tag is empty - it's just used to link the shadow CSS back to the DOM. The styles are never written to the inline stylesheet in the DOM, and as a result, the getComputedStyle method does not return the expected results.
There's an open issue to address this behavior and make development easier.
I switched my custom components to styled-components, which does not have some of these idiosyncrasies.
Material-UI is planning on transitioning soon as well.
You could add this to a custom render function. After rendering, the function pulls the styles out of cssom and puts them into a style tag. Here is an implementation:
let customRender = (ui, options) => {
let renderResult = render(ui, options);
let styleElement = document.createElement("style");
let styleText = "";
for (let styleSheet of document.styleSheets) {
for (let rule of styleSheet.cssRules) {
styleText += rule.cssText + "\n";
}
}
styleElement.textContent = styleText.slice(0, -1);
document.head.appendChild(styleElement);
// remove old style elements
let emptyStyleElements = document.head.querySelectorAll('style[data-jss=""]');
for (let element of emptyStyleElements) {
element.remove();
}
return renderResult;
}
I can't speak specifically to Material-UI stylesheets, but you can inject a stylesheet into rendered component:
import {render} from '#testing-library/react';
import fs from 'fs';
import path from 'path';
const stylesheetFile = fs.reactFileSync(path.resolve(__dirname, '../path-to-stylesheet'), 'utf-8');
const styleTag = document.createElement('style');
styleTag.type = 'text/css';
styleTag.innerHTML = stylesheetFile;
const rendered = render(<MyComponent>);
rendered.append(style);
You don't necessarily have to read from a file, you can use whatever text you want.

How to include corresponding css and javascript of the page in assemble layout

I am using assemble to generate from html files with a common layout files. I want to include the corresponding css and javascript file with different pages. So that, for index.html, only index.css and index.js are included, and for about-us.html, only about-us.css and about-us.js are included.
Here's my respository on github https://github.com/xchitox/assemble-gulp-test
If you are already using gulp then use gulp-inject to inject the html files with their respective dependencies based on injection tags.
function injectStartingTag(filepath, starttag) {
var inject = require('gulp-inject');
// Injects the source using relative paths
return inject(gulp.src(filepath, {
read: false
}), {
relative: true,
starttag: '<!-- ' + starttag + ' -->'
});
}
In your index.html:
<!--inject:index:css-->
<!--endinject-->
<!--inject:index:js-->
<!--endinject-->
In your about-us.html:
<!--inject:about-us:css-->
<!--endinject-->
<!--inject:about-us:js-->
<!--endinject-->
Call the function above in any gulp task. You can filter with gulp-if and call the function with the specific starttag. i.e.:
gulp.task('Inject', function(){
var _if = require('gulp-if');
var all_your_files = "**/*.*"; // obvously only add html, js, and css files
return gulp
.src(all_your_files)
.pipe(_if('index.html', injectStartingTag('index.css', 'inject:index:css')))
.pipe(_if('about-us.html', injectStartingTag('about-us.css', 'inject:about-us:css')))
...
...
// you get the idea
});
You can use a helper to generate the link to the assets based on the filename of the current view:
app.helper('filename', function() {
// this.view is the current view being rendered
return this.view.stem; // just get the basename without extension
});
Now you can use this to add the assets path in your layout:
<link rel="stylesheet" href="/assets/css/{{filename}}.css">
<script src="/assets/js/{{filename}}.js"></script>

Look up layouts and include files on multiple paths in express/ejs

I am building a node application based on express using ejs as template engine.
To support different looks for the site I would like to put files in folders named base holding vanilla stuff and an overlay per style/theme/client. I want the system to lookup files in overlay first, and only if not found use what is in base.
For static content like images and css files this works using the static middleware twice, first for the overlay, then for base.
I want to do the same for templates rendered through ejs. I have found:
Multiple View paths on Node.js + Express
And BananaAcids answer provided in that thread almost works for me as long as I call simple ejs views. If I want to use layouts or includes it breaks down for overlaid views because the base directory is now overlay and layouts that are unchanged from base are no longer found.
A simplified example follows.
File base/layouts/root.ejs:
<!DOCTYPE html>
<html lang="en">
<body>
<!-- Main content of pages using this layout goes here -->
<%- body %>
</body>
</html>
File base/index.ejs:
<% layout('layouts/root') -%>
<p>
A page in base using the root layout
</p>
File overlay/index.ejs:
<% layout('layouts/root') -%>
<p>
Totally different page in the overlay.
</p>
Using BananaAcids approach and setting both paths as view-sources express/ejs now correctly locates overlay/index.ejs as the view to render but as I did not also overlay layouts/root it fails because the resulting file overlay/layouts/root.ejs does not exist.
Is there a way of patching my way further down into ejs so that I can help it locate this file in base/layout/root.ejs instead?
Thank you for reading this and any brain cycles you have expended on it.
Here's what I've used to monkey patch Express (4.x) to add layout support:
/*
Usage:
Set a global/default layout with:
app.set('view layout', 'foo');
Set a layout per-render (overrides global layout) with:
res.render('foo', { layout: 'bar' });
Or disable a layout if a global layout is set with:
res.render('foo', { layout: false });
If no layout is provided using either of the above methods,
then the view will be rendered as-is like normal.
Inside your layout, the variable `body` holds the rendered partial/child view.
Installation:
Call `mpResponse();` before doing `require('express');` in your application.
*/
function mpResponse() {
var expressResponse = require('express/lib/response'),
expressResRender = expressResponse.render;
expressResponse.render = function(view, options, fn) {
options = options || {};
var self = this,
req = this.req,
app = req.app,
layout,
cb;
// support callback function as second arg
if (typeof options === 'function')
fn = options, options = {};
// merge res.locals
options._locals = self.locals;
// default callback to respond
fn = fn || function(err, str) {
if (err) return req.next(err);
self.send(str);
};
if (typeof options.layout === 'string')
layout = options.layout;
else if (options.layout !== false
&& typeof app.get('view layout') === 'string')
layout = app.get('view layout');
if (layout) {
cb = function(err, str) {
if (err) return req.next(err);
options.body = str;
expressResRender.call(self, layout, options, fn);
};
} else
cb = fn;
// render
app.render(view, options, cb);
};
}
I patched EJS to support the multiple views folder feature added in Express v.4.10. There is currently a pending pull request you can find here: https://github.com/mde/ejs/pull/120. If you still need this solution for your project you could include my fork into your package.json as a EJS replacement:
{
...
"dependencies": {
"ejs": "git://github.com/MarcelloDiSimone/ejs.git#feature/multi-views"
}
}
..or you plus one the pull request and hope it'll be accepted soon.

PJax not working with MVC project

I've followed the samples. I added a _PjaxLayout:
<title>#ViewBag.Title</title>
#RenderBody()
Modified my _Layout:
<div id="shell">
#RenderBody()
</div>
<script type="text/javascript">
$(function () {
// pjax
$.pjax.defaults.timeout = 5000;
$('a').pjax('#shell');
})
</script>
Updated ViewStart:
#{
if (Request.Headers["X-PJAX"] != null) {
Layout = "~/Views/Shared/_PjaxLayout.cshtml";
} else {
Layout = "~/Views/Shared/_Layout.cshtml";
}
}
Yet every time I click on an 'a' tag, the pjax code doesn't get called. It's as if the selector isn't working when I set up pjax. What am I doing wrong?
UPDATE:
If I do this:
$('document').ready(function () {
$('a').pjax({
container: '#shell',
timeout: 5000
});
});
I see the pjax code getting hit and the Request headers get updated, and the new content loads on the page, but the styling and layout get really messed up and duplicated...
UPDATE:
Inspecting the DOM after this craziness happens reveals that the new page content is getting loaded directly into the anchor that I click, instead of into the element with id #shell. WTF?
You are using a legacy syntax, the new pjax uses the following:
$(document).pjax('a', '#shell', { fragment: '#shell' });
Also I am not familiar with the language you use, but in order to make pjax happen there has to be an HTML element with the id shell in your ViewStart.
As I am not sure about the syntax in that language, try something similar to this for testing:
#{
if (Request.Headers["X-PJAX"] != null) {
echo "<ul id="shell"> pjaaxxx </ul>"; // Would work in php, update syntax
} else {
Layout = "~/Views/Shared/_Layout.cshtml";
}
}
I am not seeing that syntax as valid in the PJax documentation.
Are you sure you didn't mean $(document).pjax('a',{});?
$.pjax immediately executes from what I can tell.

Resources