├── .editorconfig ├── .gitignore ├── .htmlhintrc ├── .stylelintrc ├── AUTHORS ├── CHANGELOG.md ├── CNAME ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── docs ├── error.html └── index.html ├── labels.json ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── humans.txt ├── konami.js ├── main.css ├── manifest.json ├── mstile-150x150.png ├── mstile-310x310.png ├── mutation-summary.js ├── nuclear-reset.css ├── nuclear-reset.js └── safari-pinned-tab.svg ├── server.js └── views └── enter-url.html /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles 2 | # between different editors and IDEs 3 | # editorconfig.org 4 | 5 | 6 | # Topmost EditorConfig file 7 | root = true 8 | 9 | # Overall defaults 10 | [*] 11 | 12 | # Tab style 13 | indent_style = space 14 | indent_size = 4 15 | 16 | # File format and handling 17 | charset = utf-8 18 | end_of_line = lf 19 | insert_final_newline = true 20 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.htmlhintrc: -------------------------------------------------------------------------------- 1 | { 2 | "tagname-lowercase": true, 3 | "attr-lowercase": true, 4 | "attr-value-double-quotes": true, 5 | "tag-pair": true, 6 | "tag-self-close": true, 7 | "spec-char-escape": true, 8 | "id-unique": true, 9 | "src-not-empty": true, 10 | "attr-no-duplication": true, 11 | "title-require": true, 12 | "alt-require": true, 13 | "doctype-html5": true, 14 | "space-tab-mixed-disabled": true, 15 | "id-class-ad-disabled": true, 16 | "attr-unsafe-chars": true 17 | } 18 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "at-rule-no-unknown": [true, { 4 | "ignoreAtRules": ["at-root", "debug", "each", "else", "error", "extend", "for", "function", "import", "if", "include", "media", "mixin", "return", "warn", "while"] 5 | }], 6 | "at-rule-no-vendor-prefix": [true, { 7 | "message": "Vendor prefixes are automatically generated (at-rule-no-vendor-prefix)" 8 | }], 9 | "at-rule-semicolon-newline-after": ["always", { 10 | "message": "Make @ rules visually distinct from other declarations (at-rule-semicolon-newline-after)" 11 | }], 12 | "at-rule-empty-line-before": ["always", { 13 | "except": ["blockless-group", "first-nested"], 14 | "ignore": ["after-comment"] 15 | }], 16 | "block-closing-brace-empty-line-before": "never", 17 | "block-closing-brace-newline-after": "always", 18 | "block-closing-brace-newline-before": "always-multi-line", 19 | "block-closing-brace-space-before": "always-single-line", 20 | "block-no-empty": true, 21 | "block-opening-brace-newline-after": "always-multi-line", 22 | "block-opening-brace-space-after": "always-single-line", 23 | "block-opening-brace-space-before": "always", 24 | "color-hex-case": ["lower", { 25 | "severity": "warning", 26 | "message": "Lowercase letters in hex code are more distinct (color-hex-case)" 27 | }], 28 | "color-hex-length": ["long", { 29 | "severity": "warning", 30 | "message": "Keep all hex code values at a standardized six character length (color-hex-length)" 31 | }], 32 | "color-named": ["never", { 33 | "severity": "warning", 34 | "message": "Browsers can render named colors inconsistently, use variables instead (color-named)" 35 | }], 36 | "color-no-invalid-hex": true, 37 | "comment-no-empty": true, 38 | "custom-property-empty-line-before": ["always", { 39 | "except": "after-custom-property", 40 | "ignore": "after-comment" 41 | }], 42 | "declaration-bang-space-after": "never", 43 | "declaration-bang-space-before": "always", 44 | "declaration-colon-newline-after": "always-multi-line", 45 | "declaration-colon-space-after": "always-single-line", 46 | "declaration-colon-space-before": "never", 47 | "declaration-block-no-duplicate-properties": true, 48 | "declaration-block-no-shorthand-property-overrides": true, 49 | "declaration-block-semicolon-newline-after": "always-multi-line", 50 | "declaration-block-semicolon-space-after": "always-single-line", 51 | "declaration-block-semicolon-space-before": "never", 52 | "declaration-block-single-line-max-declarations": [1, { 53 | "severity": "warning" 54 | }], 55 | "declaration-block-trailing-semicolon": "always", 56 | "font-family-name-quotes": "always-unless-keyword", 57 | "font-weight-notation": ["numeric", { 58 | "severity": "warning", 59 | "message": "Use numeric font weights for better typographic control (font-weight-notation)" 60 | }], 61 | "function-calc-no-unspaced-operator": true, 62 | "function-comma-space-after": "always-single-line", 63 | "function-comma-space-before": "never-single-line", 64 | "function-linear-gradient-no-nonstandard-direction": true, 65 | "function-parentheses-newline-inside": "always-multi-line", 66 | "function-parentheses-space-inside": "never-single-line", 67 | "function-url-quotes": "never", 68 | "function-whitespace-after": "always", 69 | "length-zero-no-unit": true, 70 | "media-feature-colon-space-after": "always", 71 | "media-feature-colon-space-before": "never", 72 | "media-feature-name-case": "lower", 73 | "media-feature-name-no-vendor-prefix": [true, { 74 | "message": "Vendor prefixes are automatically generated (media-feature-name-no-vendor-prefix)" 75 | }], 76 | "media-feature-no-missing-punctuation": true, 77 | "media-feature-parentheses-space-inside": "never", 78 | "media-feature-range-operator-space-after": "always", 79 | "media-feature-range-operator-space-before": "always", 80 | "media-query-list-comma-newline-after": "always-multi-line", 81 | "media-query-list-comma-newline-before": "never-multi-line", 82 | "max-empty-lines": [3, { 83 | "severity": "warning", 84 | "message": "More than 3 empty lines are present (max-empty-lines)" 85 | }], 86 | "no-duplicate-selectors": true, 87 | "no-eol-whitespace": true, 88 | "no-extra-semicolons": true, 89 | "no-indistinguishable-colors": [true, { 90 | "severity": "warning" 91 | }], 92 | "no-unknown-animations": true, 93 | "number-leading-zero": "always", 94 | "number-max-precision": [5, { 95 | "severity": "warning", 96 | "message": "More than 5 decimal places are present (number-max-precision)" 97 | }], 98 | "number-no-trailing-zeros": true, 99 | "property-no-unknown": true, 100 | "property-no-vendor-prefix": [true, { 101 | "message": "Vendor prefixes are automatically generated (property-no-vendor-prefix)" 102 | }], 103 | "root-no-standard-properties": true, 104 | "rule-nested-empty-line-before": ["always-multi-line", { 105 | "except": ["first-nested"], 106 | "ignore": "after-comment" 107 | }], 108 | "rule-non-nested-empty-line-before": ["always", { 109 | "ignore": ["after-comment"] 110 | }], 111 | "selector-combinator-space-after": "always", 112 | "selector-combinator-space-before": "always", 113 | "selector-no-id": true, 114 | "selector-no-vendor-prefix": [true, { 115 | "message": "Vendor prefixes are automatically generated (selector-no-vendor-prefix)" 116 | }], 117 | "selector-pseudo-element-colon-notation": "double", 118 | "selector-root-no-composition": true, 119 | "selector-type-case": "lower", 120 | "selector-list-comma-newline-after": "always-multi-line", 121 | "selector-list-comma-space-after": "always-single-line", 122 | "selector-list-comma-space-before": "never", 123 | "selector-max-specificity": "0,3,1", 124 | "string-quotes": "double", 125 | "value-list-comma-space-after": "always-single-line", 126 | "value-list-comma-space-before": "never-single-line", 127 | "value-no-vendor-prefix": [true, { 128 | "message": "Vendor prefixes are automatically generated (value-no-vendor-prefix)" 129 | }] 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Daniel McLaughlin 2 | Eric Bailey 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](http://semver.org/) and tries to follow the [Keep a CHANGELOG](http://keepachangelog.com) convention. 6 | 7 | ## vNext - Unreleased 8 | 9 | - Inline input validation 10 | 11 | ## v0.0.2 - 2016-10-10 12 | 13 | - Updated the design of the landing page 14 | - Fleshed out the project README 15 | - Added error page 16 | 17 | ## v0.0.1 - 2016-10-03 18 | 19 | - Initial release. 20 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | structure.exposed 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Table of Contents 4 | 1. [Before you get started](#before-you-get-started) 5 | 1. [Code of Conduct](#code-of-conduct) 6 | - [Terms of Use](#terms-of-use) 7 | - [Labeling](#labeling) 8 | - [Templates](#templates) 9 | - [Submitting Issues](#submitting-issues) 10 | - [Pull and Feature Requests](#pull-and-feature-requests) 11 | 1. [Submitting Pull Requests](#submitting-pull-requests) 12 | - [Submitting Feature Requests](#submitting-feature-requests) 13 | 14 | Thanks for getting involved! Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. 15 | 16 | Following these guidelines helps to communicate that you respect the time of the people managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features. 17 | 18 | 19 | ## Before you get started 20 | 21 | ### Code of Conduct 22 | This project adheres to the [Contributor Covenant code of conduct](http://contributor-covenant.org/version/1/4/). By participating, you are expected to uphold this code. Please report unacceptable behavior to [the project authors](https://github.com/danielsmc/structure-exposed/blob/master/AUTHORS). 23 | 24 | ### Terms of Use 25 | By submitting code or a feature request, you agree to allow the project owners to license your work under the terms of the [project license](https://github.com/danielsmc/structure-exposed/blob/master/LICENSE). 26 | 27 | ### Labeling 28 | This project uses labels to help organize and prioritize contributions. The labels are broken into three main types: **Type**, **Status**, and **Priority**. 29 | 30 | When submitting a Bug Report, Pull Request, or Feature Request, please select an appropriate label from each of the three types. Contributions that don't observe this labeling schema are likely to be rejected. 31 | 32 | Labels will be updated throughout the submission process to reflect the submission's overall status. For more information, please refer to [this article](https://medium.com/@dave_lunny/sane-github-labels-c5d2e6004b63#.fh462xzfj). 33 | 34 | ### Templates 35 | This project uses templates for Issue and Pull Requests. Following the provided template helps the project maintainers have all the right details up front, which makes addressing feedback easier. 36 | 37 | ## Submitting Issues 38 | *An Issue is a demonstrable problem that is caused by the code in the repository.* 39 | 40 | First, use the [GitHub Issue search](https://github.com/danielsmc/structure-exposed/issues) to check if the issue has already been reported—then, check if the issue has been fixed. Try to reproduce it using the latest [master branch](https://github.com/danielsmc/structure-exposed) in the repository. Next, isolate the problem—ideally [create a reduced test case](https://css-tricks.com/reduced-test-cases/) and a live example. 41 | 42 | A good Issue report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) and OS versions experience the problem? What would you expect to be the outcome? Any guesses to a possible solution? All these details will help people to fix any potential bugs. 43 | 44 | ## Pull and Feature Requests 45 | 46 | ### Submitting Pull Requests 47 | *A Pull Request is a collection of changes submitted with the intention of being incorporated into the project.* 48 | 49 | Good Pull Requests, patches, improvements, new features, etc. are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. 50 | 51 | When submitting a Pull Request, first use the [GitHub Pull Request search](https://github.com/danielsmc/structure-exposed/pulls) to check if the request has already been submitted. 52 | 53 | If it hasn't, please ask first before embarking on any significant Pull Request (e.g. implementing features, refactoring code, porting to a different language), otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project. 54 | 55 | Please adhere to the coding conventions used throughout a project (indentation, accurate comments, etc.) and any other requirements (such as test coverage). 56 | 57 | 58 | ### Submitting Feature Requests 59 | *A Feature Request is a new component or an enhancement to existing functionality.* 60 | 61 | Feature Requests are welcome. It's up to you to make a strong case to convince the project team of the merits of this feature. Remember that your request will be prioritized accordingly and not all requests can always be implemented in a timely fashion. 62 | 63 | Before submitting, take a moment to find out whether your idea fits with the scope and aims of the project. Use the GitHub Issue and Pull Request searches to see if there is something similar to your propose feature being worked on already. 64 | 65 | Use the [GitHub Issues page](https://github.com/danielsmc/structure-exposed/issues) to submit your Feature Request. Please provide as much detail and context in your request as possible. 66 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue title 2 | 3 | ## Expected behavior 4 | 5 | 6 | ## Actual behavior 7 | 8 | 9 | ## Steps to reproduce behavior 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Daniel McLaughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request title 2 | 3 | ## What does this Pull Request do? 4 | 5 | 6 | ## Where should the reviewer start? 7 | 8 | 9 | ## How should this be manually tested? 10 | 11 | 12 | ## Any background context you want to provide? 13 | 14 | 15 | ## What are the relevant tickets? 16 | 17 | 18 | ## Applicable screenshots 19 | 20 | 21 | ## Additional questions 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [structure.exposed](http://structure.exposed/) 2 | 3 | A simple proxy that strips away CSS frippery. 4 | 5 | ## Why? 6 | 7 | [structure.exposed](http://structure.exposed/) is a good way to check how your site will behave if styles fail to load. 8 | 9 | Aggressive firewalls, intermittent connection, shiesty service providers, bad caches, browser plugins, content blockers, non-standard browsers, large blocking assets, sloppy JavaScript, compromised ad networks, VPNs, CDN outages, scrapers and archivers, and panicky production hotfixes can all conspire to interrupt your style's HTTP request. 10 | 11 | Authoring your markup in a logical order using semantic markup ensures that basic functionality is retained even if the visuals are not. It is also great for [helping to make your site accessible](http://a11yproject.com/posts/navigate-using-just-your-keyboard/). 12 | 13 | ## Frequently Asked Questions 14 | 15 | ### How are styles removed? 16 | 17 | Styles are removed by [an injected script](https://github.com/danielsmc/structure-exposed/blob/master/public/nuclear-reset.js) that removes every ` 141 | 142 | 143 | 144 |
145 | 146 |

womp womp

147 | 148 |

149 | Looks like our burning pile of garbage server is having an issue at the moment, but you are welcome to peruse our code on GitHub. 150 |

151 | 152 |
153 | 154 | 155 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /labels.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "[Status] Rejected", 4 | "color": "#ececec" 5 | }, 6 | { 7 | "name": "[Status] Submitted", 8 | "color": "#fef2c0" 9 | }, 10 | { 11 | "name": "[Status] In Progress", 12 | "color": "#bfe5bf" 13 | }, 14 | { 15 | "name": "[Status] Blocked", 16 | "color": "#e11d21" 17 | }, 18 | { 19 | "name": "[Status] Abandoned", 20 | "color": "#ececec" 21 | }, 22 | { 23 | "name": "[Status] Accepted", 24 | "color": "#009800" 25 | }, 26 | { 27 | "name": "[Priority] Critical", 28 | "color": "#e11d21" 29 | }, 30 | { 31 | "name": "[Priority] High", 32 | "color": "#fad8c7" 33 | }, 34 | { 35 | "name": "[Priority] Low", 36 | "color": "#bfe5bf" 37 | }, 38 | { 39 | "name": "[Priority] Medium", 40 | "color": "#fef2c0" 41 | }, 42 | { 43 | "name": "[Type] Bug", 44 | "color": "#e11d21" 45 | }, 46 | { 47 | "name": "[Type] Enhancement", 48 | "color": "#c7def8" 49 | }, 50 | { 51 | "name": "[Type] Maintenance", 52 | "color": "#fef2c0" 53 | }, 54 | { 55 | "name": "[Type] Question", 56 | "color": "#d4c5f9" 57 | } 58 | ] 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "structure-exposed", 3 | "description": "A simple proxy that strips away CSS frippery.", 4 | "homepage": "https://structure.exposed/", 5 | "version": "0.0.2", 6 | "author": "Dan McLaughlin (http://danielmclaughl.in/)", 7 | "contributors": "Eric Bailey (http://ericwbailey.com/)", 8 | "main": "server.js", 9 | "scripts": { 10 | "start": "node server.js" 11 | }, 12 | "dependencies": { 13 | "express": "^5.0.0-alpha.2", 14 | "request": "^2.75.0", 15 | "concat-stream": "^1.5.2" 16 | }, 17 | "engines": { 18 | "node": "^6.11.1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/danielsmc/structure-exposed" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/danielsmc/structure-exposed/issues", 26 | "email": "danielsmc@gmail.com" 27 | }, 28 | "keywords": [ 29 | "css", 30 | "styles", 31 | "html", 32 | "semantics", 33 | "accessibility" 34 | ], 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | #0074d9 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/favicon.ico -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | The Humans Responsible & Colophon 2 | humanstxt.org 3 | 4 | 5 | structure.exposed 6 | http://structure.exposed 7 | 8 | 9 | ## Team 10 | - Dan McLaughlin 11 | - Developer 12 | - Cambridge, MA 13 | - @mclaughlin 14 | - Eric Bailey 15 | - Designer/Developer 16 | - Somerville, MA 17 | - @ericwbailey 18 | 19 | 20 | ## Thanks 21 | - The Hot Garbage Crew 22 | - Konami.js, (http://code.snaptortoise.com/konami-js/) 23 | 24 | 25 | ## Site 26 | - Standards 27 | - HTML5 28 | - CSS3 29 | - JavaScript 30 | - Components 31 | - Git, https://git-scm.com/ 32 | - GitHub, https://github.com/ 33 | - HyperDev, https://hyperdev.com/ 34 | - Node.js, https://nodejs.org/en/ 35 | - Software 36 | - Atom, http://atom.io/ 37 | - Chrome, https://www.google.com/chrome/ 38 | - Firefox, https://www.mozilla.org/en-US/firefox/new/ 39 | - Safari, http://www.apple.com/safari/ 40 | - Sketch, https://www.sketchapp.com/ 41 | - Tower, https://www.git-tower.com/ 42 | -------------------------------------------------------------------------------- /public/konami.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Konami-JS ~ 3 | * :: Now with support for touch events and multiple instances for 4 | * :: those situations that call for multiple easter eggs! 5 | * Code: https://github.com/snaptortoise/konami-js 6 | * Examples: http://www.snaptortoise.com/konami-js 7 | * Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com) 8 | * Version: 1.4.6 (3/2/2016) 9 | * Licensed under the MIT License (http://opensource.org/licenses/MIT) 10 | * Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1 and Dolphin Browser 11 | */ 12 | 13 | var Konami = function (callback) { 14 | var konami = { 15 | addEvent: function (obj, type, fn, ref_obj) { 16 | if (obj.addEventListener) 17 | obj.addEventListener(type, fn, false); 18 | else if (obj.attachEvent) { 19 | // IE 20 | obj["e" + type + fn] = fn; 21 | obj[type + fn] = function () { 22 | obj["e" + type + fn](window.event, ref_obj); 23 | } 24 | obj.attachEvent("on" + type, obj[type + fn]); 25 | } 26 | }, 27 | input: "", 28 | pattern: "38384040373937396665", 29 | load: function (link) { 30 | this.addEvent(document, "keydown", function (e, ref_obj) { 31 | if (ref_obj) konami = ref_obj; // IE 32 | konami.input += e ? e.keyCode : event.keyCode; 33 | if (konami.input.length > konami.pattern.length) 34 | konami.input = konami.input.substr((konami.input.length - konami.pattern.length)); 35 | if (konami.input == konami.pattern) { 36 | konami.code(link); 37 | konami.input = ""; 38 | e.preventDefault(); 39 | return false; 40 | } 41 | }, this); 42 | this.iphone.load(link); 43 | }, 44 | code: function (link) { 45 | window.location = link 46 | }, 47 | iphone: { 48 | start_x: 0, 49 | start_y: 0, 50 | stop_x: 0, 51 | stop_y: 0, 52 | tap: false, 53 | capture: false, 54 | orig_keys: "", 55 | keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"], 56 | code: function (link) { 57 | konami.code(link); 58 | }, 59 | load: function (link) { 60 | this.orig_keys = this.keys; 61 | konami.addEvent(document, "touchmove", function (e) { 62 | if (e.touches.length == 1 && konami.iphone.capture == true) { 63 | var touch = e.touches[0]; 64 | konami.iphone.stop_x = touch.pageX; 65 | konami.iphone.stop_y = touch.pageY; 66 | konami.iphone.tap = false; 67 | konami.iphone.capture = false; 68 | konami.iphone.check_direction(); 69 | } 70 | }); 71 | konami.addEvent(document, "touchend", function (evt) { 72 | if (konami.iphone.tap == true) konami.iphone.check_direction(link); 73 | }, false); 74 | konami.addEvent(document, "touchstart", function (evt) { 75 | konami.iphone.start_x = evt.changedTouches[0].pageX; 76 | konami.iphone.start_y = evt.changedTouches[0].pageY; 77 | konami.iphone.tap = true; 78 | konami.iphone.capture = true; 79 | }); 80 | }, 81 | check_direction: function (link) { 82 | x_magnitude = Math.abs(this.start_x - this.stop_x); 83 | y_magnitude = Math.abs(this.start_y - this.stop_y); 84 | x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT"; 85 | y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP"; 86 | result = (x_magnitude > y_magnitude) ? x : y; 87 | result = (this.tap == true) ? "TAP" : result; 88 | 89 | if (result == this.keys[0]) this.keys = this.keys.slice(1, this.keys.length); 90 | if (this.keys.length == 0) { 91 | this.keys = this.orig_keys; 92 | this.code(link); 93 | } 94 | } 95 | } 96 | } 97 | 98 | typeof callback === "string" && konami.load(callback); 99 | if (typeof callback === "function") { 100 | konami.code = callback; 101 | konami.load(); 102 | } 103 | 104 | return konami; 105 | }; 106 | 107 | 108 | var easterEgg = new Konami('https://structure.exposed/proxy?url=https%3A%2F%2Fstructure.exposed%2F'); 109 | -------------------------------------------------------------------------------- /public/main.css: -------------------------------------------------------------------------------- 1 | /* Document //////////////////////////////////////////////////////////////// */ 2 | *, 3 | *::before, 4 | *::after { 5 | box-sizing: border-box; 6 | } 7 | 8 | html { 9 | margin: 0; 10 | min-height: 100%; 11 | overflow-y: scroll; 12 | padding: 0; 13 | 14 | background-color: #ffffff; 15 | 16 | color: #4a4a4a; 17 | direction: ltr; 18 | font-family: "TimesNewRoman", "Times New Roman", "Times", "Baskerville", "Georgia", serif; 19 | font-size: 16px; 20 | font-weight: 400; 21 | line-height: 1.3; 22 | text-size-adjust: 100%; 23 | } 24 | 25 | body { 26 | height: 100vh; 27 | margin: 0 8vw; 28 | overflow-x: hidden; 29 | padding: 0; 30 | 31 | font-size: 1em; 32 | 33 | display: -webkit-box; 34 | display: -ms-flexbox; 35 | display: flex; 36 | -webkit-box-align: center; 37 | -ms-flex-align: center; 38 | align-items: center; 39 | -webkit-box-pack: center; 40 | -ms-flex-pack: center; 41 | justify-content: center; 42 | } 43 | 44 | main { 45 | margin: auto; 46 | padding-bottom: 10vh; 47 | } 48 | 49 | 50 | /* Links /////////////////////////////////////////////////////////////////// */ 51 | a { 52 | color: #0074d9; 53 | text-decoration: none; 54 | text-shadow: 55 | 0.04em 0 0 #ffffff, -0.04em 0 0 #ffffff, 56 | 0 0.04em 0 #ffffff, 0 -0.04em 0 #ffffff, 0.1em 0 0 #ffffff, -0.1em 0 0 #ffffff, 0 0.1em 0 #ffffff, 0 -0.1em 0 #ffffff; 57 | background-image: linear-gradient(to right, currentColor 0%, currentColor 100%); 58 | background-repeat: repeat-x; 59 | background-position: bottom 0.08em center; 60 | background-size: 100% 0.1em; 61 | } 62 | 63 | a:hover { 64 | background-image: none; 65 | } 66 | 67 | a:active { 68 | position: relative; 69 | top: 1px; 70 | 71 | color: #004b8c; 72 | } 73 | 74 | 75 | /* Titling ///////////////////////////////////////////////////////////////// */ 76 | .page__title { 77 | margin: 0; 78 | 79 | font-size: calc(1em + 7vw); 80 | letter-spacing: -1px; 81 | } 82 | 83 | .page__title::after { 84 | content: "."; 85 | opacity: 0; 86 | } 87 | 88 | 89 | /* Form //////////////////////////////////////////////////////////////////// */ 90 | .form__label { 91 | display: block; 92 | margin-top: 0.75em; 93 | 94 | font-size: calc(1em + 1.25vw); 95 | } 96 | 97 | .form__url { 98 | display: block; 99 | margin-top: 0.3em; 100 | padding: 0.45em 0.6em 0.5em 0.6em; 101 | width: 100%; 102 | 103 | border: 1px solid #c1c1c1; 104 | border-radius: 0; 105 | 106 | font-size: calc(1em + 1.25vw); 107 | 108 | transition: border-color 0.2s ease-in-out; 109 | 110 | -webkit-appearance: none; 111 | } 112 | 113 | .form__url:hover { 114 | border-color: #4a4a4a; 115 | } 116 | 117 | .form__url:focus { 118 | color: #4a4a4a; 119 | } 120 | 121 | .form__button { 122 | margin-top: 0.75em; 123 | padding: 3vh 5vw; 124 | 125 | border: 0; 126 | border-radius: 0; 127 | 128 | background-color: #0074d9; 129 | 130 | color: #ffffff; 131 | font-size: calc(1em + 1vw); 132 | letter-spacing: 1px; 133 | 134 | transition: background-color 0.2s ease-in-out; 135 | 136 | -webkit-appearance: none; 137 | } 138 | 139 | .form__button:hover { 140 | background-color: #0066bf; 141 | } 142 | 143 | .form__button:active { 144 | padding: 4vh 5vw 2vh 5vw; 145 | 146 | -webkit-box-shadow: inset 0 0.4em 0 0 #004b8c; 147 | -moz-box-shadow: inset 0 0.4em 0 0 #004b8c; 148 | box-shadow: inset 0 0.4em 0 0 #004b8c; 149 | 150 | color: #c1c1c1; 151 | } 152 | 153 | .form__more-info { 154 | display: block; 155 | float: right; 156 | margin-top: 0.9em; 157 | margin-right: 0.25em; 158 | 159 | font-size: calc(1em + 0.5vw); 160 | text-align: right; 161 | } 162 | 163 | 164 | /* Media Queries /////////////////////////////////////////////////////////// */ 165 | @media screen and (max-width: 35em) { 166 | 167 | /* Document */ 168 | body { 169 | margin: 0 6vw; 170 | } 171 | 172 | main { 173 | padding-bottom: 5vh; 174 | } 175 | 176 | /* Form */ 177 | .form__label { 178 | margin-top: 1.25em; 179 | } 180 | 181 | .form__button { 182 | width: 100%; 183 | } 184 | 185 | .form__more-info { 186 | margin-top: 2em; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "structure", 3 | "icons": [ 4 | { 5 | "src": "\/android-chrome-192x192.png?rev=201610091050", 6 | "sizes": "192x192", 7 | "type": "image\/png" 8 | }, 9 | { 10 | "src": "\/android-chrome-512x512.png?rev=201610091050", 11 | "sizes": "512x512", 12 | "type": "image\/png" 13 | } 14 | ], 15 | "theme_color": "#0074d9" 16 | } 17 | -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielsmc/structure-exposed/f0560dbff245337ab9f2d7aa59011ae2ef35fb21/public/mstile-310x310.png -------------------------------------------------------------------------------- /public/mutation-summary.js: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | var __extends = this.__extends || function (d, b) { 15 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 16 | function __() { this.constructor = d; } 17 | __.prototype = b.prototype; 18 | d.prototype = new __(); 19 | }; 20 | var MutationObserverCtor; 21 | if (typeof WebKitMutationObserver !== 'undefined') 22 | MutationObserverCtor = WebKitMutationObserver; 23 | else 24 | MutationObserverCtor = MutationObserver; 25 | if (MutationObserverCtor === undefined) { 26 | console.error('DOM Mutation Observers are required.'); 27 | console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver'); 28 | throw Error('DOM Mutation Observers are required'); 29 | } 30 | var NodeMap = (function () { 31 | function NodeMap() { 32 | this.nodes = []; 33 | this.values = []; 34 | } 35 | NodeMap.prototype.isIndex = function (s) { 36 | return +s === s >>> 0; 37 | }; 38 | NodeMap.prototype.nodeId = function (node) { 39 | var id = node[NodeMap.ID_PROP]; 40 | if (!id) 41 | id = node[NodeMap.ID_PROP] = NodeMap.nextId_++; 42 | return id; 43 | }; 44 | NodeMap.prototype.set = function (node, value) { 45 | var id = this.nodeId(node); 46 | this.nodes[id] = node; 47 | this.values[id] = value; 48 | }; 49 | NodeMap.prototype.get = function (node) { 50 | var id = this.nodeId(node); 51 | return this.values[id]; 52 | }; 53 | NodeMap.prototype.has = function (node) { 54 | return this.nodeId(node) in this.nodes; 55 | }; 56 | NodeMap.prototype.delete = function (node) { 57 | var id = this.nodeId(node); 58 | delete this.nodes[id]; 59 | this.values[id] = undefined; 60 | }; 61 | NodeMap.prototype.keys = function () { 62 | var nodes = []; 63 | for (var id in this.nodes) { 64 | if (!this.isIndex(id)) 65 | continue; 66 | nodes.push(this.nodes[id]); 67 | } 68 | return nodes; 69 | }; 70 | NodeMap.ID_PROP = '__mutation_summary_node_map_id__'; 71 | NodeMap.nextId_ = 1; 72 | return NodeMap; 73 | })(); 74 | /** 75 | * var reachableMatchableProduct = [ 76 | * // STAYED_OUT, ENTERED, STAYED_IN, EXITED 77 | * [ STAYED_OUT, STAYED_OUT, STAYED_OUT, STAYED_OUT ], // STAYED_OUT 78 | * [ STAYED_OUT, ENTERED, ENTERED, STAYED_OUT ], // ENTERED 79 | * [ STAYED_OUT, ENTERED, STAYED_IN, EXITED ], // STAYED_IN 80 | * [ STAYED_OUT, STAYED_OUT, EXITED, EXITED ] // EXITED 81 | * ]; 82 | */ 83 | var Movement; 84 | (function (Movement) { 85 | Movement[Movement["STAYED_OUT"] = 0] = "STAYED_OUT"; 86 | Movement[Movement["ENTERED"] = 1] = "ENTERED"; 87 | Movement[Movement["STAYED_IN"] = 2] = "STAYED_IN"; 88 | Movement[Movement["REPARENTED"] = 3] = "REPARENTED"; 89 | Movement[Movement["REORDERED"] = 4] = "REORDERED"; 90 | Movement[Movement["EXITED"] = 5] = "EXITED"; 91 | })(Movement || (Movement = {})); 92 | function enteredOrExited(changeType) { 93 | return changeType === Movement.ENTERED || changeType === Movement.EXITED; 94 | } 95 | var NodeChange = (function () { 96 | function NodeChange(node, childList, attributes, characterData, oldParentNode, added, attributeOldValues, characterDataOldValue) { 97 | if (childList === void 0) { childList = false; } 98 | if (attributes === void 0) { attributes = false; } 99 | if (characterData === void 0) { characterData = false; } 100 | if (oldParentNode === void 0) { oldParentNode = null; } 101 | if (added === void 0) { added = false; } 102 | if (attributeOldValues === void 0) { attributeOldValues = null; } 103 | if (characterDataOldValue === void 0) { characterDataOldValue = null; } 104 | this.node = node; 105 | this.childList = childList; 106 | this.attributes = attributes; 107 | this.characterData = characterData; 108 | this.oldParentNode = oldParentNode; 109 | this.added = added; 110 | this.attributeOldValues = attributeOldValues; 111 | this.characterDataOldValue = characterDataOldValue; 112 | this.isCaseInsensitive = 113 | this.node.nodeType === Node.ELEMENT_NODE && 114 | this.node instanceof HTMLElement && 115 | this.node.ownerDocument instanceof HTMLDocument; 116 | } 117 | NodeChange.prototype.getAttributeOldValue = function (name) { 118 | if (!this.attributeOldValues) 119 | return undefined; 120 | if (this.isCaseInsensitive) 121 | name = name.toLowerCase(); 122 | return this.attributeOldValues[name]; 123 | }; 124 | NodeChange.prototype.getAttributeNamesMutated = function () { 125 | var names = []; 126 | if (!this.attributeOldValues) 127 | return names; 128 | for (var name in this.attributeOldValues) { 129 | names.push(name); 130 | } 131 | return names; 132 | }; 133 | NodeChange.prototype.attributeMutated = function (name, oldValue) { 134 | this.attributes = true; 135 | this.attributeOldValues = this.attributeOldValues || {}; 136 | if (name in this.attributeOldValues) 137 | return; 138 | this.attributeOldValues[name] = oldValue; 139 | }; 140 | NodeChange.prototype.characterDataMutated = function (oldValue) { 141 | if (this.characterData) 142 | return; 143 | this.characterData = true; 144 | this.characterDataOldValue = oldValue; 145 | }; 146 | // Note: is it possible to receive a removal followed by a removal. This 147 | // can occur if the removed node is added to an non-observed node, that 148 | // node is added to the observed area, and then the node removed from 149 | // it. 150 | NodeChange.prototype.removedFromParent = function (parent) { 151 | this.childList = true; 152 | if (this.added || this.oldParentNode) 153 | this.added = false; 154 | else 155 | this.oldParentNode = parent; 156 | }; 157 | NodeChange.prototype.insertedIntoParent = function () { 158 | this.childList = true; 159 | this.added = true; 160 | }; 161 | // An node's oldParent is 162 | // -its present parent, if its parentNode was not changed. 163 | // -null if the first thing that happened to it was an add. 164 | // -the node it was removed from if the first thing that happened to it 165 | // was a remove. 166 | NodeChange.prototype.getOldParent = function () { 167 | if (this.childList) { 168 | if (this.oldParentNode) 169 | return this.oldParentNode; 170 | if (this.added) 171 | return null; 172 | } 173 | return this.node.parentNode; 174 | }; 175 | return NodeChange; 176 | })(); 177 | var ChildListChange = (function () { 178 | function ChildListChange() { 179 | this.added = new NodeMap(); 180 | this.removed = new NodeMap(); 181 | this.maybeMoved = new NodeMap(); 182 | this.oldPrevious = new NodeMap(); 183 | this.moved = undefined; 184 | } 185 | return ChildListChange; 186 | })(); 187 | var TreeChanges = (function (_super) { 188 | __extends(TreeChanges, _super); 189 | function TreeChanges(rootNode, mutations) { 190 | _super.call(this); 191 | this.rootNode = rootNode; 192 | this.reachableCache = undefined; 193 | this.wasReachableCache = undefined; 194 | this.anyParentsChanged = false; 195 | this.anyAttributesChanged = false; 196 | this.anyCharacterDataChanged = false; 197 | for (var m = 0; m < mutations.length; m++) { 198 | var mutation = mutations[m]; 199 | switch (mutation.type) { 200 | case 'childList': 201 | this.anyParentsChanged = true; 202 | for (var i = 0; i < mutation.removedNodes.length; i++) { 203 | var node = mutation.removedNodes[i]; 204 | this.getChange(node).removedFromParent(mutation.target); 205 | } 206 | for (var i = 0; i < mutation.addedNodes.length; i++) { 207 | var node = mutation.addedNodes[i]; 208 | this.getChange(node).insertedIntoParent(); 209 | } 210 | break; 211 | case 'attributes': 212 | this.anyAttributesChanged = true; 213 | var change = this.getChange(mutation.target); 214 | change.attributeMutated(mutation.attributeName, mutation.oldValue); 215 | break; 216 | case 'characterData': 217 | this.anyCharacterDataChanged = true; 218 | var change = this.getChange(mutation.target); 219 | change.characterDataMutated(mutation.oldValue); 220 | break; 221 | } 222 | } 223 | } 224 | TreeChanges.prototype.getChange = function (node) { 225 | var change = this.get(node); 226 | if (!change) { 227 | change = new NodeChange(node); 228 | this.set(node, change); 229 | } 230 | return change; 231 | }; 232 | TreeChanges.prototype.getOldParent = function (node) { 233 | var change = this.get(node); 234 | return change ? change.getOldParent() : node.parentNode; 235 | }; 236 | TreeChanges.prototype.getIsReachable = function (node) { 237 | if (node === this.rootNode) 238 | return true; 239 | if (!node) 240 | return false; 241 | this.reachableCache = this.reachableCache || new NodeMap(); 242 | var isReachable = this.reachableCache.get(node); 243 | if (isReachable === undefined) { 244 | isReachable = this.getIsReachable(node.parentNode); 245 | this.reachableCache.set(node, isReachable); 246 | } 247 | return isReachable; 248 | }; 249 | // A node wasReachable if its oldParent wasReachable. 250 | TreeChanges.prototype.getWasReachable = function (node) { 251 | if (node === this.rootNode) 252 | return true; 253 | if (!node) 254 | return false; 255 | this.wasReachableCache = this.wasReachableCache || new NodeMap(); 256 | var wasReachable = this.wasReachableCache.get(node); 257 | if (wasReachable === undefined) { 258 | wasReachable = this.getWasReachable(this.getOldParent(node)); 259 | this.wasReachableCache.set(node, wasReachable); 260 | } 261 | return wasReachable; 262 | }; 263 | TreeChanges.prototype.reachabilityChange = function (node) { 264 | if (this.getIsReachable(node)) { 265 | return this.getWasReachable(node) ? 266 | Movement.STAYED_IN : Movement.ENTERED; 267 | } 268 | return this.getWasReachable(node) ? 269 | Movement.EXITED : Movement.STAYED_OUT; 270 | }; 271 | return TreeChanges; 272 | })(NodeMap); 273 | var MutationProjection = (function () { 274 | // TOOD(any) 275 | function MutationProjection(rootNode, mutations, selectors, calcReordered, calcOldPreviousSibling) { 276 | this.rootNode = rootNode; 277 | this.mutations = mutations; 278 | this.selectors = selectors; 279 | this.calcReordered = calcReordered; 280 | this.calcOldPreviousSibling = calcOldPreviousSibling; 281 | this.treeChanges = new TreeChanges(rootNode, mutations); 282 | this.entered = []; 283 | this.exited = []; 284 | this.stayedIn = new NodeMap(); 285 | this.visited = new NodeMap(); 286 | this.childListChangeMap = undefined; 287 | this.characterDataOnly = undefined; 288 | this.matchCache = undefined; 289 | this.processMutations(); 290 | } 291 | MutationProjection.prototype.processMutations = function () { 292 | if (!this.treeChanges.anyParentsChanged && 293 | !this.treeChanges.anyAttributesChanged) 294 | return; 295 | var changedNodes = this.treeChanges.keys(); 296 | for (var i = 0; i < changedNodes.length; i++) { 297 | this.visitNode(changedNodes[i], undefined); 298 | } 299 | }; 300 | MutationProjection.prototype.visitNode = function (node, parentReachable) { 301 | if (this.visited.has(node)) 302 | return; 303 | this.visited.set(node, true); 304 | var change = this.treeChanges.get(node); 305 | var reachable = parentReachable; 306 | // node inherits its parent's reachability change unless 307 | // its parentNode was mutated. 308 | if ((change && change.childList) || reachable == undefined) 309 | reachable = this.treeChanges.reachabilityChange(node); 310 | if (reachable === Movement.STAYED_OUT) 311 | return; 312 | // Cache match results for sub-patterns. 313 | this.matchabilityChange(node); 314 | if (reachable === Movement.ENTERED) { 315 | this.entered.push(node); 316 | } 317 | else if (reachable === Movement.EXITED) { 318 | this.exited.push(node); 319 | this.ensureHasOldPreviousSiblingIfNeeded(node); 320 | } 321 | else if (reachable === Movement.STAYED_IN) { 322 | var movement = Movement.STAYED_IN; 323 | if (change && change.childList) { 324 | if (change.oldParentNode !== node.parentNode) { 325 | movement = Movement.REPARENTED; 326 | this.ensureHasOldPreviousSiblingIfNeeded(node); 327 | } 328 | else if (this.calcReordered && this.wasReordered(node)) { 329 | movement = Movement.REORDERED; 330 | } 331 | } 332 | this.stayedIn.set(node, movement); 333 | } 334 | if (reachable === Movement.STAYED_IN) 335 | return; 336 | // reachable === ENTERED || reachable === EXITED. 337 | for (var child = node.firstChild; child; child = child.nextSibling) { 338 | this.visitNode(child, reachable); 339 | } 340 | }; 341 | MutationProjection.prototype.ensureHasOldPreviousSiblingIfNeeded = function (node) { 342 | if (!this.calcOldPreviousSibling) 343 | return; 344 | this.processChildlistChanges(); 345 | var parentNode = node.parentNode; 346 | var nodeChange = this.treeChanges.get(node); 347 | if (nodeChange && nodeChange.oldParentNode) 348 | parentNode = nodeChange.oldParentNode; 349 | var change = this.childListChangeMap.get(parentNode); 350 | if (!change) { 351 | change = new ChildListChange(); 352 | this.childListChangeMap.set(parentNode, change); 353 | } 354 | if (!change.oldPrevious.has(node)) { 355 | change.oldPrevious.set(node, node.previousSibling); 356 | } 357 | }; 358 | MutationProjection.prototype.getChanged = function (summary, selectors, characterDataOnly) { 359 | this.selectors = selectors; 360 | this.characterDataOnly = characterDataOnly; 361 | for (var i = 0; i < this.entered.length; i++) { 362 | var node = this.entered[i]; 363 | var matchable = this.matchabilityChange(node); 364 | if (matchable === Movement.ENTERED || matchable === Movement.STAYED_IN) 365 | summary.added.push(node); 366 | } 367 | var stayedInNodes = this.stayedIn.keys(); 368 | for (var i = 0; i < stayedInNodes.length; i++) { 369 | var node = stayedInNodes[i]; 370 | var matchable = this.matchabilityChange(node); 371 | if (matchable === Movement.ENTERED) { 372 | summary.added.push(node); 373 | } 374 | else if (matchable === Movement.EXITED) { 375 | summary.removed.push(node); 376 | } 377 | else if (matchable === Movement.STAYED_IN && (summary.reparented || summary.reordered)) { 378 | var movement = this.stayedIn.get(node); 379 | if (summary.reparented && movement === Movement.REPARENTED) 380 | summary.reparented.push(node); 381 | else if (summary.reordered && movement === Movement.REORDERED) 382 | summary.reordered.push(node); 383 | } 384 | } 385 | for (var i = 0; i < this.exited.length; i++) { 386 | var node = this.exited[i]; 387 | var matchable = this.matchabilityChange(node); 388 | if (matchable === Movement.EXITED || matchable === Movement.STAYED_IN) 389 | summary.removed.push(node); 390 | } 391 | }; 392 | MutationProjection.prototype.getOldParentNode = function (node) { 393 | var change = this.treeChanges.get(node); 394 | if (change && change.childList) 395 | return change.oldParentNode ? change.oldParentNode : null; 396 | var reachabilityChange = this.treeChanges.reachabilityChange(node); 397 | if (reachabilityChange === Movement.STAYED_OUT || reachabilityChange === Movement.ENTERED) 398 | throw Error('getOldParentNode requested on invalid node.'); 399 | return node.parentNode; 400 | }; 401 | MutationProjection.prototype.getOldPreviousSibling = function (node) { 402 | var parentNode = node.parentNode; 403 | var nodeChange = this.treeChanges.get(node); 404 | if (nodeChange && nodeChange.oldParentNode) 405 | parentNode = nodeChange.oldParentNode; 406 | var change = this.childListChangeMap.get(parentNode); 407 | if (!change) 408 | throw Error('getOldPreviousSibling requested on invalid node.'); 409 | return change.oldPrevious.get(node); 410 | }; 411 | MutationProjection.prototype.getOldAttribute = function (element, attrName) { 412 | var change = this.treeChanges.get(element); 413 | if (!change || !change.attributes) 414 | throw Error('getOldAttribute requested on invalid node.'); 415 | var value = change.getAttributeOldValue(attrName); 416 | if (value === undefined) 417 | throw Error('getOldAttribute requested for unchanged attribute name.'); 418 | return value; 419 | }; 420 | MutationProjection.prototype.attributeChangedNodes = function (includeAttributes) { 421 | if (!this.treeChanges.anyAttributesChanged) 422 | return {}; // No attributes mutations occurred. 423 | var attributeFilter; 424 | var caseInsensitiveFilter; 425 | if (includeAttributes) { 426 | attributeFilter = {}; 427 | caseInsensitiveFilter = {}; 428 | for (var i = 0; i < includeAttributes.length; i++) { 429 | var attrName = includeAttributes[i]; 430 | attributeFilter[attrName] = true; 431 | caseInsensitiveFilter[attrName.toLowerCase()] = attrName; 432 | } 433 | } 434 | var result = {}; 435 | var nodes = this.treeChanges.keys(); 436 | for (var i = 0; i < nodes.length; i++) { 437 | var node = nodes[i]; 438 | var change = this.treeChanges.get(node); 439 | if (!change.attributes) 440 | continue; 441 | if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(node) || 442 | Movement.STAYED_IN !== this.matchabilityChange(node)) { 443 | continue; 444 | } 445 | var element = node; 446 | var changedAttrNames = change.getAttributeNamesMutated(); 447 | for (var j = 0; j < changedAttrNames.length; j++) { 448 | var attrName = changedAttrNames[j]; 449 | if (attributeFilter && 450 | !attributeFilter[attrName] && 451 | !(change.isCaseInsensitive && caseInsensitiveFilter[attrName])) { 452 | continue; 453 | } 454 | var oldValue = change.getAttributeOldValue(attrName); 455 | if (oldValue === element.getAttribute(attrName)) 456 | continue; 457 | if (caseInsensitiveFilter && change.isCaseInsensitive) 458 | attrName = caseInsensitiveFilter[attrName]; 459 | result[attrName] = result[attrName] || []; 460 | result[attrName].push(element); 461 | } 462 | } 463 | return result; 464 | }; 465 | MutationProjection.prototype.getOldCharacterData = function (node) { 466 | var change = this.treeChanges.get(node); 467 | if (!change || !change.characterData) 468 | throw Error('getOldCharacterData requested on invalid node.'); 469 | return change.characterDataOldValue; 470 | }; 471 | MutationProjection.prototype.getCharacterDataChanged = function () { 472 | if (!this.treeChanges.anyCharacterDataChanged) 473 | return []; // No characterData mutations occurred. 474 | var nodes = this.treeChanges.keys(); 475 | var result = []; 476 | for (var i = 0; i < nodes.length; i++) { 477 | var target = nodes[i]; 478 | if (Movement.STAYED_IN !== this.treeChanges.reachabilityChange(target)) 479 | continue; 480 | var change = this.treeChanges.get(target); 481 | if (!change.characterData || 482 | target.textContent == change.characterDataOldValue) 483 | continue; 484 | result.push(target); 485 | } 486 | return result; 487 | }; 488 | MutationProjection.prototype.computeMatchabilityChange = function (selector, el) { 489 | if (!this.matchCache) 490 | this.matchCache = []; 491 | if (!this.matchCache[selector.uid]) 492 | this.matchCache[selector.uid] = new NodeMap(); 493 | var cache = this.matchCache[selector.uid]; 494 | var result = cache.get(el); 495 | if (result === undefined) { 496 | result = selector.matchabilityChange(el, this.treeChanges.get(el)); 497 | cache.set(el, result); 498 | } 499 | return result; 500 | }; 501 | MutationProjection.prototype.matchabilityChange = function (node) { 502 | var _this = this; 503 | // TODO(rafaelw): Include PI, CDATA? 504 | // Only include text nodes. 505 | if (this.characterDataOnly) { 506 | switch (node.nodeType) { 507 | case Node.COMMENT_NODE: 508 | case Node.TEXT_NODE: 509 | return Movement.STAYED_IN; 510 | default: 511 | return Movement.STAYED_OUT; 512 | } 513 | } 514 | // No element filter. Include all nodes. 515 | if (!this.selectors) 516 | return Movement.STAYED_IN; 517 | // Element filter. Exclude non-elements. 518 | if (node.nodeType !== Node.ELEMENT_NODE) 519 | return Movement.STAYED_OUT; 520 | var el = node; 521 | var matchChanges = this.selectors.map(function (selector) { 522 | return _this.computeMatchabilityChange(selector, el); 523 | }); 524 | var accum = Movement.STAYED_OUT; 525 | var i = 0; 526 | while (accum !== Movement.STAYED_IN && i < matchChanges.length) { 527 | switch (matchChanges[i]) { 528 | case Movement.STAYED_IN: 529 | accum = Movement.STAYED_IN; 530 | break; 531 | case Movement.ENTERED: 532 | if (accum === Movement.EXITED) 533 | accum = Movement.STAYED_IN; 534 | else 535 | accum = Movement.ENTERED; 536 | break; 537 | case Movement.EXITED: 538 | if (accum === Movement.ENTERED) 539 | accum = Movement.STAYED_IN; 540 | else 541 | accum = Movement.EXITED; 542 | break; 543 | } 544 | i++; 545 | } 546 | return accum; 547 | }; 548 | MutationProjection.prototype.getChildlistChange = function (el) { 549 | var change = this.childListChangeMap.get(el); 550 | if (!change) { 551 | change = new ChildListChange(); 552 | this.childListChangeMap.set(el, change); 553 | } 554 | return change; 555 | }; 556 | MutationProjection.prototype.processChildlistChanges = function () { 557 | if (this.childListChangeMap) 558 | return; 559 | this.childListChangeMap = new NodeMap(); 560 | for (var i = 0; i < this.mutations.length; i++) { 561 | var mutation = this.mutations[i]; 562 | if (mutation.type != 'childList') 563 | continue; 564 | if (this.treeChanges.reachabilityChange(mutation.target) !== Movement.STAYED_IN && 565 | !this.calcOldPreviousSibling) 566 | continue; 567 | var change = this.getChildlistChange(mutation.target); 568 | var oldPrevious = mutation.previousSibling; 569 | function recordOldPrevious(node, previous) { 570 | if (!node || 571 | change.oldPrevious.has(node) || 572 | change.added.has(node) || 573 | change.maybeMoved.has(node)) 574 | return; 575 | if (previous && 576 | (change.added.has(previous) || 577 | change.maybeMoved.has(previous))) 578 | return; 579 | change.oldPrevious.set(node, previous); 580 | } 581 | for (var j = 0; j < mutation.removedNodes.length; j++) { 582 | var node = mutation.removedNodes[j]; 583 | recordOldPrevious(node, oldPrevious); 584 | if (change.added.has(node)) { 585 | change.added.delete(node); 586 | } 587 | else { 588 | change.removed.set(node, true); 589 | change.maybeMoved.delete(node); 590 | } 591 | oldPrevious = node; 592 | } 593 | recordOldPrevious(mutation.nextSibling, oldPrevious); 594 | for (var j = 0; j < mutation.addedNodes.length; j++) { 595 | var node = mutation.addedNodes[j]; 596 | if (change.removed.has(node)) { 597 | change.removed.delete(node); 598 | change.maybeMoved.set(node, true); 599 | } 600 | else { 601 | change.added.set(node, true); 602 | } 603 | } 604 | } 605 | }; 606 | MutationProjection.prototype.wasReordered = function (node) { 607 | if (!this.treeChanges.anyParentsChanged) 608 | return false; 609 | this.processChildlistChanges(); 610 | var parentNode = node.parentNode; 611 | var nodeChange = this.treeChanges.get(node); 612 | if (nodeChange && nodeChange.oldParentNode) 613 | parentNode = nodeChange.oldParentNode; 614 | var change = this.childListChangeMap.get(parentNode); 615 | if (!change) 616 | return false; 617 | if (change.moved) 618 | return change.moved.get(node); 619 | change.moved = new NodeMap(); 620 | var pendingMoveDecision = new NodeMap(); 621 | function isMoved(node) { 622 | if (!node) 623 | return false; 624 | if (!change.maybeMoved.has(node)) 625 | return false; 626 | var didMove = change.moved.get(node); 627 | if (didMove !== undefined) 628 | return didMove; 629 | if (pendingMoveDecision.has(node)) { 630 | didMove = true; 631 | } 632 | else { 633 | pendingMoveDecision.set(node, true); 634 | didMove = getPrevious(node) !== getOldPrevious(node); 635 | } 636 | if (pendingMoveDecision.has(node)) { 637 | pendingMoveDecision.delete(node); 638 | change.moved.set(node, didMove); 639 | } 640 | else { 641 | didMove = change.moved.get(node); 642 | } 643 | return didMove; 644 | } 645 | var oldPreviousCache = new NodeMap(); 646 | function getOldPrevious(node) { 647 | var oldPrevious = oldPreviousCache.get(node); 648 | if (oldPrevious !== undefined) 649 | return oldPrevious; 650 | oldPrevious = change.oldPrevious.get(node); 651 | while (oldPrevious && 652 | (change.removed.has(oldPrevious) || isMoved(oldPrevious))) { 653 | oldPrevious = getOldPrevious(oldPrevious); 654 | } 655 | if (oldPrevious === undefined) 656 | oldPrevious = node.previousSibling; 657 | oldPreviousCache.set(node, oldPrevious); 658 | return oldPrevious; 659 | } 660 | var previousCache = new NodeMap(); 661 | function getPrevious(node) { 662 | if (previousCache.has(node)) 663 | return previousCache.get(node); 664 | var previous = node.previousSibling; 665 | while (previous && (change.added.has(previous) || isMoved(previous))) 666 | previous = previous.previousSibling; 667 | previousCache.set(node, previous); 668 | return previous; 669 | } 670 | change.maybeMoved.keys().forEach(isMoved); 671 | return change.moved.get(node); 672 | }; 673 | return MutationProjection; 674 | })(); 675 | var Summary = (function () { 676 | function Summary(projection, query) { 677 | var _this = this; 678 | this.projection = projection; 679 | this.added = []; 680 | this.removed = []; 681 | this.reparented = query.all || query.element || query.characterData ? [] : undefined; 682 | this.reordered = query.all ? [] : undefined; 683 | projection.getChanged(this, query.elementFilter, query.characterData); 684 | if (query.all || query.attribute || query.attributeList) { 685 | var filter = query.attribute ? [query.attribute] : query.attributeList; 686 | var attributeChanged = projection.attributeChangedNodes(filter); 687 | if (query.attribute) { 688 | this.valueChanged = attributeChanged[query.attribute] || []; 689 | } 690 | else { 691 | this.attributeChanged = attributeChanged; 692 | if (query.attributeList) { 693 | query.attributeList.forEach(function (attrName) { 694 | if (!_this.attributeChanged.hasOwnProperty(attrName)) 695 | _this.attributeChanged[attrName] = []; 696 | }); 697 | } 698 | } 699 | } 700 | if (query.all || query.characterData) { 701 | var characterDataChanged = projection.getCharacterDataChanged(); 702 | if (query.characterData) 703 | this.valueChanged = characterDataChanged; 704 | else 705 | this.characterDataChanged = characterDataChanged; 706 | } 707 | if (this.reordered) 708 | this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection); 709 | } 710 | Summary.prototype.getOldParentNode = function (node) { 711 | return this.projection.getOldParentNode(node); 712 | }; 713 | Summary.prototype.getOldAttribute = function (node, name) { 714 | return this.projection.getOldAttribute(node, name); 715 | }; 716 | Summary.prototype.getOldCharacterData = function (node) { 717 | return this.projection.getOldCharacterData(node); 718 | }; 719 | Summary.prototype.getOldPreviousSibling = function (node) { 720 | return this.projection.getOldPreviousSibling(node); 721 | }; 722 | return Summary; 723 | })(); 724 | // TODO(rafaelw): Allow ':' and '.' as valid name characters. 725 | var validNameInitialChar = /[a-zA-Z_]+/; 726 | var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/; 727 | // TODO(rafaelw): Consider allowing backslash in the attrValue. 728 | // TODO(rafaelw): There's got a to be way to represent this state machine 729 | // more compactly??? 730 | function escapeQuotes(value) { 731 | return '"' + value.replace(/"/, '\\\"') + '"'; 732 | } 733 | var Qualifier = (function () { 734 | function Qualifier() { 735 | } 736 | Qualifier.prototype.matches = function (oldValue) { 737 | if (oldValue === null) 738 | return false; 739 | if (this.attrValue === undefined) 740 | return true; 741 | if (!this.contains) 742 | return this.attrValue == oldValue; 743 | var tokens = oldValue.split(' '); 744 | for (var i = 0; i < tokens.length; i++) { 745 | if (this.attrValue === tokens[i]) 746 | return true; 747 | } 748 | return false; 749 | }; 750 | Qualifier.prototype.toString = function () { 751 | if (this.attrName === 'class' && this.contains) 752 | return '.' + this.attrValue; 753 | if (this.attrName === 'id' && !this.contains) 754 | return '#' + this.attrValue; 755 | if (this.contains) 756 | return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']'; 757 | if ('attrValue' in this) 758 | return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']'; 759 | return '[' + this.attrName + ']'; 760 | }; 761 | return Qualifier; 762 | })(); 763 | var Selector = (function () { 764 | function Selector() { 765 | this.uid = Selector.nextUid++; 766 | this.qualifiers = []; 767 | } 768 | Object.defineProperty(Selector.prototype, "caseInsensitiveTagName", { 769 | get: function () { 770 | return this.tagName.toUpperCase(); 771 | }, 772 | enumerable: true, 773 | configurable: true 774 | }); 775 | Object.defineProperty(Selector.prototype, "selectorString", { 776 | get: function () { 777 | return this.tagName + this.qualifiers.join(''); 778 | }, 779 | enumerable: true, 780 | configurable: true 781 | }); 782 | Selector.prototype.isMatching = function (el) { 783 | return el[Selector.matchesSelector](this.selectorString); 784 | }; 785 | Selector.prototype.wasMatching = function (el, change, isMatching) { 786 | if (!change || !change.attributes) 787 | return isMatching; 788 | var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName; 789 | if (tagName !== '*' && tagName !== el.tagName) 790 | return false; 791 | var attributeOldValues = []; 792 | var anyChanged = false; 793 | for (var i = 0; i < this.qualifiers.length; i++) { 794 | var qualifier = this.qualifiers[i]; 795 | var oldValue = change.getAttributeOldValue(qualifier.attrName); 796 | attributeOldValues.push(oldValue); 797 | anyChanged = anyChanged || (oldValue !== undefined); 798 | } 799 | if (!anyChanged) 800 | return isMatching; 801 | for (var i = 0; i < this.qualifiers.length; i++) { 802 | var qualifier = this.qualifiers[i]; 803 | var oldValue = attributeOldValues[i]; 804 | if (oldValue === undefined) 805 | oldValue = el.getAttribute(qualifier.attrName); 806 | if (!qualifier.matches(oldValue)) 807 | return false; 808 | } 809 | return true; 810 | }; 811 | Selector.prototype.matchabilityChange = function (el, change) { 812 | var isMatching = this.isMatching(el); 813 | if (isMatching) 814 | return this.wasMatching(el, change, isMatching) ? Movement.STAYED_IN : Movement.ENTERED; 815 | else 816 | return this.wasMatching(el, change, isMatching) ? Movement.EXITED : Movement.STAYED_OUT; 817 | }; 818 | Selector.parseSelectors = function (input) { 819 | var selectors = []; 820 | var currentSelector; 821 | var currentQualifier; 822 | function newSelector() { 823 | if (currentSelector) { 824 | if (currentQualifier) { 825 | currentSelector.qualifiers.push(currentQualifier); 826 | currentQualifier = undefined; 827 | } 828 | selectors.push(currentSelector); 829 | } 830 | currentSelector = new Selector(); 831 | } 832 | function newQualifier() { 833 | if (currentQualifier) 834 | currentSelector.qualifiers.push(currentQualifier); 835 | currentQualifier = new Qualifier(); 836 | } 837 | var WHITESPACE = /\s/; 838 | var valueQuoteChar; 839 | var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.'; 840 | var SELECTOR = 1; 841 | var TAG_NAME = 2; 842 | var QUALIFIER = 3; 843 | var QUALIFIER_NAME_FIRST_CHAR = 4; 844 | var QUALIFIER_NAME = 5; 845 | var ATTR_NAME_FIRST_CHAR = 6; 846 | var ATTR_NAME = 7; 847 | var EQUIV_OR_ATTR_QUAL_END = 8; 848 | var EQUAL = 9; 849 | var ATTR_QUAL_END = 10; 850 | var VALUE_FIRST_CHAR = 11; 851 | var VALUE = 12; 852 | var QUOTED_VALUE = 13; 853 | var SELECTOR_SEPARATOR = 14; 854 | var state = SELECTOR; 855 | var i = 0; 856 | while (i < input.length) { 857 | var c = input[i++]; 858 | switch (state) { 859 | case SELECTOR: 860 | if (c.match(validNameInitialChar)) { 861 | newSelector(); 862 | currentSelector.tagName = c; 863 | state = TAG_NAME; 864 | break; 865 | } 866 | if (c == '*') { 867 | newSelector(); 868 | currentSelector.tagName = '*'; 869 | state = QUALIFIER; 870 | break; 871 | } 872 | if (c == '.') { 873 | newSelector(); 874 | newQualifier(); 875 | currentSelector.tagName = '*'; 876 | currentQualifier.attrName = 'class'; 877 | currentQualifier.contains = true; 878 | state = QUALIFIER_NAME_FIRST_CHAR; 879 | break; 880 | } 881 | if (c == '#') { 882 | newSelector(); 883 | newQualifier(); 884 | currentSelector.tagName = '*'; 885 | currentQualifier.attrName = 'id'; 886 | state = QUALIFIER_NAME_FIRST_CHAR; 887 | break; 888 | } 889 | if (c == '[') { 890 | newSelector(); 891 | newQualifier(); 892 | currentSelector.tagName = '*'; 893 | currentQualifier.attrName = ''; 894 | state = ATTR_NAME_FIRST_CHAR; 895 | break; 896 | } 897 | if (c.match(WHITESPACE)) 898 | break; 899 | throw Error(SYNTAX_ERROR); 900 | case TAG_NAME: 901 | if (c.match(validNameNonInitialChar)) { 902 | currentSelector.tagName += c; 903 | break; 904 | } 905 | if (c == '.') { 906 | newQualifier(); 907 | currentQualifier.attrName = 'class'; 908 | currentQualifier.contains = true; 909 | state = QUALIFIER_NAME_FIRST_CHAR; 910 | break; 911 | } 912 | if (c == '#') { 913 | newQualifier(); 914 | currentQualifier.attrName = 'id'; 915 | state = QUALIFIER_NAME_FIRST_CHAR; 916 | break; 917 | } 918 | if (c == '[') { 919 | newQualifier(); 920 | currentQualifier.attrName = ''; 921 | state = ATTR_NAME_FIRST_CHAR; 922 | break; 923 | } 924 | if (c.match(WHITESPACE)) { 925 | state = SELECTOR_SEPARATOR; 926 | break; 927 | } 928 | if (c == ',') { 929 | state = SELECTOR; 930 | break; 931 | } 932 | throw Error(SYNTAX_ERROR); 933 | case QUALIFIER: 934 | if (c == '.') { 935 | newQualifier(); 936 | currentQualifier.attrName = 'class'; 937 | currentQualifier.contains = true; 938 | state = QUALIFIER_NAME_FIRST_CHAR; 939 | break; 940 | } 941 | if (c == '#') { 942 | newQualifier(); 943 | currentQualifier.attrName = 'id'; 944 | state = QUALIFIER_NAME_FIRST_CHAR; 945 | break; 946 | } 947 | if (c == '[') { 948 | newQualifier(); 949 | currentQualifier.attrName = ''; 950 | state = ATTR_NAME_FIRST_CHAR; 951 | break; 952 | } 953 | if (c.match(WHITESPACE)) { 954 | state = SELECTOR_SEPARATOR; 955 | break; 956 | } 957 | if (c == ',') { 958 | state = SELECTOR; 959 | break; 960 | } 961 | throw Error(SYNTAX_ERROR); 962 | case QUALIFIER_NAME_FIRST_CHAR: 963 | if (c.match(validNameInitialChar)) { 964 | currentQualifier.attrValue = c; 965 | state = QUALIFIER_NAME; 966 | break; 967 | } 968 | throw Error(SYNTAX_ERROR); 969 | case QUALIFIER_NAME: 970 | if (c.match(validNameNonInitialChar)) { 971 | currentQualifier.attrValue += c; 972 | break; 973 | } 974 | if (c == '.') { 975 | newQualifier(); 976 | currentQualifier.attrName = 'class'; 977 | currentQualifier.contains = true; 978 | state = QUALIFIER_NAME_FIRST_CHAR; 979 | break; 980 | } 981 | if (c == '#') { 982 | newQualifier(); 983 | currentQualifier.attrName = 'id'; 984 | state = QUALIFIER_NAME_FIRST_CHAR; 985 | break; 986 | } 987 | if (c == '[') { 988 | newQualifier(); 989 | state = ATTR_NAME_FIRST_CHAR; 990 | break; 991 | } 992 | if (c.match(WHITESPACE)) { 993 | state = SELECTOR_SEPARATOR; 994 | break; 995 | } 996 | if (c == ',') { 997 | state = SELECTOR; 998 | break; 999 | } 1000 | throw Error(SYNTAX_ERROR); 1001 | case ATTR_NAME_FIRST_CHAR: 1002 | if (c.match(validNameInitialChar)) { 1003 | currentQualifier.attrName = c; 1004 | state = ATTR_NAME; 1005 | break; 1006 | } 1007 | if (c.match(WHITESPACE)) 1008 | break; 1009 | throw Error(SYNTAX_ERROR); 1010 | case ATTR_NAME: 1011 | if (c.match(validNameNonInitialChar)) { 1012 | currentQualifier.attrName += c; 1013 | break; 1014 | } 1015 | if (c.match(WHITESPACE)) { 1016 | state = EQUIV_OR_ATTR_QUAL_END; 1017 | break; 1018 | } 1019 | if (c == '~') { 1020 | currentQualifier.contains = true; 1021 | state = EQUAL; 1022 | break; 1023 | } 1024 | if (c == '=') { 1025 | currentQualifier.attrValue = ''; 1026 | state = VALUE_FIRST_CHAR; 1027 | break; 1028 | } 1029 | if (c == ']') { 1030 | state = QUALIFIER; 1031 | break; 1032 | } 1033 | throw Error(SYNTAX_ERROR); 1034 | case EQUIV_OR_ATTR_QUAL_END: 1035 | if (c == '~') { 1036 | currentQualifier.contains = true; 1037 | state = EQUAL; 1038 | break; 1039 | } 1040 | if (c == '=') { 1041 | currentQualifier.attrValue = ''; 1042 | state = VALUE_FIRST_CHAR; 1043 | break; 1044 | } 1045 | if (c == ']') { 1046 | state = QUALIFIER; 1047 | break; 1048 | } 1049 | if (c.match(WHITESPACE)) 1050 | break; 1051 | throw Error(SYNTAX_ERROR); 1052 | case EQUAL: 1053 | if (c == '=') { 1054 | currentQualifier.attrValue = ''; 1055 | state = VALUE_FIRST_CHAR; 1056 | break; 1057 | } 1058 | throw Error(SYNTAX_ERROR); 1059 | case ATTR_QUAL_END: 1060 | if (c == ']') { 1061 | state = QUALIFIER; 1062 | break; 1063 | } 1064 | if (c.match(WHITESPACE)) 1065 | break; 1066 | throw Error(SYNTAX_ERROR); 1067 | case VALUE_FIRST_CHAR: 1068 | if (c.match(WHITESPACE)) 1069 | break; 1070 | if (c == '"' || c == "'") { 1071 | valueQuoteChar = c; 1072 | state = QUOTED_VALUE; 1073 | break; 1074 | } 1075 | currentQualifier.attrValue += c; 1076 | state = VALUE; 1077 | break; 1078 | case VALUE: 1079 | if (c.match(WHITESPACE)) { 1080 | state = ATTR_QUAL_END; 1081 | break; 1082 | } 1083 | if (c == ']') { 1084 | state = QUALIFIER; 1085 | break; 1086 | } 1087 | if (c == "'" || c == '"') 1088 | throw Error(SYNTAX_ERROR); 1089 | currentQualifier.attrValue += c; 1090 | break; 1091 | case QUOTED_VALUE: 1092 | if (c == valueQuoteChar) { 1093 | state = ATTR_QUAL_END; 1094 | break; 1095 | } 1096 | currentQualifier.attrValue += c; 1097 | break; 1098 | case SELECTOR_SEPARATOR: 1099 | if (c.match(WHITESPACE)) 1100 | break; 1101 | if (c == ',') { 1102 | state = SELECTOR; 1103 | break; 1104 | } 1105 | throw Error(SYNTAX_ERROR); 1106 | } 1107 | } 1108 | switch (state) { 1109 | case SELECTOR: 1110 | case TAG_NAME: 1111 | case QUALIFIER: 1112 | case QUALIFIER_NAME: 1113 | case SELECTOR_SEPARATOR: 1114 | // Valid end states. 1115 | newSelector(); 1116 | break; 1117 | default: 1118 | throw Error(SYNTAX_ERROR); 1119 | } 1120 | if (!selectors.length) 1121 | throw Error(SYNTAX_ERROR); 1122 | return selectors; 1123 | }; 1124 | Selector.nextUid = 1; 1125 | Selector.matchesSelector = (function () { 1126 | var element = document.createElement('div'); 1127 | if (typeof element['webkitMatchesSelector'] === 'function') 1128 | return 'webkitMatchesSelector'; 1129 | if (typeof element['mozMatchesSelector'] === 'function') 1130 | return 'mozMatchesSelector'; 1131 | if (typeof element['msMatchesSelector'] === 'function') 1132 | return 'msMatchesSelector'; 1133 | return 'matchesSelector'; 1134 | })(); 1135 | return Selector; 1136 | })(); 1137 | var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/; 1138 | function validateAttribute(attribute) { 1139 | if (typeof attribute != 'string') 1140 | throw Error('Invalid request opion. attribute must be a non-zero length string.'); 1141 | attribute = attribute.trim(); 1142 | if (!attribute) 1143 | throw Error('Invalid request opion. attribute must be a non-zero length string.'); 1144 | if (!attribute.match(attributeFilterPattern)) 1145 | throw Error('Invalid request option. invalid attribute name: ' + attribute); 1146 | return attribute; 1147 | } 1148 | function validateElementAttributes(attribs) { 1149 | if (!attribs.trim().length) 1150 | throw Error('Invalid request option: elementAttributes must contain at least one attribute.'); 1151 | var lowerAttributes = {}; 1152 | var attributes = {}; 1153 | var tokens = attribs.split(/\s+/); 1154 | for (var i = 0; i < tokens.length; i++) { 1155 | var name = tokens[i]; 1156 | if (!name) 1157 | continue; 1158 | var name = validateAttribute(name); 1159 | var nameLower = name.toLowerCase(); 1160 | if (lowerAttributes[nameLower]) 1161 | throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.'); 1162 | attributes[name] = true; 1163 | lowerAttributes[nameLower] = true; 1164 | } 1165 | return Object.keys(attributes); 1166 | } 1167 | function elementFilterAttributes(selectors) { 1168 | var attributes = {}; 1169 | selectors.forEach(function (selector) { 1170 | selector.qualifiers.forEach(function (qualifier) { 1171 | attributes[qualifier.attrName] = true; 1172 | }); 1173 | }); 1174 | return Object.keys(attributes); 1175 | } 1176 | var MutationSummary = (function () { 1177 | function MutationSummary(opts) { 1178 | var _this = this; 1179 | this.connected = false; 1180 | this.options = MutationSummary.validateOptions(opts); 1181 | this.observerOptions = MutationSummary.createObserverOptions(this.options.queries); 1182 | this.root = this.options.rootNode; 1183 | this.callback = this.options.callback; 1184 | this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map(function (query) { 1185 | return query.elementFilter ? query.elementFilter : []; 1186 | })); 1187 | if (!this.elementFilter.length) 1188 | this.elementFilter = undefined; 1189 | this.calcReordered = this.options.queries.some(function (query) { 1190 | return query.all; 1191 | }); 1192 | this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this. 1193 | if (MutationSummary.createQueryValidator) { 1194 | this.queryValidators = this.options.queries.map(function (query) { 1195 | return MutationSummary.createQueryValidator(_this.root, query); 1196 | }); 1197 | } 1198 | this.observer = new MutationObserverCtor(function (mutations) { 1199 | _this.observerCallback(mutations); 1200 | }); 1201 | this.reconnect(); 1202 | } 1203 | MutationSummary.createObserverOptions = function (queries) { 1204 | var observerOptions = { 1205 | childList: true, 1206 | subtree: true 1207 | }; 1208 | var attributeFilter; 1209 | function observeAttributes(attributes) { 1210 | if (observerOptions.attributes && !attributeFilter) 1211 | return; // already observing all. 1212 | observerOptions.attributes = true; 1213 | observerOptions.attributeOldValue = true; 1214 | if (!attributes) { 1215 | // observe all. 1216 | attributeFilter = undefined; 1217 | return; 1218 | } 1219 | // add to observed. 1220 | attributeFilter = attributeFilter || {}; 1221 | attributes.forEach(function (attribute) { 1222 | attributeFilter[attribute] = true; 1223 | attributeFilter[attribute.toLowerCase()] = true; 1224 | }); 1225 | } 1226 | queries.forEach(function (query) { 1227 | if (query.characterData) { 1228 | observerOptions.characterData = true; 1229 | observerOptions.characterDataOldValue = true; 1230 | return; 1231 | } 1232 | if (query.all) { 1233 | observeAttributes(); 1234 | observerOptions.characterData = true; 1235 | observerOptions.characterDataOldValue = true; 1236 | return; 1237 | } 1238 | if (query.attribute) { 1239 | observeAttributes([query.attribute.trim()]); 1240 | return; 1241 | } 1242 | var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []); 1243 | if (attributes.length) 1244 | observeAttributes(attributes); 1245 | }); 1246 | if (attributeFilter) 1247 | observerOptions.attributeFilter = Object.keys(attributeFilter); 1248 | return observerOptions; 1249 | }; 1250 | MutationSummary.validateOptions = function (options) { 1251 | for (var prop in options) { 1252 | if (!(prop in MutationSummary.optionKeys)) 1253 | throw Error('Invalid option: ' + prop); 1254 | } 1255 | if (typeof options.callback !== 'function') 1256 | throw Error('Invalid options: callback is required and must be a function'); 1257 | if (!options.queries || !options.queries.length) 1258 | throw Error('Invalid options: queries must contain at least one query request object.'); 1259 | var opts = { 1260 | callback: options.callback, 1261 | rootNode: options.rootNode || document, 1262 | observeOwnChanges: !!options.observeOwnChanges, 1263 | oldPreviousSibling: !!options.oldPreviousSibling, 1264 | queries: [] 1265 | }; 1266 | for (var i = 0; i < options.queries.length; i++) { 1267 | var request = options.queries[i]; 1268 | // all 1269 | if (request.all) { 1270 | if (Object.keys(request).length > 1) 1271 | throw Error('Invalid request option. all has no options.'); 1272 | opts.queries.push({ all: true }); 1273 | continue; 1274 | } 1275 | // attribute 1276 | if ('attribute' in request) { 1277 | var query = { 1278 | attribute: validateAttribute(request.attribute) 1279 | }; 1280 | query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']'); 1281 | if (Object.keys(request).length > 1) 1282 | throw Error('Invalid request option. attribute has no options.'); 1283 | opts.queries.push(query); 1284 | continue; 1285 | } 1286 | // element 1287 | if ('element' in request) { 1288 | var requestOptionCount = Object.keys(request).length; 1289 | var query = { 1290 | element: request.element, 1291 | elementFilter: Selector.parseSelectors(request.element) 1292 | }; 1293 | if (request.hasOwnProperty('elementAttributes')) { 1294 | query.attributeList = validateElementAttributes(request.elementAttributes); 1295 | requestOptionCount--; 1296 | } 1297 | if (requestOptionCount > 1) 1298 | throw Error('Invalid request option. element only allows elementAttributes option.'); 1299 | opts.queries.push(query); 1300 | continue; 1301 | } 1302 | // characterData 1303 | if (request.characterData) { 1304 | if (Object.keys(request).length > 1) 1305 | throw Error('Invalid request option. characterData has no options.'); 1306 | opts.queries.push({ characterData: true }); 1307 | continue; 1308 | } 1309 | throw Error('Invalid request option. Unknown query request.'); 1310 | } 1311 | return opts; 1312 | }; 1313 | MutationSummary.prototype.createSummaries = function (mutations) { 1314 | if (!mutations || !mutations.length) 1315 | return []; 1316 | var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling); 1317 | var summaries = []; 1318 | for (var i = 0; i < this.options.queries.length; i++) { 1319 | summaries.push(new Summary(projection, this.options.queries[i])); 1320 | } 1321 | return summaries; 1322 | }; 1323 | MutationSummary.prototype.checkpointQueryValidators = function () { 1324 | this.queryValidators.forEach(function (validator) { 1325 | if (validator) 1326 | validator.recordPreviousState(); 1327 | }); 1328 | }; 1329 | MutationSummary.prototype.runQueryValidators = function (summaries) { 1330 | this.queryValidators.forEach(function (validator, index) { 1331 | if (validator) 1332 | validator.validate(summaries[index]); 1333 | }); 1334 | }; 1335 | MutationSummary.prototype.changesToReport = function (summaries) { 1336 | return summaries.some(function (summary) { 1337 | var summaryProps = ['added', 'removed', 'reordered', 'reparented', 1338 | 'valueChanged', 'characterDataChanged']; 1339 | if (summaryProps.some(function (prop) { return summary[prop] && summary[prop].length; })) 1340 | return true; 1341 | if (summary.attributeChanged) { 1342 | var attrNames = Object.keys(summary.attributeChanged); 1343 | var attrsChanged = attrNames.some(function (attrName) { 1344 | return !!summary.attributeChanged[attrName].length; 1345 | }); 1346 | if (attrsChanged) 1347 | return true; 1348 | } 1349 | return false; 1350 | }); 1351 | }; 1352 | MutationSummary.prototype.observerCallback = function (mutations) { 1353 | if (!this.options.observeOwnChanges) 1354 | this.observer.disconnect(); 1355 | var summaries = this.createSummaries(mutations); 1356 | this.runQueryValidators(summaries); 1357 | if (this.options.observeOwnChanges) 1358 | this.checkpointQueryValidators(); 1359 | if (this.changesToReport(summaries)) 1360 | this.callback(summaries); 1361 | // disconnect() may have been called during the callback. 1362 | if (!this.options.observeOwnChanges && this.connected) { 1363 | this.checkpointQueryValidators(); 1364 | this.observer.observe(this.root, this.observerOptions); 1365 | } 1366 | }; 1367 | MutationSummary.prototype.reconnect = function () { 1368 | if (this.connected) 1369 | throw Error('Already connected'); 1370 | this.observer.observe(this.root, this.observerOptions); 1371 | this.connected = true; 1372 | this.checkpointQueryValidators(); 1373 | }; 1374 | MutationSummary.prototype.takeSummaries = function () { 1375 | if (!this.connected) 1376 | throw Error('Not connected'); 1377 | var summaries = this.createSummaries(this.observer.takeRecords()); 1378 | return this.changesToReport(summaries) ? summaries : undefined; 1379 | }; 1380 | MutationSummary.prototype.disconnect = function () { 1381 | var summaries = this.takeSummaries(); 1382 | this.observer.disconnect(); 1383 | this.connected = false; 1384 | return summaries; 1385 | }; 1386 | MutationSummary.NodeMap = NodeMap; // exposed for use in TreeMirror. 1387 | MutationSummary.parseElementFilter = Selector.parseSelectors; // exposed for testing. 1388 | MutationSummary.optionKeys = { 1389 | 'callback': true, 1390 | 'queries': true, 1391 | 'rootNode': true, 1392 | 'oldPreviousSibling': true, 1393 | 'observeOwnChanges': true 1394 | }; 1395 | return MutationSummary; 1396 | })(); -------------------------------------------------------------------------------- /public/nuclear-reset.css: -------------------------------------------------------------------------------- 1 | * { 2 | all: inital !important; 3 | } 4 | 5 | *::before, 6 | *::after { 7 | all: inital !important; 8 | } 9 | -------------------------------------------------------------------------------- /public/nuclear-reset.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function() { 2 | function unstyle() { 3 | console.log("unstyled"); 4 | var hitlist = document.querySelectorAll("style,link[rel=stylesheet]"); 5 | Array.prototype.forEach.call( hitlist, function( node ) { 6 | node.parentNode.removeChild( node ); 7 | }); 8 | 9 | var inlined = document.querySelectorAll("[style]"); 10 | Array.prototype.forEach.call( inlined, function( node ) { 11 | node.removeAttribute("style"); 12 | }); 13 | } 14 | unstyle(); 15 | 16 | function summaryUnstyle(summaries) { 17 | //style attribute 18 | summaries[0].added.forEach(function(node) { 19 | node.removeAttribute("style"); 20 | }) 21 | 22 | //style tags 23 | summaries[1].added.forEach(function(node) { 24 | node.parentNode.removeChild( node ); 25 | }) 26 | } 27 | 28 | var observer = new MutationSummary({ 29 | callback: summaryUnstyle, 30 | queries: [ 31 | { attribute: "style" }, 32 | { element: "style,link[rel=stylesheet]"} 33 | ] 34 | }); 35 | }); -------------------------------------------------------------------------------- /public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // server.js 2 | // where your node app starts 3 | 4 | // init project 5 | var express = require('express'); 6 | var request = require('request'); 7 | var concat = require('concat-stream'); 8 | var url = require('url'); 9 | var zlib = require('zlib'); 10 | 11 | 12 | var app = express(); 13 | 14 | app.use(express.static('public')); 15 | 16 | 17 | app.get("/proxy", function (req, response, next) { 18 | var headers; 19 | var write = concat(function(proxres) { 20 | try { 21 | if (!headers['content-type']) { 22 | console.log(proxres); 23 | } 24 | if ((headers['content-type']).indexOf('html') > 0) { 25 | if (headers['content-encoding']==='gzip') { 26 | proxres = zlib.gunzipSync(proxres); 27 | } 28 | var resstr = proxres.toString(); 29 | if (resstr.indexOf('') > 0) { 30 | proxres = resstr.replace('',''); 31 | // .replace('',''); 32 | } 33 | if (headers['content-encoding']==='gzip') { 34 | proxres = zlib.gzipSync(proxres); 35 | } 36 | } 37 | response.end(proxres); 38 | } catch (err) { 39 | console.log("just walk away whistling...",url); 40 | response.status(500).end(""); 41 | } 42 | }); 43 | var url = req.query.url; 44 | if (url) { 45 | if (!url.match(/^https?:\/\//)) { 46 | response.redirect("/proxy?url=http://"+url); 47 | return; 48 | } 49 | request(url) 50 | .on('response',function (proxres) { 51 | headers = proxres.headers; 52 | response.writeHead(proxres.statusCode, proxres.headers); 53 | }) 54 | .on('error', function(err) { 55 | console.log(err) 56 | response.status(500).end(""); 57 | }) 58 | .pipe(write); 59 | } else { 60 | next(); 61 | } 62 | }); 63 | 64 | 65 | app.get("/*", function (req, response, next) { 66 | var ref = req.get("Referrer") && url.parse(req.get("Referrer"),true).query.url; 67 | if (ref) { 68 | var resolved = url.resolve(ref,req.path); 69 | response.redirect("/proxy?url="+resolved); 70 | } else { 71 | next(); 72 | } 73 | }); 74 | 75 | app.get("/*", function (req, response) { 76 | response.sendFile(__dirname + '/views/enter-url.html'); 77 | }); 78 | 79 | 80 | // listen for requests :) 81 | var listener = app.listen(process.env.PORT, function () { 82 | console.log('Your app is listening on port ' + listener.address().port); 83 | }); -------------------------------------------------------------------------------- /views/enter-url.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | structure.exposed 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 |

structure.exposed

47 | 48 |
49 | 50 | 60 | 61 | 67 | 68 | 72 | Why? 73 | 74 | 75 |
76 | 77 |
78 | 79 | 89 | 90 | 91 | --------------------------------------------------------------------------------