Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Commit dce12fb

Browse files
committed
Enabled CSS Editing in most basic cases
1 parent 752be82 commit dce12fb

File tree

7 files changed

+627
-38
lines changed

7 files changed

+627
-38
lines changed

IEDiagnosticsAdapter/Resource.rc

356 Bytes
Binary file not shown.

IEDiagnosticsAdapter/WebSocketHandler.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ HRESULT WebSocketHandler::ConnectToInstance(_In_ IEInstance& instance)
431431
hr = this->InjectScript(L"browser", L"DOM.js", IDR_DOM_SCRIPT, hwnd);
432432
hr = this->InjectScript(L"browser", L"Runtime.js", IDR_RUNTIME_SCRIPT, hwnd);
433433
hr = this->InjectScript(L"browser", L"Page.js", IDR_PAGE_SCRIPT, hwnd);
434+
hr = this->InjectScript(L"browser", L"CSSParser.js", IDR_CSSPARSER_SCRIPT, hwnd);
434435

435436
// Inject script onto the debugger thread
436437
hr = this->InjectScript(L"debugger", L"Assert.js", IDR_ASSERT_SCRIPT, hwnd);

IEDiagnosticsAdapter/resource.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
#define IDR_PAGE_SCRIPT 106
1111
#define IDR_RUNTIME_SCRIPT 107
1212
#define IDR_ASSERT_SCRIPT 108
13+
#define IDR_CSSPARSER_SCRIPT 109

IEWebKitImpl/CSSParser.ts

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
//
2+
// Copyright (C) Microsoft. All rights reserved.
3+
//
4+
5+
/// <reference path="Interfaces.d.ts"/>
6+
7+
module Proxy {
8+
"use strict";
9+
10+
/**
11+
* Class that synchronously iterates through a CSS document and constructs a list of ICssMediaQuery and ICssRuleset objects.
12+
* The private members in this class store state used during the parse loop and that state is modified from
13+
* the various subroutines executed in the loop.
14+
*/
15+
export class CssParser {
16+
// A list that contains any number of ICssMediaQuery and ICssRuleset objects
17+
private _rootNodes: any[];
18+
private _text: string;
19+
20+
// Holds the index where text was last extracted from the source text
21+
private _lastCheckpoint: number;
22+
23+
// Holds a string representing the current type of CSS construct that text is being extracted for
24+
private _state: CssToken;
25+
26+
// Holds the index into the source text that the parser is currently inspecting at
27+
private _index: number;
28+
29+
// Holds components of the CSS AST that are still being constructed
30+
private _currentRuleset: ICssRuleset;
31+
private _currentDeclaration: ICssDeclaration;
32+
private _currentMediaQuery: ICssMediaQuery;
33+
34+
// Stores state about whether the loop has passed open quotes or open comments
35+
private _inComment: boolean;
36+
private _currentQuotationMark: string;
37+
private _nextCharIsEscaped: boolean;
38+
39+
constructor(text: string) {
40+
this._rootNodes = [];
41+
this._text = text;
42+
}
43+
44+
/** Returns an array containing ICssRuleset and ICssMediaQuery objects */
45+
public parseCss(): any[] {
46+
// Statement control
47+
this._inComment = false;
48+
this._currentQuotationMark = "";
49+
this._nextCharIsEscaped = false;
50+
51+
// Search maintenance state
52+
this._lastCheckpoint = 0;
53+
this._state = CssToken.Selector;
54+
55+
// Storage for under-construction nodes
56+
this._currentRuleset = null;
57+
this._currentDeclaration = null;
58+
this._currentMediaQuery = null;
59+
60+
for (this._index = 0; this._index < this._text.length; this._index++) {
61+
if (this.handleQuoteCharacter()) {
62+
} else if (this.handleCommentCharacter()) {
63+
} else if (this.handleLeadingWhitespace()) {
64+
} else if (this.handleMediaQueryStart()) {
65+
} else if (this.handleMediaQueryOpenBracket()) {
66+
} else if (this.handleMediaQueryCloseBracket()) {
67+
} else if (this.handleSelectorOpenBracket()) {
68+
} else if (this.handlePropertyColon()) {
69+
} else if (this.handleValueSemicolon()) {
70+
} else if (this.handleSelectorCloseBracket()) {
71+
}
72+
}
73+
74+
// Put any text that wasn't valid CSS into it's own node at the end of the file
75+
this.handleIncompleteBlocks();
76+
77+
return this._rootNodes;
78+
}
79+
80+
private handleMediaQueryStart(): boolean {
81+
if (this._state === CssToken.Selector && !this._currentMediaQuery &&
82+
this._lastCheckpoint >= this._index &&
83+
this._text[this._index] === "@" && this._text.substr(this._index, 7).toLowerCase() === "@media ") {
84+
this._state = CssToken.Media;
85+
return true;
86+
}
87+
88+
return false;
89+
}
90+
91+
private handleMediaQueryOpenBracket(): boolean {
92+
if (this._state === CssToken.Media && this._text[this._index] === "{") {
93+
var mediaText = this._text.substring(this._lastCheckpoint, this._index);
94+
this._currentMediaQuery = { originalOffset: this._lastCheckpoint, query: mediaText, rulesets: [] };
95+
96+
this._lastCheckpoint = this._index + 1;
97+
this._state = CssToken.Selector;
98+
99+
return true;
100+
}
101+
102+
return false;
103+
}
104+
105+
private handleMediaQueryCloseBracket(): boolean {
106+
if (this._state === CssToken.Selector && this._text[this._index] === "}" && this._currentMediaQuery) {
107+
this._lastCheckpoint = this._index + 1;
108+
this._state = CssToken.Selector;
109+
this._currentMediaQuery.endOffset = this._index + 1;
110+
this._rootNodes.push(this._currentMediaQuery);
111+
this._currentMediaQuery = null;
112+
113+
return true;
114+
}
115+
116+
return false;
117+
}
118+
119+
private handleSelectorOpenBracket(): boolean {
120+
if (this._state === CssToken.Selector && this._text[this._index] === "{") {
121+
var selectorText = this._text.substring(this._lastCheckpoint, this._index);
122+
this._currentRuleset = { originalOffset: this._lastCheckpoint, selector: selectorText, declarations: [] };
123+
124+
this._lastCheckpoint = this._index + 1;
125+
this._state = CssToken.Property;
126+
127+
return true;
128+
}
129+
130+
return false;
131+
}
132+
133+
private handlePropertyColon(): boolean {
134+
if (this._state === CssToken.Property && this._text[this._index] === ":") {
135+
var propertyText = this._text.substring(this._lastCheckpoint, this._index);
136+
this._currentDeclaration = { originalOffset: this._lastCheckpoint, property: propertyText, value: "" };
137+
138+
this._lastCheckpoint = this._index + 1;
139+
this._state = CssToken.Value;
140+
141+
return true;
142+
}
143+
144+
return false;
145+
}
146+
147+
private handleValueSemicolon(): boolean {
148+
if (this._state === CssToken.Value && this._text[this._index] === ";") {
149+
var valueText = this._text.substring(this._lastCheckpoint, this._index);
150+
this._currentDeclaration.value = valueText;
151+
this._currentDeclaration.endOffset = this._index + 1;
152+
this._currentRuleset.declarations.push(this._currentDeclaration);
153+
this._currentDeclaration = null;
154+
155+
this._lastCheckpoint = this._index + 1;
156+
this._state = CssToken.Property;
157+
158+
return true;
159+
}
160+
161+
return false;
162+
}
163+
164+
private handleSelectorCloseBracket(): boolean {
165+
if (this._text[this._index] === "}") {
166+
if (this._state === CssToken.Property) {
167+
var incompleteDeclaration: ICssDeclaration = { originalOffset: this._lastCheckpoint, endOffset: this._index, property: this._text.substring(this._lastCheckpoint, this._index), value: null };
168+
if (incompleteDeclaration.property.trim()) {
169+
this._currentRuleset.declarations.push(incompleteDeclaration);
170+
}
171+
172+
this._lastCheckpoint = this._index + 1;
173+
this._state = CssToken.Selector;
174+
175+
if (this._currentMediaQuery) {
176+
this._currentMediaQuery.endOffset = this._index;
177+
this._currentMediaQuery.rulesets.push(this._currentRuleset);
178+
} else {
179+
this._currentRuleset.endOffset = this._index;
180+
this._rootNodes.push(this._currentRuleset);
181+
}
182+
183+
this._currentRuleset = null;
184+
return true;
185+
}
186+
187+
if (this._state === CssToken.Value) { // No closing semicolon, which is valid syntax
188+
var valueText = this._text.substring(this._lastCheckpoint, this._index);
189+
this._currentDeclaration.value = valueText;
190+
this._currentDeclaration.isMissingSemicolon = true;
191+
this._currentDeclaration.endOffset = this._index;
192+
this._currentRuleset.declarations.push(this._currentDeclaration);
193+
this._currentRuleset.endOffset = this._index;
194+
this._currentDeclaration = null;
195+
196+
this._lastCheckpoint = this._index + 1;
197+
this._state = CssToken.Selector;
198+
199+
if (this._currentMediaQuery) {
200+
this._currentMediaQuery.rulesets.push(this._currentRuleset);
201+
} else {
202+
this._rootNodes.push(this._currentRuleset);
203+
}
204+
205+
this._currentRuleset = null;
206+
return true;
207+
}
208+
}
209+
210+
return false;
211+
}
212+
213+
private handleIncompleteBlocks(): void {
214+
if (this._currentMediaQuery) {
215+
this._lastCheckpoint = this._currentMediaQuery.originalOffset;
216+
} else if (this._currentRuleset) {
217+
this._lastCheckpoint = this._currentRuleset.originalOffset;
218+
}
219+
220+
if (this._lastCheckpoint < this._text.length - 1) {
221+
var textNode: ICssRuleset = { selector: this._text.substr(this._lastCheckpoint), originalOffset: this._lastCheckpoint, declarations: null, endOffset: this._index + 1};
222+
this._rootNodes.push(textNode);
223+
}
224+
}
225+
226+
private handleCommentCharacter(): boolean {
227+
if (this._text.substr(this._index, 2) === "/*") {
228+
var endOfCommentIndex = this._text.indexOf("*/", this._index);
229+
if (endOfCommentIndex === -1) {
230+
endOfCommentIndex = this._text.length;
231+
}
232+
233+
if (this._state === CssToken.Property && !this._text.substring(this._lastCheckpoint, this._index).trim()) {
234+
// this case is a disabled property
235+
var colonIndex = this._text.indexOf(":", this._index);
236+
if (colonIndex === -1 || colonIndex > endOfCommentIndex) {
237+
Assert.fail("this is not a disabled property, hanlde this case later");
238+
}
239+
240+
var propertyText = this._text.substring(this._index + 2, colonIndex);
241+
242+
var semiColonIndex = this._text.indexOf(";", this._index);
243+
if (semiColonIndex === -1 || semiColonIndex >= endOfCommentIndex) {
244+
var valueText = this._text.substring(colonIndex + 1, endOfCommentIndex);
245+
} else {
246+
var valueText = this._text.substring(colonIndex + 1, semiColonIndex);
247+
}
248+
249+
this._currentDeclaration = { originalOffset: this._lastCheckpoint, property: propertyText, value: valueText };
250+
251+
this._currentDeclaration.isDisabled = true;
252+
this._currentDeclaration.disabledFullText = this._text.substring(this._index, endOfCommentIndex + 2);
253+
254+
this._index = endOfCommentIndex + "*/".length - 1; // Adjust -1 because the loop will increment index by 1
255+
this._currentDeclaration.endOffset = this._index + 1;
256+
257+
this._currentRuleset.declarations.push(this._currentDeclaration);
258+
this._currentDeclaration = null;
259+
260+
this._lastCheckpoint = this._index + 1;
261+
} else {
262+
// This case is for normal comments
263+
this._index = endOfCommentIndex + "*/".length - 1; // Adjust -1 because the loop will increment index by 1
264+
}
265+
266+
return true;
267+
}
268+
269+
return false;
270+
}
271+
272+
private handleQuoteCharacter(): boolean {
273+
if (this._currentQuotationMark) {
274+
if (this._nextCharIsEscaped) {
275+
this._nextCharIsEscaped = false;
276+
} else if (this._text[this._index] === this._currentQuotationMark) {
277+
this._currentQuotationMark = "";
278+
} else if (this._text[this._index] === "\\") {
279+
this._nextCharIsEscaped = true;
280+
}
281+
282+
return true;
283+
}
284+
285+
if (this._text[this._index] === "\"" || this._text[this._index] === "'") {
286+
this._currentQuotationMark = this._text[this._index];
287+
return true;
288+
}
289+
290+
return false;
291+
}
292+
293+
private handleLeadingWhitespace(): boolean {
294+
if (this._lastCheckpoint === this._index && this._text[this._index].trim().length === 0) {
295+
this._lastCheckpoint++;
296+
return true;
297+
}
298+
299+
return false;
300+
}
301+
}
302+
303+
enum CssToken {
304+
Selector,
305+
Media,
306+
Property,
307+
Value
308+
};
309+
}

0 commit comments

Comments
 (0)