soiz1 commited on
Commit
d32453d
·
verified ·
1 Parent(s): 9760483

Update dev-tools.js

Browse files
Files changed (1) hide show
  1. dev-tools.js +191 -719
dev-tools.js CHANGED
@@ -36,519 +36,7 @@
36
  --dom-text: #d4d4d4;
37
  }
38
 
39
- .devtools-container {
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
- let observer = new MutationObserver((mutations) => {
571
- mutations.forEach((mutation) => {
572
- if (mutation.type === 'childList') {
573
- mutation.addedNodes.forEach((node) => {
574
- if (node.nodeType === Node.ELEMENT_NODE) {
575
- highlightNode(node);
576
- }
577
- });
 
 
 
 
 
578
  }
579
  });
580
- refreshElementsPanel();
581
- });
582
- const loadWebVitals = () => {
583
- return new Promise((resolve) => {
584
- if (window.webVitals) {
585
- resolve();
586
- return;
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().then(() => {
780
- if (window.webVitals) {
781
- try {
782
- // 新しいバージョンのWeb Vitalsではメソッド名が異なる可能性がある
783
- if (window.webVitals.onCLS) {
784
- window.webVitals.onCLS(updateCLS);
785
- window.webVitals.onFCP(updateFCP);
786
- window.webVitals.onFID(updateFID);
787
- window.webVitals.onLCP(updateLCP);
788
- window.webVitals.onTTFB(updateTTFB);
789
- } else if (window.webVitals.getCLS) { // 古いバージョンの場合
790
- window.webVitals.getCLS(updateCLS);
791
- window.webVitals.getFCP(updateFCP);
792
- window.webVitals.getFID(updateFID);
793
- window.webVitals.getLCP(updateLCP);
794
- window.webVitals.getTTFB(updateTTFB);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  }
796
- } catch (e) {
797
- console.error('Web Vitals error:', e);
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
- // Elementsパネル作成
 
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
- // この関数を createElementsPanel() の外に移動し、最初に定義する
1720
- function refreshElementsPanel() {
1721
- let tree = document.getElementById('dom-tree');
1722
-
1723
- if (!tree) {
1724
- const panel = document.getElementById('elements-panel');
1725
- if (panel) {
1726
- const container = panel.querySelector('.elements-container');
1727
- if (container) {
1728
- tree = document.createElement('div');
1729
- tree.className = 'dom-tree';
1730
- tree.id = 'dom-tree';
1731
- container.insertBefore(tree, container.querySelector('.css-panel'));
1732
- }
1733
- }
1734
- }
1735
-
1736
- if (!tree) return;
1737
-
1738
- tree.innerHTML = '';
1739
- buildDOMTree(document.documentElement, tree, 0, true);
1740
-
1741
- if (selectedElement) {
1742
- const elementId = selectedElement.id || Array.from(selectedElement.attributes)
1743
- .find(attr => attr.name.startsWith('data-element-id'))?.value;
1744
-
1745
- if (elementId) {
1746
- const node = document.querySelector(`[data-element-id="${elementId}"]`);
1747
- if (node) {
1748
- node.classList.add('selected');
1749
- updateCSSPanel(selectedElement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1750
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1751
  }
1752
- }
1753
- }
1754
 
1755
- // DOMツリー構築
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
- // DOM変更を監視
2008
- observer.observe(document.documentElement, {
2009
- childList: true,
2010
- subtree: true,
2011
- attributes: true,
2012
- characterData: true
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');