Update dev-tools.js
Browse files- dev-tools.js +191 -719
dev-tools.js
CHANGED
@@ -36,519 +36,7 @@
|
|
36 |
--dom-text: #d4d4d4;
|
37 |
}
|
38 |
|
39 |
-
|
40 |
-
position: fixed;
|
41 |
-
bottom: 0;
|
42 |
-
left: 0;
|
43 |
-
width: 100%;
|
44 |
-
height: 300px;
|
45 |
-
background-color: var(--panel-bg);
|
46 |
-
border-top: 1px solid var(--border-color);
|
47 |
-
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
|
48 |
-
z-index: 9999;
|
49 |
-
display: flex;
|
50 |
-
flex-direction: column;
|
51 |
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
52 |
-
color: var(--text-color);
|
53 |
-
}
|
54 |
-
|
55 |
-
.devtools-header {
|
56 |
-
display: flex;
|
57 |
-
justify-content: space-between;
|
58 |
-
align-items: center;
|
59 |
-
padding: 5px 10px;
|
60 |
-
background-color: var(--tab-bg);
|
61 |
-
border-bottom: 1px solid var(--border-color);
|
62 |
-
}
|
63 |
-
|
64 |
-
.devtools-tabs {
|
65 |
-
display: flex;
|
66 |
-
gap: 5px;
|
67 |
-
}
|
68 |
-
|
69 |
-
.devtools-tab {
|
70 |
-
padding: 5px 10px;
|
71 |
-
cursor: pointer;
|
72 |
-
border-radius: 3px 3px 0 0;
|
73 |
-
background-color: var(--tab-bg);
|
74 |
-
border: 1px solid var(--border-color);
|
75 |
-
border-bottom: none;
|
76 |
-
font-size: 12px;
|
77 |
-
color: var(--text-muted);
|
78 |
-
}
|
79 |
-
|
80 |
-
.devtools-tab.active {
|
81 |
-
background-color: var(--tab-active-bg);
|
82 |
-
color: var(--text-color);
|
83 |
-
border-bottom: 1px solid var(--tab-active-bg);
|
84 |
-
margin-bottom: -1px;
|
85 |
-
font-weight: bold;
|
86 |
-
}
|
87 |
-
|
88 |
-
.devtools-close {
|
89 |
-
background: none;
|
90 |
-
border: none;
|
91 |
-
font-size: 16px;
|
92 |
-
cursor: pointer;
|
93 |
-
padding: 0 5px;
|
94 |
-
color: var(--text-color);
|
95 |
-
}
|
96 |
-
|
97 |
-
.devtools-content {
|
98 |
-
flex: 1;
|
99 |
-
overflow: auto;
|
100 |
-
position: relative;
|
101 |
-
background-color: var(--panel-bg);
|
102 |
-
}
|
103 |
-
|
104 |
-
.devtools-panel {
|
105 |
-
position: absolute;
|
106 |
-
top: 0;
|
107 |
-
left: 0;
|
108 |
-
width: 100%;
|
109 |
-
height: 100%;
|
110 |
-
padding: 10px;
|
111 |
-
overflow: auto;
|
112 |
-
display: none;
|
113 |
-
background-color: var(--panel-bg);
|
114 |
-
}
|
115 |
-
|
116 |
-
.devtools-panel.active {
|
117 |
-
display: block;
|
118 |
-
}
|
119 |
-
|
120 |
-
/* Console スタイル */
|
121 |
-
#console-log {
|
122 |
-
white-space: pre-wrap;
|
123 |
-
margin: 0;
|
124 |
-
line-height: 1.4;
|
125 |
-
flex: 1;
|
126 |
-
color: var(--console-log-color);
|
127 |
-
font-family: 'Consolas', 'Monaco', monospace;
|
128 |
-
font-size: 13px;
|
129 |
-
}
|
130 |
-
|
131 |
-
.console-log {
|
132 |
-
color: var(--console-log-color);
|
133 |
-
}
|
134 |
-
|
135 |
-
.console-error {
|
136 |
-
color: var(--console-error-color);
|
137 |
-
}
|
138 |
-
|
139 |
-
.console-warn {
|
140 |
-
color: var(--console-warn-color);
|
141 |
-
}
|
142 |
-
|
143 |
-
.console-info {
|
144 |
-
color: var(--console-info-color);
|
145 |
-
}
|
146 |
-
|
147 |
-
.console-input {
|
148 |
-
width: calc(100% - 16px);
|
149 |
-
background: var(--tab-bg);
|
150 |
-
border: 1px solid var(--border-color);
|
151 |
-
color: var(--text-color);
|
152 |
-
padding: 8px;
|
153 |
-
margin-top: 10px;
|
154 |
-
font-family: monospace;
|
155 |
-
border-radius: 3px;
|
156 |
-
}
|
157 |
-
|
158 |
-
/* Elements スタイル */
|
159 |
-
.elements-container {
|
160 |
-
display: flex;
|
161 |
-
flex: 1;
|
162 |
-
overflow: hidden;
|
163 |
-
}
|
164 |
-
|
165 |
-
.dom-tree {
|
166 |
-
font-family: 'Consolas', 'Monaco', monospace;
|
167 |
-
flex: 1;
|
168 |
-
overflow: auto;
|
169 |
-
border-right: 1px solid var(--border-color);
|
170 |
-
padding-right: 10px;
|
171 |
-
color: var(--dom-text);
|
172 |
-
font-size: 13px;
|
173 |
-
}
|
174 |
-
|
175 |
-
.dom-node {
|
176 |
-
margin-left: 15px;
|
177 |
-
position: relative;
|
178 |
-
line-height: 1.4;
|
179 |
-
transition: background-color 0.3s;
|
180 |
-
}
|
181 |
-
|
182 |
-
.dom-node.selected {
|
183 |
-
background: var(--highlight-bg);
|
184 |
-
}
|
185 |
-
|
186 |
-
.dom-node.highlight {
|
187 |
-
animation: highlight-fade 1.5s;
|
188 |
-
}
|
189 |
-
|
190 |
-
@keyframes highlight-fade {
|
191 |
-
0% { background-color: rgba(79, 195, 247, 0.5); }
|
192 |
-
100% { background-color: transparent; }
|
193 |
-
}
|
194 |
-
|
195 |
-
.dom-tag {
|
196 |
-
color: var(--dom-tag);
|
197 |
-
font-weight: bold;
|
198 |
-
}
|
199 |
-
|
200 |
-
.dom-attr {
|
201 |
-
color: var(--dom-attr);
|
202 |
-
}
|
203 |
-
|
204 |
-
.dom-attr.editable:hover {
|
205 |
-
text-decoration: underline;
|
206 |
-
cursor: pointer;
|
207 |
-
}
|
208 |
-
|
209 |
-
.dom-text {
|
210 |
-
color: var(--dom-text);
|
211 |
-
}
|
212 |
-
|
213 |
-
.dom-edit-input {
|
214 |
-
background: var(--panel-bg);
|
215 |
-
border: 1px solid var(--primary-color);
|
216 |
-
padding: 0 2px;
|
217 |
-
margin: -1px 0;
|
218 |
-
font-family: monospace;
|
219 |
-
min-width: 50px;
|
220 |
-
color: var(--text-color);
|
221 |
-
}
|
222 |
-
|
223 |
-
.css-panel {
|
224 |
-
flex: 1;
|
225 |
-
overflow: auto;
|
226 |
-
padding-left: 10px;
|
227 |
-
font-size: 13px;
|
228 |
-
}
|
229 |
-
|
230 |
-
.css-rule {
|
231 |
-
margin-bottom: 15px;
|
232 |
-
border: 1px solid var(--border-color);
|
233 |
-
padding: 8px;
|
234 |
-
background-color: var(--tab-bg);
|
235 |
-
border-radius: 3px;
|
236 |
-
}
|
237 |
-
|
238 |
-
.css-selector {
|
239 |
-
color: var(--primary-color);
|
240 |
-
margin-bottom: 5px;
|
241 |
-
font-weight: bold;
|
242 |
-
}
|
243 |
-
|
244 |
-
.css-property {
|
245 |
-
display: flex;
|
246 |
-
margin-bottom: 3px;
|
247 |
-
}
|
248 |
-
|
249 |
-
.css-property-name {
|
250 |
-
color: var(--dom-attr);
|
251 |
-
min-width: 120px;
|
252 |
-
}
|
253 |
-
|
254 |
-
.css-property-value {
|
255 |
-
color: var(--dom-text);
|
256 |
-
flex: 1;
|
257 |
-
}
|
258 |
-
|
259 |
-
.css-toggle {
|
260 |
-
margin-left: 10px;
|
261 |
-
color: var(--error-color);
|
262 |
-
cursor: pointer;
|
263 |
-
}
|
264 |
-
|
265 |
-
/* Context Menu */
|
266 |
-
.context-menu {
|
267 |
-
position: absolute;
|
268 |
-
background: var(--panel-bg);
|
269 |
-
border: 1px solid var(--border-color);
|
270 |
-
z-index: 10000;
|
271 |
-
min-width: 200px;
|
272 |
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
273 |
-
display: none;
|
274 |
-
border-radius: 3px;
|
275 |
-
overflow: hidden;
|
276 |
-
}
|
277 |
-
|
278 |
-
.context-menu-item {
|
279 |
-
padding: 8px 15px;
|
280 |
-
cursor: pointer;
|
281 |
-
color: var(--text-color);
|
282 |
-
font-size: 13px;
|
283 |
-
}
|
284 |
-
|
285 |
-
.context-menu-item:hover {
|
286 |
-
background: var(--primary-color);
|
287 |
-
color: #000;
|
288 |
-
}
|
289 |
-
|
290 |
-
/* Storage スタイル */
|
291 |
-
.storage-table {
|
292 |
-
width: 100%;
|
293 |
-
border-collapse: collapse;
|
294 |
-
margin-bottom: 10px;
|
295 |
-
font-size: 13px;
|
296 |
-
}
|
297 |
-
|
298 |
-
.storage-table th, .storage-table td {
|
299 |
-
border: 1px solid var(--border-color);
|
300 |
-
padding: 5px;
|
301 |
-
text-align: left;
|
302 |
-
}
|
303 |
-
|
304 |
-
.storage-table th {
|
305 |
-
background: var(--tab-bg);
|
306 |
-
}
|
307 |
-
|
308 |
-
.storage-actions {
|
309 |
-
display: flex;
|
310 |
-
gap: 5px;
|
311 |
-
}
|
312 |
-
|
313 |
-
.storage-btn {
|
314 |
-
background: var(--primary-color);
|
315 |
-
border: none;
|
316 |
-
padding: 2px 5px;
|
317 |
-
cursor: pointer;
|
318 |
-
border-radius: 3px;
|
319 |
-
color: #000;
|
320 |
-
font-size: 12px;
|
321 |
-
}
|
322 |
-
|
323 |
-
.editable {
|
324 |
-
cursor: pointer;
|
325 |
-
padding: 2px 5px;
|
326 |
-
border: 1px dashed transparent;
|
327 |
-
}
|
328 |
-
|
329 |
-
.editable:hover {
|
330 |
-
border-color: var(--primary-color);
|
331 |
-
}
|
332 |
-
|
333 |
-
.add-btn {
|
334 |
-
background: var(--primary-color);
|
335 |
-
color: #000;
|
336 |
-
border: none;
|
337 |
-
padding: 5px 10px;
|
338 |
-
margin-top: 10px;
|
339 |
-
cursor: pointer;
|
340 |
-
border-radius: 3px;
|
341 |
-
font-size: 13px;
|
342 |
-
}
|
343 |
-
|
344 |
-
.add-btn:hover {
|
345 |
-
background: var(--primary-hover);
|
346 |
-
}
|
347 |
-
|
348 |
-
/* Network スタイル */
|
349 |
-
.network-container {
|
350 |
-
display: flex;
|
351 |
-
height: 100%;
|
352 |
-
overflow: hidden;
|
353 |
-
}
|
354 |
-
|
355 |
-
.network-requests {
|
356 |
-
width: 40%;
|
357 |
-
overflow-y: auto;
|
358 |
-
border-right: 1px solid var(--border-color);
|
359 |
-
font-size: 13px;
|
360 |
-
}
|
361 |
-
|
362 |
-
.network-details {
|
363 |
-
width: 60%;
|
364 |
-
overflow-y: auto;
|
365 |
-
padding-left: 10px;
|
366 |
-
}
|
367 |
-
|
368 |
-
.network-request {
|
369 |
-
padding: 8px;
|
370 |
-
border-bottom: 1px solid var(--border-color);
|
371 |
-
cursor: pointer;
|
372 |
-
display: flex;
|
373 |
-
align-items: center;
|
374 |
-
}
|
375 |
-
|
376 |
-
.network-request:hover {
|
377 |
-
background-color: rgba(0, 122, 204, 0.1);
|
378 |
-
}
|
379 |
-
|
380 |
-
.network-request.selected {
|
381 |
-
background-color: var(--highlight-bg);
|
382 |
-
}
|
383 |
-
|
384 |
-
.network-status {
|
385 |
-
width: 20px;
|
386 |
-
height: 20px;
|
387 |
-
border-radius: 50%;
|
388 |
-
display: flex;
|
389 |
-
align-items: center;
|
390 |
-
justify-content: center;
|
391 |
-
margin-right: 8px;
|
392 |
-
flex-shrink: 0;
|
393 |
-
}
|
394 |
-
|
395 |
-
.network-status.success {
|
396 |
-
background-color: var(--success-color);
|
397 |
-
color: white;
|
398 |
-
}
|
399 |
-
|
400 |
-
.network-status.error {
|
401 |
-
background-color: var(--error-color);
|
402 |
-
color: white;
|
403 |
-
}
|
404 |
-
|
405 |
-
.network-method {
|
406 |
-
font-weight: bold;
|
407 |
-
margin-right: 8px;
|
408 |
-
color: var(--primary-color);
|
409 |
-
min-width: 40px;
|
410 |
-
}
|
411 |
-
|
412 |
-
.network-url {
|
413 |
-
flex: 1;
|
414 |
-
white-space: nowrap;
|
415 |
-
overflow: hidden;
|
416 |
-
text-overflow: ellipsis;
|
417 |
-
}
|
418 |
-
|
419 |
-
.network-time {
|
420 |
-
color: var(--text-muted);
|
421 |
-
font-size: 11px;
|
422 |
-
margin-left: 8px;
|
423 |
-
}
|
424 |
-
|
425 |
-
.network-detail-section {
|
426 |
-
margin-bottom: 15px;
|
427 |
-
}
|
428 |
-
|
429 |
-
.network-detail-title {
|
430 |
-
font-weight: bold;
|
431 |
-
margin-bottom: 5px;
|
432 |
-
color: var(--primary-color);
|
433 |
-
}
|
434 |
-
|
435 |
-
.network-detail-content {
|
436 |
-
background: var(--tab-bg);
|
437 |
-
padding: 8px;
|
438 |
-
border-radius: 3px;
|
439 |
-
border: 1px solid var(--border-color);
|
440 |
-
font-family: monospace;
|
441 |
-
white-space: pre-wrap;
|
442 |
-
font-size: 12px;
|
443 |
-
max-height: 200px;
|
444 |
-
overflow-y: auto;
|
445 |
-
}
|
446 |
-
|
447 |
-
/* Web Vitals スタイル */
|
448 |
-
.vitals-container {
|
449 |
-
display: flex;
|
450 |
-
flex-direction: column;
|
451 |
-
gap: 15px;
|
452 |
-
}
|
453 |
-
|
454 |
-
.vital-card {
|
455 |
-
background: var(--tab-bg);
|
456 |
-
border: 1px solid var(--border-color);
|
457 |
-
border-radius: 5px;
|
458 |
-
padding: 15px;
|
459 |
-
}
|
460 |
-
|
461 |
-
.vital-title {
|
462 |
-
font-weight: bold;
|
463 |
-
margin-bottom: 10px;
|
464 |
-
color: var(--primary-color);
|
465 |
-
display: flex;
|
466 |
-
justify-content: space-between;
|
467 |
-
align-items: center;
|
468 |
-
}
|
469 |
-
|
470 |
-
.vital-value {
|
471 |
-
font-size: 24px;
|
472 |
-
font-weight: bold;
|
473 |
-
margin: 10px 0;
|
474 |
-
}
|
475 |
-
|
476 |
-
.vital-good {
|
477 |
-
color: var(--success-color);
|
478 |
-
}
|
479 |
-
|
480 |
-
.vital-needs-improvement {
|
481 |
-
color: var(--warning-color);
|
482 |
-
}
|
483 |
-
|
484 |
-
.vital-poor {
|
485 |
-
color: var(--error-color);
|
486 |
-
}
|
487 |
-
|
488 |
-
.vital-description {
|
489 |
-
font-size: 13px;
|
490 |
-
color: var(--text-muted);
|
491 |
-
}
|
492 |
-
|
493 |
-
.vital-thresholds {
|
494 |
-
display: flex;
|
495 |
-
margin-top: 10px;
|
496 |
-
font-size: 12px;
|
497 |
-
}
|
498 |
-
|
499 |
-
.vital-threshold {
|
500 |
-
flex: 1;
|
501 |
-
text-align: center;
|
502 |
-
padding: 5px;
|
503 |
-
border-radius: 3px;
|
504 |
-
}
|
505 |
-
|
506 |
-
.vital-threshold.active {
|
507 |
-
background: rgba(0, 122, 204, 0.2);
|
508 |
-
}
|
509 |
-
|
510 |
-
/* DOM Tree Toggle */
|
511 |
-
.dom-toggle {
|
512 |
-
position: absolute;
|
513 |
-
left: -12px;
|
514 |
-
top: 2px;
|
515 |
-
width: 10px;
|
516 |
-
height: 10px;
|
517 |
-
cursor: pointer;
|
518 |
-
background-color: var(--text-muted);
|
519 |
-
clip-path: polygon(0 0, 100% 50%, 0 100%);
|
520 |
-
transition: transform 0.2s;
|
521 |
-
}
|
522 |
-
|
523 |
-
.dom-toggle.collapsed {
|
524 |
-
transform: rotate(-90deg);
|
525 |
-
}
|
526 |
-
|
527 |
-
.dom-children {
|
528 |
-
overflow: hidden;
|
529 |
-
transition: max-height 0.3s ease-out;
|
530 |
-
}
|
531 |
-
|
532 |
-
/* JSON スタイル */
|
533 |
-
.json-key {
|
534 |
-
color: var(--json-key);
|
535 |
-
}
|
536 |
-
|
537 |
-
.json-string {
|
538 |
-
color: var(--json-string);
|
539 |
-
}
|
540 |
-
|
541 |
-
.json-number {
|
542 |
-
color: var(--json-number);
|
543 |
-
}
|
544 |
-
|
545 |
-
.json-boolean {
|
546 |
-
color: var(--json-boolean);
|
547 |
-
}
|
548 |
-
|
549 |
-
.json-null {
|
550 |
-
color: var(--json-null);
|
551 |
-
}
|
552 |
`;
|
553 |
document.head.appendChild(style);
|
554 |
|
@@ -566,39 +54,34 @@
|
|
566 |
LCP: null,
|
567 |
TTFB: null
|
568 |
};
|
|
|
|
|
|
|
569 |
// DOM変更を監視するMutationObserver
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
mutation.
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
|
|
|
|
|
|
|
|
|
|
578 |
}
|
579 |
});
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
const webVitalsScript = document.createElement('script');
|
590 |
-
webVitalsScript.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
|
591 |
-
webVitalsScript.onload = resolve;
|
592 |
-
document.head.appendChild(webVitalsScript);
|
593 |
-
});
|
594 |
-
};
|
595 |
-
|
596 |
-
observer.observe(document.documentElement, {
|
597 |
-
childList: true,
|
598 |
-
subtree: true,
|
599 |
-
attributes: true,
|
600 |
-
characterData: true
|
601 |
-
});
|
602 |
// ノードをハイライト表示
|
603 |
function highlightNode(node) {
|
604 |
const elementId = node.id || Math.random().toString(36).substr(2, 9);
|
@@ -684,7 +167,6 @@ observer.observe(document.documentElement, {
|
|
684 |
tab.classList.add('active');
|
685 |
document.getElementById(panelId + '-panel').classList.add('active');
|
686 |
|
687 |
-
// ネットワークタブを選択した場合はリクエストリストを更新
|
688 |
if (panelId === 'network') {
|
689 |
renderNetworkRequests();
|
690 |
}
|
@@ -775,29 +257,44 @@ observer.observe(document.documentElement, {
|
|
775 |
`;
|
776 |
container.appendChild(ttfbCard);
|
777 |
|
778 |
-
// Web Vitalsの計測を開始
|
779 |
-
loadWebVitals()
|
780 |
-
|
781 |
-
|
782 |
-
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
|
789 |
-
|
790 |
-
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
795 |
}
|
796 |
-
}
|
797 |
-
|
798 |
-
}
|
799 |
-
}
|
800 |
-
});
|
801 |
function updateCLS(metric) {
|
802 |
vitalsData.CLS = metric.value;
|
803 |
updateVitalDisplay('cls', metric.value, 0.1, 0.25);
|
@@ -827,14 +324,12 @@ loadWebVitals().then(() => {
|
|
827 |
const element = document.getElementById(`${id}-value`);
|
828 |
if (!element) return;
|
829 |
|
830 |
-
// ミリ秒を秒に変換 (TTFB以外)
|
831 |
const displayValue = id === 'ttfb' ?
|
832 |
`${Math.round(value)}ms` :
|
833 |
`${value.toFixed(2)}${id === 'cls' ? '' : 's'}`;
|
834 |
|
835 |
element.textContent = displayValue;
|
836 |
|
837 |
-
// 閾値に基づいてクラスを設定
|
838 |
element.className = 'vital-value';
|
839 |
if (value <= goodThreshold) {
|
840 |
element.classList.add('vital-good');
|
@@ -844,7 +339,6 @@ loadWebVitals().then(() => {
|
|
844 |
element.classList.add('vital-poor');
|
845 |
}
|
846 |
|
847 |
-
// 閾値表示を更新
|
848 |
const thresholds = element.parentElement.querySelectorAll('.vital-threshold');
|
849 |
thresholds.forEach((threshold, index) => {
|
850 |
threshold.classList.remove('active');
|
@@ -871,17 +365,14 @@ loadWebVitals().then(() => {
|
|
871 |
container.className = 'network-container';
|
872 |
panel.appendChild(container);
|
873 |
|
874 |
-
// リクエストリスト
|
875 |
const requestsList = document.createElement('div');
|
876 |
requestsList.className = 'network-requests';
|
877 |
container.appendChild(requestsList);
|
878 |
|
879 |
-
// 詳細パネル
|
880 |
const detailsPanel = document.createElement('div');
|
881 |
detailsPanel.className = 'network-details';
|
882 |
container.appendChild(detailsPanel);
|
883 |
|
884 |
-
// ネットワークリクエストを監視
|
885 |
setupNetworkMonitoring();
|
886 |
|
887 |
return panel;
|
@@ -927,7 +418,6 @@ loadWebVitals().then(() => {
|
|
927 |
body: null
|
928 |
};
|
929 |
|
930 |
-
// レスポンスボディをクローンして読み取る
|
931 |
const clonedResponse = response.clone();
|
932 |
const contentType = clonedResponse.headers.get('content-type') || '';
|
933 |
|
@@ -1091,7 +581,6 @@ loadWebVitals().then(() => {
|
|
1091 |
const detailsPanel = panel.querySelector('.network-details');
|
1092 |
detailsPanel.innerHTML = '';
|
1093 |
|
1094 |
-
// 一般情報
|
1095 |
const generalSection = document.createElement('div');
|
1096 |
generalSection.className = 'network-detail-section';
|
1097 |
generalSection.innerHTML = `
|
@@ -1105,7 +594,6 @@ loadWebVitals().then(() => {
|
|
1105 |
`;
|
1106 |
detailsPanel.appendChild(generalSection);
|
1107 |
|
1108 |
-
// リクエストヘッダー
|
1109 |
if (selectedRequest.requestHeaders) {
|
1110 |
const headersSection = document.createElement('div');
|
1111 |
headersSection.className = 'network-detail-section';
|
@@ -1132,7 +620,6 @@ loadWebVitals().then(() => {
|
|
1132 |
detailsPanel.appendChild(headersSection);
|
1133 |
}
|
1134 |
|
1135 |
-
// リクエストボディ
|
1136 |
if (selectedRequest.requestBody) {
|
1137 |
const bodySection = document.createElement('div');
|
1138 |
bodySection.className = 'network-detail-section';
|
@@ -1159,7 +646,6 @@ loadWebVitals().then(() => {
|
|
1159 |
detailsPanel.appendChild(bodySection);
|
1160 |
}
|
1161 |
|
1162 |
-
// レスポンス
|
1163 |
if (selectedRequest.response) {
|
1164 |
const responseSection = document.createElement('div');
|
1165 |
responseSection.className = 'network-detail-section';
|
@@ -1186,7 +672,6 @@ loadWebVitals().then(() => {
|
|
1186 |
detailsPanel.appendChild(responseSection);
|
1187 |
}
|
1188 |
|
1189 |
-
// エラー
|
1190 |
if (selectedRequest.error) {
|
1191 |
const errorSection = document.createElement('div');
|
1192 |
errorSection.className = 'network-detail-section';
|
@@ -1245,7 +730,6 @@ loadWebVitals().then(() => {
|
|
1245 |
panel.className = 'devtools-panel';
|
1246 |
panel.id = 'storage-panel';
|
1247 |
|
1248 |
-
// LocalStorage表示
|
1249 |
const localStorageTitle = document.createElement('h3');
|
1250 |
localStorageTitle.textContent = 'Local Storage';
|
1251 |
panel.appendChild(localStorageTitle);
|
@@ -1267,7 +751,6 @@ loadWebVitals().then(() => {
|
|
1267 |
};
|
1268 |
panel.appendChild(addLocalStorageBtn);
|
1269 |
|
1270 |
-
// SessionStorage表示
|
1271 |
const sessionStorageTitle = document.createElement('h3');
|
1272 |
sessionStorageTitle.style.marginTop = '20px';
|
1273 |
sessionStorageTitle.textContent = 'Session Storage';
|
@@ -1290,7 +773,6 @@ loadWebVitals().then(() => {
|
|
1290 |
};
|
1291 |
panel.appendChild(addSessionStorageBtn);
|
1292 |
|
1293 |
-
// Cookie表示
|
1294 |
const cookiesTitle = document.createElement('h3');
|
1295 |
cookiesTitle.style.marginTop = '20px';
|
1296 |
cookiesTitle.textContent = 'Cookies';
|
@@ -1313,7 +795,6 @@ loadWebVitals().then(() => {
|
|
1313 |
};
|
1314 |
panel.appendChild(addCookieBtn);
|
1315 |
|
1316 |
-
// ストレージを表示する関数
|
1317 |
function renderStorage() {
|
1318 |
renderTable(localStorageTable, localStorage, 'local');
|
1319 |
renderTable(sessionStorageTable, sessionStorage, 'session');
|
@@ -1462,7 +943,6 @@ loadWebVitals().then(() => {
|
|
1462 |
});
|
1463 |
}
|
1464 |
|
1465 |
-
// 初期表示
|
1466 |
renderStorage();
|
1467 |
|
1468 |
return panel;
|
@@ -1474,7 +954,6 @@ loadWebVitals().then(() => {
|
|
1474 |
|
1475 |
switch (action) {
|
1476 |
case 'edit-html':
|
1477 |
-
// document.documentElement(html要素)は編集不可
|
1478 |
if (selectedElement === document.documentElement) {
|
1479 |
alert('ルートHTML要素は直接編集できません');
|
1480 |
return;
|
@@ -1635,7 +1114,6 @@ loadWebVitals().then(() => {
|
|
1635 |
panel.appendChild(log);
|
1636 |
panel.appendChild(input);
|
1637 |
|
1638 |
-
// コンソールメソッドをオーバーライド
|
1639 |
const originalConsole = {
|
1640 |
log: console.log,
|
1641 |
error: console.error,
|
@@ -1696,7 +1174,8 @@ loadWebVitals().then(() => {
|
|
1696 |
|
1697 |
return panel;
|
1698 |
}
|
1699 |
-
|
|
|
1700 |
function createElementsPanel() {
|
1701 |
const panel = document.createElement('div');
|
1702 |
panel.className = 'devtools-panel';
|
@@ -1716,43 +1195,95 @@ loadWebVitals().then(() => {
|
|
1716 |
container.appendChild(tree);
|
1717 |
container.appendChild(cssPanel);
|
1718 |
panel.appendChild(container);
|
1719 |
-
|
1720 |
-
|
1721 |
-
|
1722 |
-
|
1723 |
-
|
1724 |
-
|
1725 |
-
|
1726 |
-
|
1727 |
-
if (
|
1728 |
-
|
1729 |
-
|
1730 |
-
|
1731 |
-
|
1732 |
-
|
1733 |
-
|
1734 |
-
|
1735 |
-
|
1736 |
-
|
1737 |
-
|
1738 |
-
|
1739 |
-
|
1740 |
-
|
1741 |
-
|
1742 |
-
|
1743 |
-
|
1744 |
-
|
1745 |
-
|
1746 |
-
|
1747 |
-
|
1748 |
-
|
1749 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1750 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1751 |
}
|
1752 |
-
}
|
1753 |
-
}
|
1754 |
|
1755 |
-
|
1756 |
function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
|
1757 |
if (node.nodeType === Node.ELEMENT_NODE) {
|
1758 |
const element = document.createElement('div');
|
@@ -1760,7 +1291,6 @@ function refreshElementsPanel() {
|
|
1760 |
element.style.marginLeft = `${depth * 15}px`;
|
1761 |
element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
|
1762 |
|
1763 |
-
// 右クリックイベント
|
1764 |
element.oncontextmenu = (e) => {
|
1765 |
e.preventDefault();
|
1766 |
selectedElement = node;
|
@@ -1769,7 +1299,6 @@ function refreshElementsPanel() {
|
|
1769 |
document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
|
1770 |
element.classList.add('selected');
|
1771 |
|
1772 |
-
// HTML要素にはコンテキストメニューを表示しない
|
1773 |
if (node !== document.documentElement) {
|
1774 |
contextMenu.style.display = 'block';
|
1775 |
contextMenu.style.left = `${e.pageX}px`;
|
@@ -1779,7 +1308,6 @@ function refreshElementsPanel() {
|
|
1779 |
updateCSSPanel(node);
|
1780 |
};
|
1781 |
|
1782 |
-
// 左クリックで選択
|
1783 |
element.onclick = (e) => {
|
1784 |
if (e.target.classList.contains('dom-toggle')) return;
|
1785 |
|
@@ -1793,7 +1321,6 @@ function refreshElementsPanel() {
|
|
1793 |
updateCSSPanel(node);
|
1794 |
};
|
1795 |
|
1796 |
-
// 子要素がある場合はトグルボタンを追加
|
1797 |
const hasChildren = node.childNodes.length > 0 &&
|
1798 |
!(node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE && !node.childNodes[0].textContent.trim());
|
1799 |
|
@@ -1816,7 +1343,6 @@ function refreshElementsPanel() {
|
|
1816 |
element.appendChild(toggle);
|
1817 |
}
|
1818 |
|
1819 |
-
// タグ名(HTML要素は編集不可)
|
1820 |
const tag = document.createElement('span');
|
1821 |
tag.className = 'dom-tag';
|
1822 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
@@ -1840,7 +1366,6 @@ function refreshElementsPanel() {
|
|
1840 |
|
1841 |
element.appendChild(tag);
|
1842 |
|
1843 |
-
// 属性(HTML要素は編集不可)
|
1844 |
Array.from(node.attributes).forEach(attr => {
|
1845 |
const attrSpan = document.createElement('span');
|
1846 |
attrSpan.className = 'dom-attr';
|
@@ -1897,92 +1422,6 @@ function refreshElementsPanel() {
|
|
1897 |
parentElement.appendChild(text);
|
1898 |
}
|
1899 |
}
|
1900 |
-
// CSSパネル更新関数
|
1901 |
-
function updateCSSPanel(element) {
|
1902 |
-
const cssPanel = document.getElementById('css-panel');
|
1903 |
-
cssPanel.innerHTML = '';
|
1904 |
-
|
1905 |
-
if (!element) return;
|
1906 |
-
|
1907 |
-
if (element.style.length > 0) {
|
1908 |
-
const inlineRule = document.createElement('div');
|
1909 |
-
inlineRule.className = 'css-rule';
|
1910 |
-
|
1911 |
-
const selector = document.createElement('div');
|
1912 |
-
selector.className = 'css-selector';
|
1913 |
-
selector.textContent = 'インラインスタイル';
|
1914 |
-
inlineRule.appendChild(selector);
|
1915 |
-
|
1916 |
-
for (let i = 0; i < element.style.length; i++) {
|
1917 |
-
const propName = element.style[i];
|
1918 |
-
const propValue = element.style[propName];
|
1919 |
-
|
1920 |
-
const propDiv = document.createElement('div');
|
1921 |
-
propDiv.className = 'css-property';
|
1922 |
-
|
1923 |
-
const nameSpan = document.createElement('span');
|
1924 |
-
nameSpan.className = 'css-property-name editable';
|
1925 |
-
nameSpan.textContent = propName;
|
1926 |
-
nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1927 |
-
|
1928 |
-
const valueSpan = document.createElement('span');
|
1929 |
-
valueSpan.className = 'css-property-value editable';
|
1930 |
-
valueSpan.textContent = propValue;
|
1931 |
-
valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1932 |
-
|
1933 |
-
const toggleSpan = document.createElement('span');
|
1934 |
-
toggleSpan.className = 'css-toggle';
|
1935 |
-
toggleSpan.textContent = '×';
|
1936 |
-
toggleSpan.title = 'プロパティを無効化';
|
1937 |
-
toggleSpan.onclick = () => {
|
1938 |
-
element.style[propName] = '';
|
1939 |
-
updateCSSPanel(element);
|
1940 |
-
};
|
1941 |
-
|
1942 |
-
propDiv.appendChild(nameSpan);
|
1943 |
-
propDiv.appendChild(valueSpan);
|
1944 |
-
propDiv.appendChild(toggleSpan);
|
1945 |
-
inlineRule.appendChild(propDiv);
|
1946 |
-
}
|
1947 |
-
|
1948 |
-
cssPanel.appendChild(inlineRule);
|
1949 |
-
}
|
1950 |
-
|
1951 |
-
const computedStyles = window.getComputedStyle(element);
|
1952 |
-
const computedRule = document.createElement('div');
|
1953 |
-
computedRule.className = 'css-rule';
|
1954 |
-
|
1955 |
-
const computedSelector = document.createElement('div');
|
1956 |
-
computedSelector.className = 'css-selector';
|
1957 |
-
computedSelector.textContent = '計算されたスタイル';
|
1958 |
-
computedRule.appendChild(computedSelector);
|
1959 |
-
|
1960 |
-
const importantProps = [
|
1961 |
-
'display', 'position', 'width', 'height', 'margin', 'padding',
|
1962 |
-
'color', 'background', 'border', 'font', 'flex', 'grid'
|
1963 |
-
];
|
1964 |
-
|
1965 |
-
importantProps.forEach(prop => {
|
1966 |
-
const value = computedStyles[prop];
|
1967 |
-
|
1968 |
-
const propDiv = document.createElement('div');
|
1969 |
-
propDiv.className = 'css-property';
|
1970 |
-
|
1971 |
-
const nameSpan = document.createElement('span');
|
1972 |
-
nameSpan.className = 'css-property-name';
|
1973 |
-
nameSpan.textContent = prop;
|
1974 |
-
|
1975 |
-
const valueSpan = document.createElement('span');
|
1976 |
-
valueSpan.className = 'css-property-value';
|
1977 |
-
valueSpan.textContent = value;
|
1978 |
-
|
1979 |
-
propDiv.appendChild(nameSpan);
|
1980 |
-
propDiv.appendChild(valueSpan);
|
1981 |
-
computedRule.appendChild(propDiv);
|
1982 |
-
});
|
1983 |
-
|
1984 |
-
cssPanel.appendChild(computedRule);
|
1985 |
-
}
|
1986 |
|
1987 |
// CSSプロパティ編集
|
1988 |
function editCSSProperty(element, propName, styleType) {
|
@@ -2004,20 +1443,53 @@ function refreshElementsPanel() {
|
|
2004 |
}
|
2005 |
}
|
2006 |
|
2007 |
-
//
|
2008 |
-
|
2009 |
-
|
2010 |
-
|
2011 |
-
|
2012 |
-
|
2013 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014 |
|
|
|
|
|
|
|
|
|
2015 |
setTimeout(() => {
|
2016 |
refreshElementsPanel();
|
2017 |
}, 0);
|
2018 |
|
2019 |
return panel;
|
2020 |
}
|
|
|
2021 |
// 開発者ツール表示/非表示
|
2022 |
function toggleDevTools() {
|
2023 |
const container = document.getElementById('devtools-container');
|
|
|
36 |
--dom-text: #d4d4d4;
|
37 |
}
|
38 |
|
39 |
+
/* ... (スタイル定義は変更なし、前と同じ) ... */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
`;
|
41 |
document.head.appendChild(style);
|
42 |
|
|
|
54 |
LCP: null,
|
55 |
TTFB: null
|
56 |
};
|
57 |
+
let observer = null;
|
58 |
+
let refreshElementsPanel = null;
|
59 |
+
|
60 |
// DOM変更を監視するMutationObserver
|
61 |
+
function setupMutationObserver() {
|
62 |
+
observer = new MutationObserver((mutations) => {
|
63 |
+
mutations.forEach((mutation) => {
|
64 |
+
if (mutation.type === 'childList') {
|
65 |
+
mutation.addedNodes.forEach((node) => {
|
66 |
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
67 |
+
highlightNode(node);
|
68 |
+
}
|
69 |
+
});
|
70 |
+
}
|
71 |
+
});
|
72 |
+
if (refreshElementsPanel) {
|
73 |
+
refreshElementsPanel();
|
74 |
}
|
75 |
});
|
76 |
+
|
77 |
+
observer.observe(document.documentElement, {
|
78 |
+
childList: true,
|
79 |
+
subtree: true,
|
80 |
+
attributes: true,
|
81 |
+
characterData: true
|
82 |
+
});
|
83 |
+
}
|
84 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
// ノードをハイライト表示
|
86 |
function highlightNode(node) {
|
87 |
const elementId = node.id || Math.random().toString(36).substr(2, 9);
|
|
|
167 |
tab.classList.add('active');
|
168 |
document.getElementById(panelId + '-panel').classList.add('active');
|
169 |
|
|
|
170 |
if (panelId === 'network') {
|
171 |
renderNetworkRequests();
|
172 |
}
|
|
|
257 |
`;
|
258 |
container.appendChild(ttfbCard);
|
259 |
|
260 |
+
// Web Vitalsの計測を開始
|
261 |
+
const loadWebVitals = () => {
|
262 |
+
return new Promise((resolve) => {
|
263 |
+
if (window.webVitals) {
|
264 |
+
resolve();
|
265 |
+
return;
|
266 |
+
}
|
267 |
+
|
268 |
+
const script = document.createElement('script');
|
269 |
+
script.src = 'https://unpkg.com/[email protected]/dist/web-vitals.iife.js';
|
270 |
+
script.onload = resolve;
|
271 |
+
document.head.appendChild(script);
|
272 |
+
});
|
273 |
+
};
|
274 |
+
|
275 |
+
loadWebVitals().then(() => {
|
276 |
+
if (window.webVitals) {
|
277 |
+
try {
|
278 |
+
const vitals = window.webVitals;
|
279 |
+
if (vitals.getCLS) {
|
280 |
+
vitals.getCLS(updateCLS);
|
281 |
+
vitals.getFCP(updateFCP);
|
282 |
+
vitals.getFID(updateFID);
|
283 |
+
vitals.getLCP(updateLCP);
|
284 |
+
vitals.getTTFB(updateTTFB);
|
285 |
+
} else if (vitals.onCLS) {
|
286 |
+
vitals.onCLS(updateCLS);
|
287 |
+
vitals.onFCP(updateFCP);
|
288 |
+
vitals.onFID(updateFID);
|
289 |
+
vitals.onLCP(updateLCP);
|
290 |
+
vitals.onTTFB(updateTTFB);
|
291 |
+
}
|
292 |
+
} catch (e) {
|
293 |
+
console.error('Web Vitals error:', e);
|
294 |
+
}
|
295 |
}
|
296 |
+
});
|
297 |
+
|
|
|
|
|
|
|
298 |
function updateCLS(metric) {
|
299 |
vitalsData.CLS = metric.value;
|
300 |
updateVitalDisplay('cls', metric.value, 0.1, 0.25);
|
|
|
324 |
const element = document.getElementById(`${id}-value`);
|
325 |
if (!element) return;
|
326 |
|
|
|
327 |
const displayValue = id === 'ttfb' ?
|
328 |
`${Math.round(value)}ms` :
|
329 |
`${value.toFixed(2)}${id === 'cls' ? '' : 's'}`;
|
330 |
|
331 |
element.textContent = displayValue;
|
332 |
|
|
|
333 |
element.className = 'vital-value';
|
334 |
if (value <= goodThreshold) {
|
335 |
element.classList.add('vital-good');
|
|
|
339 |
element.classList.add('vital-poor');
|
340 |
}
|
341 |
|
|
|
342 |
const thresholds = element.parentElement.querySelectorAll('.vital-threshold');
|
343 |
thresholds.forEach((threshold, index) => {
|
344 |
threshold.classList.remove('active');
|
|
|
365 |
container.className = 'network-container';
|
366 |
panel.appendChild(container);
|
367 |
|
|
|
368 |
const requestsList = document.createElement('div');
|
369 |
requestsList.className = 'network-requests';
|
370 |
container.appendChild(requestsList);
|
371 |
|
|
|
372 |
const detailsPanel = document.createElement('div');
|
373 |
detailsPanel.className = 'network-details';
|
374 |
container.appendChild(detailsPanel);
|
375 |
|
|
|
376 |
setupNetworkMonitoring();
|
377 |
|
378 |
return panel;
|
|
|
418 |
body: null
|
419 |
};
|
420 |
|
|
|
421 |
const clonedResponse = response.clone();
|
422 |
const contentType = clonedResponse.headers.get('content-type') || '';
|
423 |
|
|
|
581 |
const detailsPanel = panel.querySelector('.network-details');
|
582 |
detailsPanel.innerHTML = '';
|
583 |
|
|
|
584 |
const generalSection = document.createElement('div');
|
585 |
generalSection.className = 'network-detail-section';
|
586 |
generalSection.innerHTML = `
|
|
|
594 |
`;
|
595 |
detailsPanel.appendChild(generalSection);
|
596 |
|
|
|
597 |
if (selectedRequest.requestHeaders) {
|
598 |
const headersSection = document.createElement('div');
|
599 |
headersSection.className = 'network-detail-section';
|
|
|
620 |
detailsPanel.appendChild(headersSection);
|
621 |
}
|
622 |
|
|
|
623 |
if (selectedRequest.requestBody) {
|
624 |
const bodySection = document.createElement('div');
|
625 |
bodySection.className = 'network-detail-section';
|
|
|
646 |
detailsPanel.appendChild(bodySection);
|
647 |
}
|
648 |
|
|
|
649 |
if (selectedRequest.response) {
|
650 |
const responseSection = document.createElement('div');
|
651 |
responseSection.className = 'network-detail-section';
|
|
|
672 |
detailsPanel.appendChild(responseSection);
|
673 |
}
|
674 |
|
|
|
675 |
if (selectedRequest.error) {
|
676 |
const errorSection = document.createElement('div');
|
677 |
errorSection.className = 'network-detail-section';
|
|
|
730 |
panel.className = 'devtools-panel';
|
731 |
panel.id = 'storage-panel';
|
732 |
|
|
|
733 |
const localStorageTitle = document.createElement('h3');
|
734 |
localStorageTitle.textContent = 'Local Storage';
|
735 |
panel.appendChild(localStorageTitle);
|
|
|
751 |
};
|
752 |
panel.appendChild(addLocalStorageBtn);
|
753 |
|
|
|
754 |
const sessionStorageTitle = document.createElement('h3');
|
755 |
sessionStorageTitle.style.marginTop = '20px';
|
756 |
sessionStorageTitle.textContent = 'Session Storage';
|
|
|
773 |
};
|
774 |
panel.appendChild(addSessionStorageBtn);
|
775 |
|
|
|
776 |
const cookiesTitle = document.createElement('h3');
|
777 |
cookiesTitle.style.marginTop = '20px';
|
778 |
cookiesTitle.textContent = 'Cookies';
|
|
|
795 |
};
|
796 |
panel.appendChild(addCookieBtn);
|
797 |
|
|
|
798 |
function renderStorage() {
|
799 |
renderTable(localStorageTable, localStorage, 'local');
|
800 |
renderTable(sessionStorageTable, sessionStorage, 'session');
|
|
|
943 |
});
|
944 |
}
|
945 |
|
|
|
946 |
renderStorage();
|
947 |
|
948 |
return panel;
|
|
|
954 |
|
955 |
switch (action) {
|
956 |
case 'edit-html':
|
|
|
957 |
if (selectedElement === document.documentElement) {
|
958 |
alert('ルートHTML要素は直接編集できません');
|
959 |
return;
|
|
|
1114 |
panel.appendChild(log);
|
1115 |
panel.appendChild(input);
|
1116 |
|
|
|
1117 |
const originalConsole = {
|
1118 |
log: console.log,
|
1119 |
error: console.error,
|
|
|
1174 |
|
1175 |
return panel;
|
1176 |
}
|
1177 |
+
|
1178 |
+
// Elementsパネル作成
|
1179 |
function createElementsPanel() {
|
1180 |
const panel = document.createElement('div');
|
1181 |
panel.className = 'devtools-panel';
|
|
|
1195 |
container.appendChild(tree);
|
1196 |
container.appendChild(cssPanel);
|
1197 |
panel.appendChild(container);
|
1198 |
+
|
1199 |
+
// CSSパネル更新関数
|
1200 |
+
function updateCSSPanel(element) {
|
1201 |
+
const cssPanel = document.getElementById('css-panel');
|
1202 |
+
cssPanel.innerHTML = '';
|
1203 |
+
|
1204 |
+
if (!element) return;
|
1205 |
+
|
1206 |
+
if (element.style.length > 0) {
|
1207 |
+
const inlineRule = document.createElement('div');
|
1208 |
+
inlineRule.className = 'css-rule';
|
1209 |
+
|
1210 |
+
const selector = document.createElement('div');
|
1211 |
+
selector.className = 'css-selector';
|
1212 |
+
selector.textContent = 'インラインスタイル';
|
1213 |
+
inlineRule.appendChild(selector);
|
1214 |
+
|
1215 |
+
for (let i = 0; i < element.style.length; i++) {
|
1216 |
+
const propName = element.style[i];
|
1217 |
+
const propValue = element.style[propName];
|
1218 |
+
|
1219 |
+
const propDiv = document.createElement('div');
|
1220 |
+
propDiv.className = 'css-property';
|
1221 |
+
|
1222 |
+
const nameSpan = document.createElement('span');
|
1223 |
+
nameSpan.className = 'css-property-name editable';
|
1224 |
+
nameSpan.textContent = propName;
|
1225 |
+
nameSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1226 |
+
|
1227 |
+
const valueSpan = document.createElement('span');
|
1228 |
+
valueSpan.className = 'css-property-value editable';
|
1229 |
+
valueSpan.textContent = propValue;
|
1230 |
+
valueSpan.onclick = () => editCSSProperty(element, propName, 'style');
|
1231 |
+
|
1232 |
+
const toggleSpan = document.createElement('span');
|
1233 |
+
toggleSpan.className = 'css-toggle';
|
1234 |
+
toggleSpan.textContent = '×';
|
1235 |
+
toggleSpan.title = 'プロパティを無効化';
|
1236 |
+
toggleSpan.onclick = () => {
|
1237 |
+
element.style[propName] = '';
|
1238 |
+
updateCSSPanel(element);
|
1239 |
+
};
|
1240 |
+
|
1241 |
+
propDiv.appendChild(nameSpan);
|
1242 |
+
propDiv.appendChild(valueSpan);
|
1243 |
+
propDiv.appendChild(toggleSpan);
|
1244 |
+
inlineRule.appendChild(propDiv);
|
1245 |
+
}
|
1246 |
+
|
1247 |
+
cssPanel.appendChild(inlineRule);
|
1248 |
}
|
1249 |
+
|
1250 |
+
const computedStyles = window.getComputedStyle(element);
|
1251 |
+
const computedRule = document.createElement('div');
|
1252 |
+
computedRule.className = 'css-rule';
|
1253 |
+
|
1254 |
+
const computedSelector = document.createElement('div');
|
1255 |
+
computedSelector.className = 'css-selector';
|
1256 |
+
computedSelector.textContent = '計算されたスタイル';
|
1257 |
+
computedRule.appendChild(computedSelector);
|
1258 |
+
|
1259 |
+
const importantProps = [
|
1260 |
+
'display', 'position', 'width', 'height', 'margin', 'padding',
|
1261 |
+
'color', 'background', 'border', 'font', 'flex', 'grid'
|
1262 |
+
];
|
1263 |
+
|
1264 |
+
importantProps.forEach(prop => {
|
1265 |
+
const value = computedStyles[prop];
|
1266 |
+
|
1267 |
+
const propDiv = document.createElement('div');
|
1268 |
+
propDiv.className = 'css-property';
|
1269 |
+
|
1270 |
+
const nameSpan = document.createElement('span');
|
1271 |
+
nameSpan.className = 'css-property-name';
|
1272 |
+
nameSpan.textContent = prop;
|
1273 |
+
|
1274 |
+
const valueSpan = document.createElement('span');
|
1275 |
+
valueSpan.className = 'css-property-value';
|
1276 |
+
valueSpan.textContent = value;
|
1277 |
+
|
1278 |
+
propDiv.appendChild(nameSpan);
|
1279 |
+
propDiv.appendChild(valueSpan);
|
1280 |
+
computedRule.appendChild(propDiv);
|
1281 |
+
});
|
1282 |
+
|
1283 |
+
cssPanel.appendChild(computedRule);
|
1284 |
}
|
|
|
|
|
1285 |
|
1286 |
+
// DOMツリー構築
|
1287 |
function buildDOMTree(node, parentElement, depth = 0, isRoot = false) {
|
1288 |
if (node.nodeType === Node.ELEMENT_NODE) {
|
1289 |
const element = document.createElement('div');
|
|
|
1291 |
element.style.marginLeft = `${depth * 15}px`;
|
1292 |
element.dataset.elementId = node.id || Math.random().toString(36).substr(2, 9);
|
1293 |
|
|
|
1294 |
element.oncontextmenu = (e) => {
|
1295 |
e.preventDefault();
|
1296 |
selectedElement = node;
|
|
|
1299 |
document.querySelectorAll('.dom-node').forEach(el => el.classList.remove('selected'));
|
1300 |
element.classList.add('selected');
|
1301 |
|
|
|
1302 |
if (node !== document.documentElement) {
|
1303 |
contextMenu.style.display = 'block';
|
1304 |
contextMenu.style.left = `${e.pageX}px`;
|
|
|
1308 |
updateCSSPanel(node);
|
1309 |
};
|
1310 |
|
|
|
1311 |
element.onclick = (e) => {
|
1312 |
if (e.target.classList.contains('dom-toggle')) return;
|
1313 |
|
|
|
1321 |
updateCSSPanel(node);
|
1322 |
};
|
1323 |
|
|
|
1324 |
const hasChildren = node.childNodes.length > 0 &&
|
1325 |
!(node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE && !node.childNodes[0].textContent.trim());
|
1326 |
|
|
|
1343 |
element.appendChild(toggle);
|
1344 |
}
|
1345 |
|
|
|
1346 |
const tag = document.createElement('span');
|
1347 |
tag.className = 'dom-tag';
|
1348 |
tag.textContent = `<${node.tagName.toLowerCase()}`;
|
|
|
1366 |
|
1367 |
element.appendChild(tag);
|
1368 |
|
|
|
1369 |
Array.from(node.attributes).forEach(attr => {
|
1370 |
const attrSpan = document.createElement('span');
|
1371 |
attrSpan.className = 'dom-attr';
|
|
|
1422 |
parentElement.appendChild(text);
|
1423 |
}
|
1424 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1425 |
|
1426 |
// CSSプロパティ編集
|
1427 |
function editCSSProperty(element, propName, styleType) {
|
|
|
1443 |
}
|
1444 |
}
|
1445 |
|
1446 |
+
// refreshElementsPanel関数定義
|
1447 |
+
refreshElementsPanel = function() {
|
1448 |
+
let tree = document.getElementById('dom-tree');
|
1449 |
+
|
1450 |
+
if (!tree) {
|
1451 |
+
const panel = document.getElementById('elements-panel');
|
1452 |
+
if (panel) {
|
1453 |
+
const container = panel.querySelector('.elements-container');
|
1454 |
+
if (container) {
|
1455 |
+
tree = document.createElement('div');
|
1456 |
+
tree.className = 'dom-tree';
|
1457 |
+
tree.id = 'dom-tree';
|
1458 |
+
container.insertBefore(tree, container.querySelector('.css-panel'));
|
1459 |
+
}
|
1460 |
+
}
|
1461 |
+
}
|
1462 |
+
|
1463 |
+
if (!tree) return;
|
1464 |
+
|
1465 |
+
tree.innerHTML = '';
|
1466 |
+
buildDOMTree(document.documentElement, tree, 0, true);
|
1467 |
+
|
1468 |
+
if (selectedElement) {
|
1469 |
+
const elementId = selectedElement.id || Array.from(selectedElement.attributes)
|
1470 |
+
.find(attr => attr.name.startsWith('data-element-id'))?.value;
|
1471 |
+
|
1472 |
+
if (elementId) {
|
1473 |
+
const node = document.querySelector(`[data-element-id="${elementId}"]`);
|
1474 |
+
if (node) {
|
1475 |
+
node.classList.add('selected');
|
1476 |
+
updateCSSPanel(selectedElement);
|
1477 |
+
}
|
1478 |
+
}
|
1479 |
+
}
|
1480 |
+
};
|
1481 |
|
1482 |
+
// MutationObserverを設定
|
1483 |
+
setupMutationObserver();
|
1484 |
+
|
1485 |
+
// 初期表示
|
1486 |
setTimeout(() => {
|
1487 |
refreshElementsPanel();
|
1488 |
}, 0);
|
1489 |
|
1490 |
return panel;
|
1491 |
}
|
1492 |
+
|
1493 |
// 開発者ツール表示/非表示
|
1494 |
function toggleDevTools() {
|
1495 |
const container = document.getElementById('devtools-container');
|