<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Google Chrome - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Google Chrome - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 14 Jun 2026 17:07:17 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/chrome/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Develop Chrome Extensions using Plasmo [Full Handbook] ]]>
                </title>
                <description>
                    <![CDATA[ Chrome extensions are lightweight tools that enhance and personalize your browsing experience, whether that's managing passwords, translating pages, or adding entirely new features to websites you use ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-develop-chrome-extensions-using-plasmo-handbook/</link>
                <guid isPermaLink="false">6a0237edfca21b0d4b636175</guid>
                
                    <category>
                        <![CDATA[ chrome extension ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Preston Mayieka ]]>
                </dc:creator>
                <pubDate>Mon, 11 May 2026 20:11:25 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e0d0bca4-a2e8-495a-9c1c-4f0b9ef52630.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Chrome extensions are lightweight tools that enhance and personalize your browsing experience, whether that's managing passwords, translating pages, or adding entirely new features to websites you use every day.</p>
<p>Millions of developers have published extensions to the Chrome Web Store, and building one is more approachable than you might think.</p>
<p>In this handbook you'll go from zero to a published Chrome extension using TypeScript, React, and Plasmo, a modern framework that handles the repetitive setup and configuration so you can focus on writing features instead of boilerplate.</p>
<p>Along the way you'll touch the real Chrome extension APIs that power production extensions: querying tabs, creating tab groups, and passing messages between different parts of an extension.</p>
<p>By the end you'll have working code, a mental model of how extensions are structured, and everything you need to publish your own ideas to the Chrome Web Store.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-plasmo">What is Plasmo?</a></p>
</li>
<li><p><a href="#heading-what-you-will-build">What You Will Build</a></p>
</li>
<li><p><a href="#heading-what-you-will-learn">What You Will Learn</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a href="#heading-understanding-the-background-script">Understanding the Background Script</a></p>
</li>
<li><p><a href="#heading-building-the-popup-ui">Building the Popup UI</a></p>
</li>
<li><p><a href="#heading-testing-your-extension">Testing Your Extension</a></p>
</li>
<li><p><a href="#heading-next-steps-and-extension-ideas">Next Steps and Extension Ideas</a></p>
</li>
<li><p><a href="#heading-deploying-to-chrome-web-store">Deploying to Chrome Web Store</a></p>
</li>
</ul>
<h2 id="heading-what-is-plasmo">What is Plasmo?</h2>
<p><a href="https://www.plasmo.com/">Plasmo</a> is an open-source framework for building browser extensions. Think of it as the equivalent of Create React App or Next.js, but for Chrome extensions.</p>
<p>Without Plasmo, building a Chrome extension requires manually writing a <code>manifest.json</code> file, wiring up build tooling, and configuring TypeScript and React yourself. Plasmo handles all of that.</p>
<p>A single command scaffolds a working project with TypeScript and React already configured. It reads your <code>package.json</code> and generates the <code>manifest.json</code> Chrome requires, so you never edit it directly.</p>
<p>Moreover, changes to your source files automatically rebuild and reload the extension in Chrome during development, and full type safety including types for Chrome's own APIs is available out of the box.</p>
<p>Plasmo doesn't hide the Chrome extension concepts from you. You still use <code>chrome.tabs</code>, <code>chrome.runtime</code>, and the rest of the Chrome APIs directly. It just removes the tedious scaffolding so you can start building immediately.</p>
<h2 id="heading-what-you-will-build">What You Will Build</h2>
<p>In this tutorial, you'll build a <strong>Tab Grouper</strong> Chrome extension from scratch.</p>
<p>This extension automatically organizes your browser tabs by grouping them based on their website domain.</p>
<img src="https://cdn.hashnode.com/uploads/covers/64ef9ca6a3a26476fe998b69/43f51cde-41c8-46ac-9305-6b4ad5adc1ac.gif" alt="Animated demo of the Tab Grouper extension grouping open tabs into colored groups by domain" style="display:block;margin:0 auto" width="800" height="520" loading="lazy">

<h3 id="heading-example-use-case">Example Use Case</h3>
<p>Imagine you have 20 tabs open: 5 from GitHub, 4 from YouTube, 3 from Stack Overflow, and 8 from other websites.</p>
<p>With one click, the Tab Grouper extension will automatically create colored groups for each website, making it straightforward to find and manage your tabs.</p>
<h2 id="heading-what-you-will-learn">What You Will Learn</h2>
<p>By completing this tutorial, you'll get hands-on experience in three areas.</p>
<p>First, <strong>Chrome Extension Basics</strong>: how extensions work under the hood, the anatomy of an extension (manifest, background scripts, popups), and how to load and test extensions in Chrome during development.</p>
<p>Second, <strong>Chrome APIs</strong>: specifically <code>chrome.tabs</code> for managing browser tabs, <code>chrome.tabGroups</code> for creating and customizing tab groups, and <code>chrome.runtime</code> for passing messages between different parts of your extension.</p>
<p>Third, <strong>Modern Web Development tooling</strong>: TypeScript for type-safe JavaScript, React for building the popup UI, and the Plasmo framework that ties it all together.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You don't need to be an expert in any of these, but you'll have the smoothest experience if you're comfortable with basic JavaScript or TypeScript and have a general understanding of HTML and CSS.</p>
<p>Some familiarity with React is helpful but not required. The pop-up component we'll build is simple enough to follow even if you're new to it.</p>
<p>On the software side, you'll need Node.js version 18 or higher (<a href="https://nodejs.org/">download here</a>), Google Chrome, a code editor (VS Code is recommended), and pnpm as your package manager.</p>
<h3 id="heading-verify-your-setup">Verify Your Setup</h3>
<p>Open your terminal and run these commands to confirm everything is installed:</p>
<pre><code class="language-bash">node --version
# Should output v18.0.0 or higher

npm --version
# Should output 9.0.0 or higher
</code></pre>
<h3 id="heading-getting-help">Getting Help</h3>
<p>If you get stuck, review the complete code in the repository, consult the Chrome Extension documentation, or ask for help in the community forums.</p>
<h3 id="heading-ready-to-begin">Ready to Begin?</h3>
<p>In the next section, you'll set up your development environment and create your first Chrome extension project.</p>
<p>Let's get started!</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>In this section, you'll use Plasmo to scaffold your Chrome extension project, then customize it for the Tab Grouper.</p>
<p>Rather than creating files manually, you'll let Plasmo generate a starter project with all required configuration, then explore what was created before customizing it for our needs.</p>
<h2 id="heading-step-1-install-pnpm-recommended">Step 1: Install pnpm (Recommended)</h2>
<p>Plasmo officially recommends <strong>pnpm</strong> for faster installs and better disk space usage. Check if you already have it:</p>
<pre><code class="language-bash">pnpm --version
</code></pre>
<p>If you see a version number, skip to Step 2.</p>
<img src="https://cdn.hashnode.com/uploads/covers/64ef9ca6a3a26476fe998b69/aeed7b06-a403-4fe2-81fe-571a00219acf.png" alt="Terminal output showing pnpm version number after running pnpm --version" style="display:block;margin:0 auto" width="1126" height="460" loading="lazy">

<p>If you get "command not found", install it with:</p>
<pre><code class="language-bash">npm install -g pnpm
</code></pre>
<h2 id="heading-step-2-create-your-extension-project">Step 2: Create Your Extension Project</h2>
<p>Run this command to create a new Plasmo project:</p>
<pre><code class="language-bash">pnpm create plasmo tab-grouper
</code></pre>
<p>You'll see:</p>
<pre><code class="language-plaintext">🟣 Creating a new Plasmo extension
📁 Project name: tab-grouper
? Extension description: (Give your extension a nice description)
? Author name: (Your Name)
</code></pre>
<p>Plasmo will then scaffold the project and install dependencies automatically. You might be prompted to enter a description and author name.</p>
<p>Fill these in however you like.</p>
<img src="https://cdn.hashnode.com/uploads/covers/64ef9ca6a3a26476fe998b69/e0a58818-0bec-42a7-bde3-c7a66de68b7a.png" alt="Terminal output showing Plasmo scaffolding a new project called tab-grouper and installing dependencies." style="display:block;margin:0 auto" width="1652" height="530" loading="lazy">

<h3 id="heading-step-3-navigate-to-your-project">Step 3: Navigate to Your Project</h3>
<pre><code class="language-bash">cd tab-grouper
</code></pre>
<h3 id="heading-step-4-explore-what-was-created">Step 4: Explore What Was Created</h3>
<p>List the files that Plasmo generated:</p>
<pre><code class="language-bash">ls -la
</code></pre>
<p>You should see something like this:</p>
<pre><code class="language-plaintext">tab-grouper/
├── .git/                 # Git repository (already initialized!)
├── .github/              # GitHub Actions workflows
├── assets/
│   └── icon.png          # Default Plasmo icon 
├── node_modules/         # Dependencies (already installed!)
├── package.json          # Project configuration
├── popup.tsx             # Default popup 
├── .prettierrc.cjs       # Code formatting rules
├── .gitignore            # Git ignore rules
├── README.md             # Default readme
└── tsconfig.json         # TypeScript configuration
</code></pre>
<p>The key files to know about:</p>
<ul>
<li><p><strong>assets/icon.png</strong>: The extension icon required by Chrome.</p>
</li>
<li><p><strong>package.json</strong>: Lists dependencies and scripts, and is where you configure the extension manifest.</p>
</li>
<li><p><strong>popup.tsx</strong>: The UI that appears when you click the extension icon.</p>
</li>
<li><p><strong>tsconfig.json</strong>: Contains TypeScript settings that are already correctly configured.</p>
</li>
</ul>
<h3 id="heading-step-5-test-the-default-extension">Step 5: Test the Default Extension</h3>
<p>Make sure everything works <strong>before</strong> you customize it.</p>
<p>You can do this by starting the development server:</p>
<pre><code class="language-bash">pnpm dev
</code></pre>
<p>You should see output like this:</p>
<pre><code class="language-plaintext">🟣 Plasmo v0.90.5
🔴 The Browser Extension Framework
🔵 INFO   | Starting the extension development server...
🔵 INFO   | Building for target: chrome-mv3
🔵 INFO   | Loaded environment variables from: []
🟢 DONE   | Extension re-packaged in 1842ms! 🚀

View Extension:
📦 build/chrome-mv3-dev
</code></pre>
<p>Your extension is ready. Keep this terminal window open.</p>
<p>Plasmo watches for file changes and rebuilds automatically.</p>
<h3 id="heading-step-6-load-the-extension-in-chrome">Step 6: Load the Extension in Chrome</h3>
<p>Now load the extension into Chrome to test it:</p>
<ol>
<li><p>Open Google Chrome</p>
</li>
<li><p>Go to <code>chrome://extensions/</code></p>
</li>
<li><p>Enable <strong>Developer mode</strong> (toggle in top-right)</p>
</li>
<li><p>Click <strong>"Load unpacked"</strong></p>
</li>
<li><p>Navigate to your project folder</p>
</li>
<li><p>Select the <code>build/chrome-mv3-dev</code> folder</p>
</li>
<li><p>Click "Select Folder"</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/64ef9ca6a3a26476fe998b69/19cef596-a9d1-4709-8d27-594381d03842.gif" alt="Animated gif showing how to load an unpacked extension in Chrome via the Extensions page developer mode" style="display:block;margin:0 auto" width="800" height="461" loading="lazy">

<p>Your extension should now appear in the list.</p>
<h3 id="heading-step-7-test-the-default-popup">Step 7: Test the Default Popup</h3>
<ol>
<li><p>Click the puzzle piece icon in Chrome's toolbar</p>
</li>
<li><p>Find "tab-grouper" and pin it</p>
</li>
<li><p>Click the extension icon</p>
</li>
</ol>
<p>You will see a default popup that says "Welcome to Plasmo!"</p>
<img src="https://cdn.hashnode.com/uploads/covers/64ef9ca6a3a26476fe998b69/56bad298-b07e-41c5-a648-49e382e0c51b.png" alt="The default Plasmo popup showing a Welcome to Plasmo message in the Chrome toolbar popup" style="display:block;margin:0 auto" width="846" height="616" loading="lazy">

<p>The extension is working. Now you can customize it.</p>
<h3 id="heading-step-8-update-extension-information">Step 8: Update Extension Information</h3>
<p>Open <code>package.json</code> in your editor. This file stores metadata about your project. name, version, description, dependencies, and scripts for building and running your extension.</p>
<p>Find these lines near the top:</p>
<pre><code class="language-json">{
  "name": "tab-grouper",
  "displayName": "tab-grouper",
  "version": "0.0.0",
  "description": "A basic Plasmo extension.",
</code></pre>
<p>Change them to:</p>
<pre><code class="language-json">{
  "name": "tab-grouper",
  "displayName": "Tab Grouper",
  "version": "1.0.0",
  "description": "A simple Chrome extension - group tabs by domain",
</code></pre>
<p>Save the file.</p>
<h3 id="heading-step-9-add-required-permissions-critical">Step 9: Add Required Permissions (Critical!)</h3>
<p><strong>This is a critical step.</strong> Without permissions, your extension will fail with errors like:</p>
<pre><code class="language-plaintext">TypeError: Cannot read properties of undefined (reading 'query')
</code></pre>
<p>Chrome extensions must declare which browser APIs they intend to use. In <code>package.json</code>, find the <code>"manifest"</code> section.</p>
<p>It looks like this:</p>
<pre><code class="language-json">"manifest": {
  "host_permissions": [
    "https://*/*"
  ]
}
</code></pre>
<p>Replace it with:</p>
<pre><code class="language-json">"manifest": {
  "permissions": [
    "tabs",
    "tabGroups"
  ]
}
</code></pre>
<p>Save the file. The <code>tabs</code> permission allows you to read tab information (required for <code>chrome.tabs.query()</code>), and <code>tabGroups</code> allows you to create and manage tab groups (required for <code>chrome.tabGroups.update()</code>).</p>
<h3 id="heading-finding-the-right-permissions-for-your-own-extensions">Finding the right permissions for your own extensions:</h3>
<p>The <a href="https://developer.chrome.com/docs/extensions/reference/permissions-list">Chrome Extension Permissions Reference</a> lists every available permission and what it unlocks.</p>
<p>Each API's documentation page also lists which permissions it requires, for example, the <a href="https://developer.chrome.com/docs/extensions/reference/api/tabs">chrome.tabs API page</a> specifies the <code>"tabs"</code> permission.</p>
<p>If you're using Plasmo, the <a href="https://docs.plasmo.com/framework/customization/manifest">Manifest Configuration docs</a> explain how to add permissions through <code>package.json</code>.</p>
<p>As a general rule: if you're getting <code>undefined</code> errors when calling a Chrome API, a missing permission is the first thing to check.</p>
<h3 id="heading-step-10-verify-hot-reload-works">Step 10: Verify Hot Reload Works</h3>
<p>Plasmo automatically reloads your extension when you save changes.</p>
<p>Check the terminal where <code>pnpm dev</code> is running. After saving <code>package.json</code> you should see something like:</p>
<pre><code class="language-plaintext">🔄 Reloading extension...
✅ Ready in 0.8s
</code></pre>
<p>Your project is now ready: a working extension loaded in Chrome, a development server running with hot reload, and the required permissions in place.</p>
<p>Leave the dev server running and the extension loaded as you work through the next sections. Your changes will reload automatically.</p>
<h3 id="heading-section-summary">Section Summary</h3>
<p>In this section you installed pnpm, scaffolded a new extension with <code>pnpm create plasmo</code>, explored the generated project structure, started the development server, loaded the extension in Chrome, and updated the extension metadata and permissions.</p>
<p><strong>Next:</strong> You'll create the background script that handles the tab grouping logic.</p>
<h2 id="heading-understanding-the-background-script">Understanding the Background Script</h2>
<p>The background script is the heart of your extension. It runs persistently behind the scenes and contains the core logic.</p>
<p>In this case, the code that groups your tabs by domain.</p>
<h3 id="heading-what-is-a-background-script">What is a Background Script?</h3>
<p>A background script runs continuously even when the popup is closed.</p>
<p>It can listen to browser events like tabs opening, closing, or updating, perform tasks that don't require direct user interaction, and communicate with other parts of the extension by passing messages.</p>
<p>Think of it as the server-side of your extension. The popup is just a UI that talks to it.</p>
<h3 id="heading-step-1-create-backgroundts">Step 1: Create background.ts</h3>
<p>Plasmo's scaffolding didn't create a background script by default, so you'll create this file from scratch. Create a new file called <code>background.ts</code> in your project root (the same level as <code>popup.tsx</code>):</p>
<pre><code class="language-typescript">export {}

// Background script - runs in the background and handles tab grouping logic

console.log("Tab Grouper background script loaded!")

// Listen for messages from the popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) =&gt; {
  if (message.type === "GROUP_TABS") {
    groupTabsByDomain()
    sendResponse({ success: true })
  }
  return true
})
</code></pre>
<p>The <code>export {}</code> at the top is required by Plasmo to treat this file as a module. Without it you may get errors about conflicting global variable declarations.</p>
<p>The <code>console.log</code> will help you verify the script loaded correctly (you'll see it in the extension's DevTools console). <code>chrome.runtime.onMessage</code> sets up a listener so the background script can receive instructions from the popup.</p>
<p>When it receives a <code>"GROUP_TABS"</code> message, it calls the grouping function.</p>
<p>You can read more about this messaging pattern in the <a href="https://developer.chrome.com/docs/extensions/develop/concepts/messaging">Chrome Extensions documentation</a>.</p>
<h3 id="heading-step-2-implement-tab-grouping-logic">Step 2: Implement Tab Grouping Logic</h3>
<p>Now add the main grouping function below the message listener:</p>
<pre><code class="language-typescript">async function groupTabsByDomain() {
  try {
    // Step 1: Get all tabs in the current window
    const tabs = await chrome.tabs.query({ currentWindow: true })

    // Step 2: Create a Map to organize tabs by domain
    const domainGroups = new Map&lt;string, chrome.tabs.Tab[]&gt;()

    // Step 3: Loop through each tab and group by domain
    tabs.forEach(tab =&gt; {
      // Skip tabs without URLs
      if (!tab.url) return

      // Extract the domain from the URL
      const domain = getDomainFromUrl(tab.url)

      // Skip invalid domains (like chrome:// pages)
      if (!domain) return

      // Add tab to the appropriate domain group
      if (!domainGroups.has(domain)) {
        domainGroups.set(domain, [])
      }
      domainGroups.get(domain)!.push(tab)
    })

    // Step 4: Create tab groups for each domain (only if 2+ tabs)
    for (const [domain, domainTabs] of domainGroups) {
      // Skip domains with only 1 tab
      if (domainTabs.length &lt; 2) continue

      // Get all tab IDs
      const tabIds = domainTabs
        .map(t =&gt; t.id!)
        .filter(id =&gt; id !== undefined)

      if (tabIds.length === 0) continue

      // Create the tab group
      const groupId = await chrome.tabs.group({ tabIds })

      // Customize the group with a title and color
      await chrome.tabGroups.update(groupId, {
        title: domain,
        color: getColorForDomain(domain) // Randomized Tab Group colors.
      })
    }

    console.log(`Successfully grouped ${domainGroups.size} domains`)
  } catch (error) {
    console.error("Error grouping tabs:", error)
  }
}
</code></pre>
<p>The function starts by querying all tabs in the current window, then iterates over them to build a <code>Map</code> keyed by domain name.</p>
<p>Once every tab has been sorted into a domain bucket, it loops through the map and calls <code>chrome.tabs.group()</code> for any domain that has two or more tabs, then immediately customizes the resulting group with a title and color.</p>
<p>Domains with only a single tab are skipped. There's no point grouping a lone tab.</p>
<h3 id="heading-step-3-extract-domain-helper">Step 3: Extract Domain Helper</h3>
<p>Add a helper function to pull the hostname out of a URL:</p>
<pre><code class="language-typescript">function getDomainFromUrl(url: string): string | null {
  try {
    const urlObj = new URL(url)

    // Skip Chrome internal pages (chrome://, chrome-extension://)
    if (urlObj.protocol === "chrome:" || urlObj.protocol === "chrome-extension:") {
      return null
    }

    // Remove "www." prefix and return the hostname
    return urlObj.hostname.replace(/^www\./, "")
  } catch {
    // Return null if URL is invalid
    return null
  }
}
</code></pre>
<p><code>new URL(url)</code> gives us a structured object to work with rather than string-parsing the URL manually.</p>
<p>The protocol check filters out Chrome's internal pages like <code>chrome://extensions</code> and <code>chrome://settings</code>, which extensions can't access.</p>
<p>The <code>.replace(/^www\./, "")</code> ensures that <code>www.github.com</code> and <code>github.com</code> are treated as the same domain rather than two separate groups.</p>
<p>The whole thing is wrapped in a try-catch so malformed URLs simply return <code>null</code> and get skipped.</p>
<p>In practice: <code>https://www.github.com/user/repo</code> becomes <code>github.com</code>, <code>https://youtube.com/watch?v=123</code> becomes <code>youtube.com</code>, and <code>chrome://extensions</code> returns <code>null</code>.</p>
<h3 id="heading-step-4-color-assignment-helper">Step 4: Color Assignment Helper</h3>
<p>Add a function to deterministically assign a color to each domain:</p>
<pre><code class="language-typescript">function getColorForDomain(domain: string): chrome.tabGroups.ColorEnum {
  // Available colors in Chrome
  const colors: chrome.tabGroups.ColorEnum[] = [
    "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"
  ]

  // Create a simple hash from the domain name
  let hash = 0
  for (let i = 0; i &lt; domain.length; i++) {
    hash = domain.charCodeAt(i) + ((hash &lt;&lt; 5) - hash)
  }

  // Return a color based on the hash
  return colors[Math.abs(hash) % colors.length]
}
</code></pre>
<p>Chrome supports eight colors for tab groups. Rather than assigning them randomly (which would change every time you group), this function hashes the domain name to a number and uses the modulo operator to pick a consistent index into the color array.</p>
<p>The result is that <code>github.com</code> always gets the same color across sessions, while different domains are likely to get different colors.</p>
<h3 id="heading-complete-backgroundts-file">Complete background.ts File</h3>
<p>Your complete <code>background.ts</code> should look like this:</p>
<pre><code class="language-typescript">export {}

console.log("Tab Grouper background script loaded!")

chrome.runtime.onMessage.addListener((message, sender, sendResponse) =&gt; {
  if (message.type === "GROUP_TABS") {
    groupTabsByDomain()
    sendResponse({ success: true })
  }
  return true
})

async function groupTabsByDomain() {
  try {
    const tabs = await chrome.tabs.query({ currentWindow: true })
    const domainGroups = new Map&lt;string, chrome.tabs.Tab[]&gt;()

    tabs.forEach(tab =&gt; {
      if (!tab.url) return
      const domain = getDomainFromUrl(tab.url)
      if (!domain) return

      if (!domainGroups.has(domain)) {
        domainGroups.set(domain, [])
      }
      domainGroups.get(domain)!.push(tab)
    })

    for (const [domain, domainTabs] of domainGroups) {
      if (domainTabs.length &lt; 2) continue

      const tabIds = domainTabs
        .map(t =&gt; t.id!)
        .filter(id =&gt; id !== undefined)

      if (tabIds.length === 0) continue

      const groupId = await chrome.tabs.group({ tabIds })

      await chrome.tabGroups.update(groupId, {
        title: domain,
        color: getColorForDomain(domain)
      })
    }

    console.log(`Successfully grouped ${domainGroups.size} domains`)
  } catch (error) {
    console.error("Error grouping tabs:", error)
  }
}

function getDomainFromUrl(url: string): string | null {
  try {
    const urlObj = new URL(url)
    if (urlObj.protocol === "chrome:" || urlObj.protocol === "chrome-extension:") {
      return null
    }
    return urlObj.hostname.replace(/^www\./, "")
  } catch {
    return null
  }
}

function getColorForDomain(domain: string): chrome.tabGroups.ColorEnum {
  const colors: chrome.tabGroups.ColorEnum[] = [
    "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"
  ]

  let hash = 0
  for (let i = 0; i &lt; domain.length; i++) {
    hash = domain.charCodeAt(i) + ((hash &lt;&lt; 5) - hash)
  }

  return colors[Math.abs(hash) % colors.length]
}
</code></pre>
<h3 id="heading-testing-the-background-script">Testing the Background Script</h3>
<p>If your development server isn't already running from the previous section, start it:</p>
<pre><code class="language-bash">pnpm dev
</code></pre>
<p>To verify the background script loaded correctly, go to <code>chrome://extensions</code>, find "Tab Grouper Tutorial", and click the <strong>"service worker"</strong> link.</p>
<p>A DevTools console will open and you should see "Tab Grouper background script loaded!" confirming everything is wired up.</p>
<h2 id="heading-building-the-popup-ui">Building the Popup UI</h2>
<p>The popup is the small window that appears when a user clicks your extension icon in the Chrome toolbar.</p>
<p>It can display information, provide buttons for actions, and show settings.</p>
<p>In this section you'll build a React-based popup that shows live tab statistics and triggers the grouping logic in the background script.</p>
<h3 id="heading-step-1-replace-popuptsx">Step 1: Replace popup.tsx</h3>
<p>When you ran <code>pnpm create plasmo</code>, a default <code>popup.tsx</code> was created that just displays a welcome message.</p>
<p>Open that file and replace <strong>all</strong> of its contents with this starting skeleton:</p>
<pre><code class="language-tsx">import { useState, useEffect } from "react"

function IndexPopup() {
  const [tabCount, setTabCount] = useState(0)
  const [groupCount, setGroupCount] = useState(0)
  const [isGrouping, setIsGrouping] = useState(false)

  return (
    &lt;div&gt;
      &lt;h2&gt;Tab Grouper&lt;/h2&gt;
      &lt;button&gt;Group Tabs&lt;/button&gt;
    &lt;/div&gt;
  )
}

export default IndexPopup
</code></pre>
<p>Save the file and the extension will automatically reload.</p>
<p>The three state variables track the number of open tabs, the number of existing groups, and whether a grouping operation is currently in progress.</p>
<p>That last one lets us disable the button and show a loading state so users can't trigger multiple groupings at once.</p>
<h3 id="heading-step-2-load-statistics">Step 2: Load Statistics</h3>
<p>Now add the logic to load tab and group counts when the popup opens. Add this inside the <code>IndexPopup</code> function, right after the state declarations:</p>
<pre><code class="language-tsx">// Load tab statistics when popup opens
useEffect(() =&gt; {
  loadStats()
}, [])

async function loadStats() {
  const tabs = await chrome.tabs.query({ currentWindow: true })
  const groups = await chrome.tabGroups.query({
    windowId: chrome.windows.WINDOW_ID_CURRENT
  })

  setTabCount(tabs.length)
  setGroupCount(groups.length)
}
</code></pre>
<p>The <code>useEffect</code> with an empty dependency array <code>[]</code> runs once when the component first mounts. In other words, every time the popup opens.</p>
<p>It calls <code>loadStats</code>, which queries Chrome for the current window's tabs and groups, then updates the state variables with the counts.</p>
<h3 id="heading-step-3-trigger-tab-grouping">Step 3: Trigger Tab Grouping</h3>
<p>Add the handler that sends a message to the background script when the button is clicked:</p>
<pre><code class="language-tsx">async function handleGroupTabs() {
  setIsGrouping(true)

  // Send message to background script
  await chrome.runtime.sendMessage({ type: "GROUP_TABS" })

  // Refresh statistics
  await loadStats()
  setIsGrouping(false)
}
</code></pre>
<p><code>chrome.runtime.sendMessage</code> delivers the <code>{ type: "GROUP_TABS" }</code> message to the listener we set up in <code>background.ts</code>.</p>
<p>After the background script finishes, we reload the statistics so the group count updates immediately, then re-enable the button.</p>
<h3 id="heading-step-4-build-the-ui">Step 4: Build the UI</h3>
<p>Replace the placeholder <code>return</code> statement with this complete, styled version:</p>
<pre><code class="language-tsx">return (
  &lt;div style={{
    width: 300,
    padding: 20,
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
  }}&gt;
    {/* Header */}
    &lt;div style={{ marginBottom: 20 }}&gt;
      &lt;h2 style={{ margin: 0, fontSize: 20, fontWeight: 600 }}&gt;
        🗂️ Tab Grouper
      &lt;/h2&gt;
      &lt;p style={{ margin: "8px 0 0", fontSize: 13, color: "#666" }}&gt;
        Organize your tabs by domain
      &lt;/p&gt;
    &lt;/div&gt;

    {/* Statistics */}
    &lt;div style={{
      display: "flex",
      gap: 12,
      marginBottom: 20,
      padding: 12,
      background: "#f5f5f5",
      borderRadius: 8
    }}&gt;
      &lt;div style={{ flex: 1 }}&gt;
        &lt;div style={{ fontSize: 24, fontWeight: 600, color: "#333" }}&gt;
          {tabCount}
        &lt;/div&gt;
        &lt;div style={{ fontSize: 12, color: "#666" }}&gt;
          Open Tabs
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div style={{ flex: 1 }}&gt;
        &lt;div style={{ fontSize: 24, fontWeight: 600, color: "#0066ff" }}&gt;
          {groupCount}
        &lt;/div&gt;
        &lt;div style={{ fontSize: 12, color: "#666" }}&gt;
          Tab Groups
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;

    {/* Group Button */}
    &lt;button
      onClick={handleGroupTabs}
      disabled={isGrouping}
      style={{
        width: "100%",
        padding: "12px 16px",
        fontSize: 14,
        fontWeight: 500,
        color: "white",
        background: isGrouping ? "#ccc" : "#0066ff",
        border: "none",
        borderRadius: 8,
        cursor: isGrouping ? "not-allowed" : "pointer",
        transition: "background 0.2s"
      }}
    &gt;
      {isGrouping ? "Grouping..." : "🗂️ Group Tabs by Domain"}
    &lt;/button&gt;

    {/* Footer */}
    &lt;div style={{
      marginTop: 16,
      padding: 12,
      fontSize: 12,
      color: "#666",
      background: "#fff9e6",
      borderRadius: 6,
      border: "1px solid #ffe066"
    }}&gt;
      💡 &lt;strong&gt;Tip:&lt;/strong&gt; This will group all tabs in this window by their website domain.
    &lt;/div&gt;
  &lt;/div&gt;
)
</code></pre>
<p>The UI has four parts: a header with the extension title and a short description, a statistics box showing the live tab and group counts side by side, the main action button (which grays out and changes text to "Grouping..." while work is in progress), and a tip box at the bottom.</p>
<p>This tutorial uses inline styles for simplicity. In a production extension, you'd likely reach for CSS modules, Tailwind, or styled-components instead.</p>
<h3 id="heading-complete-popuptsx-file">Complete popup.tsx File</h3>
<p>Your complete <code>popup.tsx</code> should look like this:</p>
<pre><code class="language-tsx">import { useState, useEffect } from "react"

function IndexPopup() {
  const [tabCount, setTabCount] = useState(0)
  const [groupCount, setGroupCount] = useState(0)
  const [isGrouping, setIsGrouping] = useState(false)

  useEffect(() =&gt; {
    loadStats()
  }, [])

  async function loadStats() {
    const tabs = await chrome.tabs.query({ currentWindow: true })
    const groups = await chrome.tabGroups.query({
      windowId: chrome.windows.WINDOW_ID_CURRENT
    })

    setTabCount(tabs.length)
    setGroupCount(groups.length)
  }

  async function handleGroupTabs() {
    setIsGrouping(true)
    await chrome.runtime.sendMessage({ type: "GROUP_TABS" })
    await loadStats()
    setIsGrouping(false)
  }

  return (
    &lt;div style={{
      width: 300,
      padding: 20,
      fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
    }}&gt;
      &lt;div style={{ marginBottom: 20 }}&gt;
        &lt;h2 style={{ margin: 0, fontSize: 20, fontWeight: 600 }}&gt;
          🗂️ Tab Grouper
        &lt;/h2&gt;
        &lt;p style={{ margin: "8px 0 0", fontSize: 13, color: "#666" }}&gt;
          Organize your tabs by domain
        &lt;/p&gt;
      &lt;/div&gt;

      &lt;div style={{
        display: "flex",
        gap: 12,
        marginBottom: 20,
        padding: 12,
        background: "#f5f5f5",
        borderRadius: 8
      }}&gt;
        &lt;div style={{ flex: 1 }}&gt;
          &lt;div style={{ fontSize: 24, fontWeight: 600, color: "#333" }}&gt;
            {tabCount}
          &lt;/div&gt;
          &lt;div style={{ fontSize: 12, color: "#666" }}&gt;
            Open Tabs
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div style={{ flex: 1 }}&gt;
          &lt;div style={{ fontSize: 24, fontWeight: 600, color: "#0066ff" }}&gt;
            {groupCount}
          &lt;/div&gt;
          &lt;div style={{ fontSize: 12, color: "#666" }}&gt;
            Tab Groups
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;button
        onClick={handleGroupTabs}
        disabled={isGrouping}
        style={{
          width: "100%",
          padding: "12px 16px",
          fontSize: 14,
          fontWeight: 500,
          color: "white",
          background: isGrouping ? "#ccc" : "#0066ff",
          border: "none",
          borderRadius: 8,
          cursor: isGrouping ? "not-allowed" : "pointer",
          transition: "background 0.2s"
        }}
      &gt;
        {isGrouping ? "Grouping..." : "🗂️ Group Tabs by Domain"}
      &lt;/button&gt;

      &lt;div style={{
        marginTop: 16,
        padding: 12,
        fontSize: 12,
        color: "#666",
        background: "#fff9e6",
        borderRadius: 6,
        border: "1px solid #ffe066"
      }}&gt;
        💡 &lt;strong&gt;Tip:&lt;/strong&gt; This will group all tabs in this window by their website domain.
      &lt;/div&gt;
    &lt;/div&gt;
  )
}

export default IndexPopup
</code></pre>
<h2 id="heading-testing-your-extension">Testing Your Extension</h2>
<p>Now that you have both the background script and popup UI built, it's time to verify that everything works together in Chrome.</p>
<h3 id="heading-step-1-make-sure-the-dev-server-is-running">Step 1: Make Sure the Dev Server is Running</h3>
<p>If <code>pnpm dev</code> isn't already running from an earlier step, start it now:</p>
<pre><code class="language-bash">pnpm run dev # or pnpm dev
</code></pre>
<p>Plasmo will build the extension into <code>build/chrome-mv3-dev</code> and watch for changes.</p>
<h3 id="heading-step-2-load-the-extension-in-chrome">Step 2: Load the Extension in Chrome</h3>
<p>If you haven't already loaded the extension, go to <code>chrome://extensions/</code>, enable <strong>Developer mode</strong>, click <strong>Load unpacked</strong>, and select the <code>build/chrome-mv3-dev</code> folder.</p>
<p>Once loaded you should see the extension listed with the name "Tab Grouper Tutorial", version "1.0.0", and status Enabled.</p>
<h3 id="heading-step-3-pin-the-extension">Step 3: Pin the Extension</h3>
<p>Click the puzzle piece icon in the Chrome toolbar, find "Tab Grouper Tutorial", and click the pin icon to keep it visible.</p>
<p>The extension icon will now appear directly in your toolbar.</p>
<h3 id="heading-step-4-test-the-extension">Step 4: Test the Extension</h3>
<h4 id="heading-test-1-open-multiple-tabs">Test 1: Open Multiple Tabs</h4>
<p>Open several tabs across a few domains so there's something to group:</p>
<ol>
<li><p><code>https://github.com/topics</code>, <code>https://github.com/trending</code>, <code>https://github.com/explore</code></p>
</li>
<li><p><code>https://www.youtube.com/</code> and <code>https://www.youtube.com/trending</code></p>
</li>
<li><p><code>https://stackoverflow.com/questions</code> and <code>https://stackoverflow.com/tags</code></p>
</li>
</ol>
<p>Have at least 7 tabs open.</p>
<h4 id="heading-test-2-group-the-tabs">Test 2: Group the Tabs</h4>
<p>Click the Tab Grouper extension icon. The popup should appear showing your open tab count (7 or more) and group count (probably 0).</p>
<p>Click <strong>"Group Tabs by Domain"</strong> and watch your tabs get organized into colored groups.</p>
<h4 id="heading-test-3-verify-groups">Test 3: Verify Groups</h4>
<p>After clicking the button, GitHub tabs should be grouped together with a label like "github.com" and a consistent color, and YouTube tabs similarly.</p>
<p>Click the extension icon again, the group count should now show 2, while the tab count stays the same.</p>
<h3 id="heading-step-5-debug-the-extension">Step 5: Debug the Extension</h3>
<p>If something doesn't work, Chrome's DevTools are your best friend.</p>
<p>To inspect the background script, go to <code>chrome://extensions/</code>, find your extension, and click the <strong>"service worker"</strong> link.</p>
<p>A DevTools console opens where you can look for the "Tab Grouper background script loaded!" message and any error output in red.</p>
<p>To inspect the popup, right-click the extension icon and select <strong>"Inspect popup"</strong>. This opens DevTools for the popup specifically — check the Console tab for any errors there.</p>
<p><strong>If nothing happens when you click the button</strong>, check the background script console for errors, confirm you have at least 2 tabs from the same domain, and verify the message is being sent (look in the popup console for any <code>sendMessage</code> failures).</p>
<p><strong>If tabs aren't grouping</strong>, double-check that you added the <code>tabs</code> and <code>tabGroups</code> permissions to <code>package.json</code> and reloaded the extension after saving.</p>
<p><strong>If you see "Extension cannot access chrome://..."</strong>, that's expected behavior — extensions can't interact with Chrome's internal pages and the code skips them intentionally.</p>
<h3 id="heading-step-6-hot-reloading">Step 6: Hot Reloading</h3>
<p>One of the benefits of Plasmo is hot reloading, which allows you to update code in a running app instantly without needing to restart it manually.</p>
<p>Open <code>popup.tsx</code>, change the header emoji from 🗂️ to 📁, and save.</p>
<p>The extension reloads automatically.</p>
<p>Click the icon and you'll see the updated emoji immediately.</p>
<p>Hot reloading is advantageous because it speeds up development by letting you see changes in real time.</p>
<p>You can change the emoji back afterward if you'd like to keep the extension consistent with the rest of the tutorial examples and screenshots.</p>
<h3 id="heading-step-7-test-edge-cases">Step 7: Test Edge Cases</h3>
<p>It's worth testing a few scenarios to make sure the extension handles them gracefully.</p>
<p>If you close all tabs except one and click "Group Tabs", nothing should happen. The extension requires at least two tabs from the same domain to form a group. Opening <code>chrome://extensions</code> and <code>chrome://settings</code> and then grouping should also do nothing, since those pages are filtered out.</p>
<p>If you have one tab from <code>reddit.com</code> and one from <code>freecodecamp.org</code>, each domain appearing only once, no groups should be created.</p>
<h3 id="heading-step-8-production-build">Step 8: Production Build</h3>
<p>When you're ready to share your extension, run:</p>
<pre><code class="language-bash">pnpm run build
</code></pre>
<p>This creates a production-optimized version in <code>build/chrome-mv3-prod</code>, minified JavaScript, no development-only code, and smaller file size.</p>
<p>To verify the production build, go to <code>chrome://extensions/</code>, remove the development version, click "Load unpacked", and select <code>build/chrome-mv3-prod</code>. Test thoroughly before publishing.</p>
<p>The extension is lightweight (under 100 KB), only runs when you click the button, and has no background processes when idle.</p>
<h2 id="heading-next-steps-and-extension-ideas">Next Steps and Extension Ideas</h2>
<p>Congratulations on building your first Chrome extension!</p>
<p>You now have a working tool that groups tabs by domain with one click, shows live statistics about open tabs and groups, and is built on modern tooling: TypeScript, React, and Plasmo following Chrome extension best practices.</p>
<p>The extension is a solid foundation. Here are some ideas for where to take it next.</p>
<h3 id="heading-1-auto-grouping">1. Auto-Grouping</h3>
<p>Instead of requiring a button click, you could automatically group new tabs as they're opened. You'd listen for the <code>chrome.tabs.onCreated</code> event in <code>background.ts</code> and trigger <code>groupTabsByDomain()</code> with a short delay to let the page URL load:</p>
<pre><code class="language-typescript">// In background.ts
chrome.tabs.onCreated.addListener(async (tab) =&gt; {
  // Wait a bit for the URL to load
  setTimeout(() =&gt; {
    groupTabsByDomain()
  }, 2000)
})
</code></pre>
<p>This gets into event listeners, asynchronous timing, and thinking carefully about when to fire — a good next step for understanding how background scripts can be more proactive.</p>
<h3 id="heading-2-keyboard-shortcuts">2. Keyboard Shortcuts</h3>
<p>You can trigger grouping without even opening the popup by adding a keyboard shortcut. Add a <code>commands</code> section to the manifest in <code>package.json</code>:</p>
<pre><code class="language-json">"manifest": {
  "commands": {
    "group-tabs": {
      "suggested_key": {
        "default": "Ctrl+Shift+G",
        "mac": "Command+Shift+G"
      },
      "description": "Group tabs by domain"
    }
  }
}
</code></pre>
<p>Then listen for the command in <code>background.ts</code>:</p>
<pre><code class="language-typescript">chrome.commands.onCommand.addListener((command) =&gt; {
  if (command === "group-tabs") {
    groupTabsByDomain()
  }
})
</code></pre>
<h3 id="heading-3-category-based-grouping">3. Category-Based Grouping</h3>
<p>Rather than grouping by raw domain, you could group by category — putting GitHub, Stack Overflow, and npm together in a "Dev" group, for instance:</p>
<pre><code class="language-typescript">const categories = {
  social: ["facebook.com", "twitter.com", "instagram.com"],
  shopping: ["amazon.com", "ebay.com", "etsy.com"],
  dev: ["github.com", "stackoverflow.com", "npmjs.com"]
}

function getCategoryForDomain(domain: string): string {
  for (const [category, domains] of Object.entries(categories)) {
    if (domains.includes(domain)) {
      return category
    }
  }
  return "other"
}
</code></pre>
<h3 id="heading-4-options-page">4. Options Page</h3>
<p>Plasmo makes it trivial to add a settings page by creating an <code>options.tsx</code> file.</p>
<p>This is where you'd let users toggle auto-grouping, choose between domain and category mode, or configure their own category mappings.</p>
<p>It's a good introduction to the Chrome Storage API and persisting user preferences.</p>
<pre><code class="language-tsx">function OptionsPage() {
  return (
    &lt;div&gt;
      &lt;h1&gt;Tab Grouper Settings&lt;/h1&gt;
      &lt;label&gt;
        &lt;input type="checkbox" /&gt;
        Enable auto-grouping
      &lt;/label&gt;
      &lt;label&gt;
        &lt;input type="checkbox" /&gt;
        Group by category instead of domain
      &lt;/label&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<h3 id="heading-5-tab-age-tracking">5. Tab Age Tracking</h3>
<p>You could track when each tab was created and surface tabs that have been sitting untouched for a week or more, a nice way to encourage tab hygiene:</p>
<pre><code class="language-typescript">// Track tab creation times
const tabCreationTimes = new Map&lt;number, number&gt;()

chrome.tabs.onCreated.addListener((tab) =&gt; {
  if (tab.id) {
    tabCreationTimes.set(tab.id, Date.now())
  }
})

// Find old tabs (e.g., &gt; 7 days)
function getOldTabs(): chrome.tabs.Tab[] {
  const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000)
  return tabs.filter(tab =&gt; {
    const created = tabCreationTimes.get(tab.id!)
    return created &amp;&amp; created &lt; sevenDaysAgo
  })
}
</code></pre>
<h3 id="heading-6-search-within-groups">6. Search Within Groups</h3>
<p>A search bar in the popup would let users filter their open tabs by title, making it easy to jump to a specific tab:</p>
<pre><code class="language-tsx">const [searchQuery, setSearchQuery] = useState("")

const filteredTabs = tabs.filter(tab =&gt;
  tab.title?.toLowerCase().includes(searchQuery.toLowerCase())
)
</code></pre>
<h3 id="heading-7-exportimport-groups">7. Export/Import Groups</h3>
<p>You could let users save their current tab groups to a JSON file and restore them later. Useful for preserving a working session across restarts:</p>
<pre><code class="language-typescript">// Export
async function exportGroups() {
  const groups = await chrome.tabGroups.query({})
  const data = JSON.stringify(groups)
  const blob = new Blob([data], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  chrome.downloads.download({ url, filename: 'tab-groups.json' })
}

// Import
async function importGroups(file: File) {
  const text = await file.text()
  const groups = JSON.parse(text)
  // Restore groups...
}
</code></pre>
<h3 id="heading-8-group-statistics-dashboard">8. Group Statistics Dashboard</h3>
<p>An expanded popup could show browsing analytics, total tabs opened today, most-visited domain, and more:</p>
<pre><code class="language-tsx">function Statistics() {
  const [stats, setStats] = useState({
    totalTabs: 0,
    totalGroups: 0,
    mostUsedDomain: "",
    tabsToday: 0
  })

  return (
    &lt;div&gt;
      &lt;h3&gt;Browsing Statistics&lt;/h3&gt;
      &lt;p&gt;Total tabs opened today: {stats.tabsToday}&lt;/p&gt;
      &lt;p&gt;Most visited domain: {stats.mostUsedDomain}&lt;/p&gt;
    &lt;/div&gt;
  )
}
</code></pre>
<h2 id="heading-learning-resources">Learning Resources</h2>
<p>If you want to go deeper, the <a href="https://developer.chrome.com/docs/extensions/">official Chrome Extension docs</a> are excellent and cover every API in detail.</p>
<p>The <a href="https://github.com/GoogleChrome/chrome-extensions-samples">Chrome Extension Samples repository</a> on GitHub has dozens of real examples to learn from. For Plasmo-specific questions, the <a href="https://docs.plasmo.com/">Plasmo documentation</a> and <a href="https://github.com/PlasmoHQ/examples">example repository</a> are the best starting points, and the community is active on <a href="https://www.plasmo.com/community">Plasmo Discord</a>.</p>
<p>The <a href="https://react.dev/">React docs</a> and <a href="https://www.typescriptlang.org/docs/">TypeScript docs</a> are worth bookmarking as reference material, and the <a href="https://react-typescript-cheatsheet.netlify.app/">React TypeScript Cheatsheet</a> is handy when you're unsure about specific type patterns.</p>
<p>For community support, Stack Overflow's <code>chrome-extension</code> tag is well-monitored, and r/chrome_extensions on Reddit is a friendly place to ask questions.</p>
<h2 id="heading-deploying-to-chrome-web-store">Deploying to Chrome Web Store</h2>
<p>Now that you've built and tested your extension, here's how to publish it and share it with the world.</p>
<h3 id="heading-what-youll-need">What You'll Need</h3>
<p>Before you can publish, you'll need a completed and tested extension, a Google account, a $5 USD one-time developer registration fee, and some store assets such as icons, screenshots, and a written description.</p>
<p>The $5 fee is a one-time charge (not annual) that Google uses to verify developer identity and reduce spam. It covers unlimited extension submissions and is processed immediately via Google Payments.</p>
<h3 id="heading-step-1-create-a-production-build">Step 1: Create a Production Build</h3>
<p>Build your extension for production if you didn't do this before:</p>
<pre><code class="language-bash">cd tab-grouper-tutorial
npm run build
</code></pre>
<p>This creates an optimized version in <code>build/chrome-mv3-prod/</code>. The production build minifies JavaScript and CSS for a smaller file size, strips out development-only code and console logs, and optimizes assets for faster loading.</p>
<p>Before uploading, load <code>build/chrome-mv3-prod/</code> as an unpacked extension and test all features one more time to confirm nothing broke in the build process.</p>
<h3 id="heading-step-2-create-store-assets">Step 2: Create Store Assets</h3>
<h4 id="heading-extension-icons">Extension Icons</h4>
<p>You'll need icons in three sizes: <strong>128×128 pixels</strong> for the main store listing (required), <strong>48×48</strong> for the extension management page, and <strong>16×16</strong> for use as a favicon.</p>
<p>All should be PNG files with transparent backgrounds. Keep the design simple and recognizable at small sizes. Avoid putting text in the 16×16 version.</p>
<p><a href="https://figma.com">Figma</a> is free and works well for this, as does <a href="https://canva.com">Canva</a> or <a href="https://gimp.org">GIMP</a>.</p>
<h4 id="heading-screenshots">Screenshots</h4>
<p>Upload between 1 and 5 screenshots at either 1280×800 or 640×400 pixels (PNG or JPEG).</p>
<p>Show the extension in actual use rather than mockups. The popup with statistics, tabs being grouped, and the before/after state all work well.</p>
<p>Adding annotations to highlight key features helps users understand what they're looking at.</p>
<h4 id="heading-promotional-images-optional">Promotional Images (Optional)</h4>
<p>If you want to be featured on the store, you can also upload a small tile (440×280), large tile (920×680), and marquee image (1400×560). These are only needed if Google chooses to promote your extension.</p>
<h4 id="heading-demo-video-optional">Demo Video (Optional)</h4>
<p>A short YouTube video (30–60 seconds) showing the extension in action can significantly increase conversions. Link to it in your store listing.</p>
<h3 id="heading-step-3-write-your-store-listing">Step 3: Write Your Store Listing</h3>
<p><strong>Extension Name</strong> (45 character limit): Be clear and descriptive. "Tab Grouper - Organize Tabs by Domain" works well. Avoid keyword stuffing or excessive punctuation.</p>
<p><strong>Summary</strong> (132 character limit): This is what appears in search results. Lead with what the extension does: "Automatically organize browser tabs by domain. One-click grouping keeps your workspace clean and productive."</p>
<p><strong>Detailed Description</strong> (16,000 character limit): Start with what the extension does, list features clearly, explain how to use it, address privacy, and provide contact information. Here's a template you can adapt:</p>
<pre><code class="language-markdown">## What is Tab Grouper?

Tab Grouper automatically organizes your browser tabs by grouping them based on their website domain. No more hunting through dozens of tabs - everything is neatly organized.

## Features

- ✅ One-click tab grouping
- ✅ Automatic color-coding by domain
- ✅ Real-time statistics
- ✅ Works with all websites
- ✅ Lightweight and fast

## How to Use

1. Click the Tab Grouper icon in your toolbar
2. Click "Group Tabs by Domain"
3. Your tabs are instantly organized

## Why You Need This

If you regularly have numerous tabs open, finding the right one can waste valuable time. Tab Grouper solves this by automatically organizing tabs into colored groups, making navigation quick and straightforward.

## Privacy

This extension does not collect any personal data. It only accesses tab information locally to perform grouping. No data is sent to external servers.

## Support

Found a bug or have a suggestion? Contact us at support@example.com
</code></pre>
<p><strong>Category</strong>: Choose <strong>Productivity</strong> for Tab Grouper. You can add additional languages later if you want to localize the listing.</p>
<h3 id="heading-step-4-register-as-a-chrome-web-store-developer">Step 4: Register as a Chrome Web Store Developer</h3>
<p>Go to the <a href="https://chrome.google.com/webstore/devconsole">Chrome Web Store Developer Dashboard</a>, sign in with your Google account, accept the Developer Agreement, and pay the $5 registration fee. Your account is activated within minutes.</p>
<h3 id="heading-step-5-submit-your-extension">Step 5: Submit Your Extension</h3>
<p>In the Developer Dashboard, click <strong>"New Item"</strong> and upload your extension. You can either manually zip the <code>build/chrome-mv3-prod/</code> folder or use Plasmo's package command:</p>
<pre><code class="language-bash"># Option 1: Manual zip
cd build/chrome-mv3-prod
zip -r ../../tab-grouper.zip .

# Option 2: Use Plasmo package command
cd tab-grouper-tutorial
npm run package
</code></pre>
<p>Once uploaded, fill in all four sections of the store listing form: <strong>Product details</strong> (name, summary, description, category, language), <strong>Graphic assets</strong> (icon and screenshots), <strong>Privacy practices</strong> (see below), and <strong>Distribution</strong> (visibility, regions, pricing).</p>
<h4 id="heading-single-purpose-description">Single Purpose Description</h4>
<p>Chrome requires each extension to have a single, clearly stated purpose. For Tab Grouper: "This extension organizes browser tabs by grouping them based on their domain name, helping users manage multiple open tabs efficiently."</p>
<h4 id="heading-permission-justification">Permission Justification</h4>
<p>You'll need to justify each permission you declared. For <code>tabs</code>: "The tabs permission is required to read tab URLs and titles in order to group them by domain." For <code>tabGroups</code>: "The tabGroups permission is required to create and manage tab groups for organization."</p>
<h4 id="heading-privacy-policy">Privacy Policy</h4>
<p>Even though Tab Grouper doesn't collect personal data, Chrome may require a privacy policy. Host one on GitHub Pages or your personal website and link to it. Here's a minimal template:</p>
<pre><code class="language-markdown"># Privacy Policy for Tab Grouper

## Data Collection
Tab Grouper does not collect, store, or transmit any personal data.

## Permissions
- **tabs**: Used only to read tab URLs for grouping purposes
- **tabGroups**: Used only to create and manage tab groups

## Local Processing
All tab grouping happens locally in your browser. No data is sent to external servers.

## Contact
For questions: your-email@example.com

Last updated: [Current Date]
</code></pre>
<h3 id="heading-step-6-submit-for-review">Step 6: Submit for Review</h3>
<p>Before clicking submit, run through this checklist:</p>
<ul>
<li><p>Production build tested thoroughly</p>
</li>
<li><p>All store assets uploaded (icon + at least one screenshot)</p>
</li>
<li><p>Description is clear and accurate</p>
</li>
<li><p>Permissions are justified</p>
</li>
<li><p>Privacy policy is linked</p>
</li>
<li><p>Extension name is descriptive</p>
</li>
</ul>
<p>When you're ready, click <strong>"Submit for review"</strong>, confirm your details, and click <strong>"Publish"</strong>. Your extension enters the review queue.</p>
<h3 id="heading-step-7-the-review-process">Step 7: The Review Process</h3>
<p>Google typically reviews extensions within 1–3 business days for straightforward submissions, though complex extensions or first submissions can take up to a week. Reviewers check that the extension works as described, that permissions are justified, that there's no malicious code, and that the listing complies with Chrome Web Store policies.</p>
<p>You can track your status in the Developer Dashboard: Pending review → In review → Approved or Rejected. If rejected, Google will email you specific reasons and instructions for resubmitting.</p>
<p>The most common rejection reasons are insufficient permission justification, misleading descriptions, missing privacy policies, and requesting more permissions than necessary. Address each point in the rejection email, update your submission, and resubmit.</p>
<h3 id="heading-step-8-after-approval">Step 8: After Approval</h3>
<p>Once approved, your extension is live at <code>https://chrome.google.com/webstore/detail/[extension-id]</code>. Share the link on social media, write a blog post, post to Reddit (r/chrome, r/chrome_extensions), or submit to Product Hunt to drive installs.</p>
<p>The Developer Dashboard gives you ongoing analytics — total and weekly installs, reviews and ratings, impressions, and uninstall counts. Check it regularly, especially in the first week. Respond to reviews (particularly negative ones), thank users for positive feedback, and use reported bugs to prioritize future updates.</p>
<h3 id="heading-step-9-publishing-updates">Step 9: Publishing Updates</h3>
<p>When you fix bugs or add features, bump the version number in <code>package.json</code> (following <a href="https://semver.org/">Semantic Versioning</a> — patch for bug fixes, minor for new features, major for breaking changes), run <code>npm run build</code>, and upload the new package through the Developer Dashboard's <strong>Package</strong> tab. Updates are typically reviewed faster than initial submissions, often within 24 hours.</p>
<h3 id="heading-step-10-managing-your-extension-long-term">Step 10: Managing Your Extension Long-Term</h3>
<p>The Chrome Web Store provides built-in analytics, but you can also add Google Analytics if you need more detail.</p>
<p>For user support, an email address in the description or a GitHub issues page both work well. As you add features, keep the description updated and maintain a changelog so users know what changed and when. Responding to user questions and reviews goes a long way toward building a loyal base of users who'll recommend the extension to others.</p>
<h3 id="heading-troubleshooting-common-publishing-issues">Troubleshooting Common Publishing Issues</h3>
<p><strong>"Package is invalid" on upload</strong>: Make sure you zipped the contents of <code>build/chrome-mv3-prod/</code> rather than the folder itself, and verify the generated <code>manifest.json</code> is valid JSON.</p>
<p><strong>Rejection: Permissions Not Justified</strong>: In the "Permission justification" field, be specific about which feature requires each permission and what would break without it.</p>
<p><strong>Rejection: Single Purpose Unclear</strong>: Rewrite the single purpose description to focus on one main function, stated plainly.</p>
<p><strong>Low installation rate after launch</strong>: Poor screenshots are often the culprit — they're the first thing most users look at. Make sure they clearly show the extension solving a real problem. Building even a small number of early reviews also makes a big difference to new visitors.</p>
<h3 id="heading-alternative-distribution">Alternative Distribution</h3>
<p>The Chrome Web Store is the right choice for most public extensions. If you're building an internal tool, an <strong>Unlisted</strong> extension (accessible only via direct link, not searchable) is a good option.</p>
<p>If you need to restrict it to users in a specific Google Workspace organization, a <strong>Private</strong> extension is available for that. Self-hosting and sideloading is possible but requires users to enable Developer Mode manually, so it's only practical for very technical audiences.</p>
<h2 id="heading-congratulations">Congratulations!</h2>
<p>You've gone from an empty folder to a live Chrome extension on the Web Store. Along the way you learned how extensions are structured, how background scripts and popups communicate, how Chrome's tab APIs work, and how to navigate the publishing process end to end.</p>
<p>More than any specific API or configuration detail, the most important thing you've built is a mental model for how extensions work and that transfers directly to any extension idea you want to build next.</p>
<p>Keep building, keep learning, and keep shipping!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Chrome DevTools – Simple Strategies for Smarter Web Development ]]>
                </title>
                <description>
                    <![CDATA[ As a web developer, there are many tools out there – in addition to your code editor – that can make you more efficient.  It doesn't matter if you're just starting out or have been coding for years. Knowing how to effectively use Developer Tools (Dev... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/chrome-devtools/</link>
                <guid isPermaLink="false">66c5a334215f782a032b1cb7</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ clean code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                    <category>
                        <![CDATA[ devtools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Problem Solving ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ophy Boamah ]]>
                </dc:creator>
                <pubDate>Thu, 15 Feb 2024 16:12:02 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/02/ChromeDevTools-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As a web developer, there are many tools out there – in addition to your code editor – that can make you more efficient. </p>
<p>It doesn't matter if you're just starting out or have been coding for years. Knowing how to effectively use Developer Tools (DevTools for short) can significantly boost your development process. You can edit pages on the fly, quickly spot issues, and deeply understand your site's performance. </p>
<p>All major browsers have their own DevTools that let you examine the code of a webpage, evaluate its metrics, and run some tests alongside. This article will discuss Chrome's DevTools, as it's the industry standard.</p>
<h2 id="heading-table-of-contents">Table of contents:</h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-chrome-devtools">What is Chrome DevTools?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-open-chrome-devtools">How to Open Chrome DevTools</a></li>
<li><a class="post-section-overview" href="#heading-keyboard-shortcuts-for-easy-navigation">Keyboard shortcuts for Easy Navigation</a></li>
<li><a class="post-section-overview" href="#heading-key-chrome-devtools-features">Key Chrome DevTools Features</a></li>
<li><a class="post-section-overview" href="#heading-practical-devtools-use-cases">Practical DevTools Use Cases</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h1 id="heading-what-is-chrome-devtools">What is Chrome DevTools?</h1>
<p>Chrome DevTools is a set of tools that are essential for diagnosing and solving web development challenges, directly within the Google Chrome browser. </p>
<p>It gives you direct access to a website's inner workings - to inspect HTML and CSS, debug JavaScript, analyze performance, and see the immediate impact of your code, all in realtime. </p>
<p>This direct access to a website's inner workings is crucial for diagnosing issues quickly and efficiently, ensuring your web applications are both performant and bug-free.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/DevToolsScreenshots-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>A grid of elements, console, performance and network panels screenshots</em></p>
<h1 id="heading-how-to-open-chrome-devtools">How to Open Chrome DevTools</h1>
<p>To open DevTools in your Chrome browser, you can either:</p>
<ol>
<li>Right-click on any webpage and select inspect from the list of options.</li>
<li>Use the shortcut (command + option + I on Mac or control + shift + I on Windows). </li>
<li>Click the three dot icon next to your profile picture on your Chrome browser, choose 'More Tools' and 'Developer Tools' from the second option box.</li>
</ol>
<p>It usually opens in a split screen interface, either below your current webpage or beside it. Once open, its features line up as tabs at the top of the DevTools window. These tabs include: Elements, Console, Source, Network, Application, Security, Memory, Performance, Audits.</p>
<h2 id="heading-keyboard-shortcuts-for-easy-navigation">Keyboard Shortcuts for Easy Navigation</h2>
<ol>
<li>Use Cmd or Ctrl + Shift + C to open the Elements panel</li>
<li>Use Cmd or Ctrl + Shift + J to open the Console panel</li>
<li>Use Cmd or Ctrl + ] to move forward to the next panel </li>
<li>Use Cmd or Ctrl + [ to move back to the previous panel </li>
</ol>
<h1 id="heading-key-chrome-devtools-features">Key Chrome DevTools Features</h1>
<p>DevTools is packed with features essential for web developers to streamline various aspects of their workflow. Let's look at a few of them in some detail now.</p>
<h2 id="heading-elements-panel">Elements Panel</h2>
<p>This panel is used for inspecting and modifying the HTML and CSS of a webpage in real-time, which is great for debugging layout issues or experimenting with new styles before applying them in your actual code. You also get to see how the DOM (Document Object Model) is structured. </p>
<p>Imagine fine-tuning your website's footer appearance (background color, font size) directly in your browser and seeing the results instantly. </p>
<p>With DevTools open, click on the Elements tab to access it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/elpanel-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot of Chrome DevTools' Elements panel</em></p>
<h2 id="heading-console-panel">Console Panel</h2>
<p>This panel serves as your interactive playground for JavaScript within the browser. Whether you're tracking down an elusive bug with a quick <code>console.log()</code> or experimenting with DOM elements, in the Console panel you can test snippets of JavaScript and view any logs or errors in the currently loaded webpage. </p>
<p>To use it, simply open DevTools and select the "Console" tab or use the shortcut (option + command + J on Mac or contrl + shift + J on Windows).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/clpanel-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot of Chrome DevTools' Console panel</em></p>
<h2 id="heading-network-panel">Network Panel</h2>
<p>This panel gives you an overview of all network activity on your webpage – from tracking every resource that is loaded to how your site communicates with servers. </p>
<p>If you've wondered why your website takes forever to load or why some API requests seem to vanish into thin air, the Network panel is your go-to as it provides insights into the success or failure of API calls. </p>
<p>To access it, open DevTools and navigate to the "Network" tab.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/netpanel-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot of Chrome DevTools' Network panel</em></p>
<h2 id="heading-performance-panel">Performance Panel</h2>
<p>This panel is used for capturing and analyzing a website's performance metrics. It shows all the activities happening when interacting with a page. </p>
<p>When your web app starts to crawl under heavy usage, the Performance panel can pinpoint where the performance bottlenecks lie so that you can resolve these issues, ensuring your app runs smoothly. </p>
<p>With DevTools open, click on the "Performance" tab to use it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/perfpanel-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot of Chrome DevTools' Performance panel</em></p>
<p>The above are only a handful of the panels available, but they're by far the most popular and must-knows. Using them properly will make your development processes more intuitive and rewarding.</p>
<h1 id="heading-practical-devtools-use-cases">Practical DevTools Use Cases</h1>
<p>In the following interactive examples, I intentionally created the mini project in Codepen <strong>with issues</strong> to simulate real-world debugging scenarios using Chrome DevTools. </p>
<p>I figured it'd be a great way to highlight the practical uses of certain DevTools panels and features in identifying bugs and troubleshooting right in the browser. </p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li>Chrome browser (<a target="_blank" href="https://support.google.com/chrome/answer/95346?hl=en&amp;co=GENIE.Platform%3DDesktop">Click this link to download</a>)</li>
<li>A basic understanding of HTML, CSS, and JavaScript</li>
<li><a target="_blank" href="https://codepen.io/ophyboamah/full/rNpZZwo">Codepen</a></li>
</ul>
<p></p><p>
  <span>See the Pen <a href="https://codepen.io/ophyboamah/pen/rNpZZwo">
  Modal Window</a> by Ophy Boamah (<a href="https://codepen.io/ophyboamah">@ophyboamah</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p><p></p>


<h2 id="heading-how-to-debug-html-and-css-with-the-elements-panel">How to Debug HTML and CSS with the Elements Panel</h2>
<p>Our mini project contains a modal that, upon clicking, should display a modal window with some important information. But there's a bug preventing this from happening. </p>
<p>This situation sets the stage for a practical demonstration of how you can use the Elements Panel to troubleshoot and resolve styling and structural issues.</p>
<pre><code>&lt;body&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"show-modal"</span>&gt;</span>Click me to learn a secret 🤫<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal hidden"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"close-modal"</span>&gt;</span><span class="hljs-symbol">&amp;times;</span><span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hey Ophy here 👋🏾<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      I lead Women Who Code Frontend, a global remote community of 3,000+ women frontend devs and enthusiasts. Find us on beacons.ai/wwcodefrontend
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"overlay hidden"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"script.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
&lt;/body&gt;
</code></pre><pre><code class="lang-css"><span class="hljs-selector-class">.hidden</span> {
  <span class="hljs-attribute">display</span>: none;
}

<span class="hljs-selector-class">.modal</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate</span>(-<span class="hljs-number">50%</span>);
  <span class="hljs-attribute">width</span>: <span class="hljs-number">70%</span>;

  <span class="hljs-attribute">background-color</span>: white;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">6rem</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">3rem</span> <span class="hljs-number">5rem</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.3</span>);
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">10</span>;
}
</code></pre>
<p>In our modal's HTML code above, we've added the class name 'modal hidden' which has a corresponding styling with the CSS property of <code>display:none</code> that is set to hide the modal when the page is loaded initially and only display it when the button is clicked.</p>
<h3 id="heading-step-1-initial-inspection">✅ Step 1 - Initial inspection:</h3>
<p>Attempt to trigger the modal by clicking on the 'Click me to learn a secret' button. Since we've set that up not to work, right-click on the area where the modal should appear and choose "Inspect" to open DevTools' Elements Panel.</p>
<h3 id="heading-step-2-diagnose-visibility-issues">✅ Step 2 - Diagnose visibility issues:</h3>
<p>In the Elements Panel, locate the modal in the DOM to see that the modal is present but not visible. This confirms that the bug is caused within our CSS code <code>display: hidden</code>. </p>
<p>As soon as you click on the modal in the DOM, any corresponding CSS classes will be pulled up within Styles at the bottom section of the Elements panel. You can toggle some properties on and off or type others to see the effects in real-time.</p>
<p>Manually change the class name from <code>modal hidden</code> to <code>modal block</code> to trigger the right properties that'll cause the modal to show.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/modalblock.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot of debugging the modal's HTML, CSS in Elements panel</em></p>
<h3 id="heading-step-3-center-the-modal">✅ Step 3 - Center the modal:</h3>
<p>Now the modal is visible, but it's displayed at the top – which is different from where we'd like it to be (that is, in the center of the page).</p>
<p>To change this, modify the <code>transform</code> property to <code>translate(-50%, -50%)</code> by adding the second <code>-50%</code> and ensure that <code>top: 50%</code>, and <code>left: 50%</code> are correctly set to center the modal on the screen.</p>
<h3 id="heading-step-4-enhance-the-appearance">✅ Step 4 - Enhance the appearance:</h3>
<p>You can go further to refine the modal's appearance by tweaking its <code>background-color</code>, <code>padding</code>, or other stylistic properties directly within the Styles to achieve the desired look and feel.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/ChromeDevTools.gif" alt="Image" width="600" height="400" loading="lazy">
<em>A GIF fixing the modal in Chrome DevTools' Elements panel</em></p>
<h2 id="heading-debug-javascript-with-the-sources-panel">Debug JavaScript with the Sources Panel</h2>
<p>I added a bug in the JavaScript code of our modal mini project to prevent it from opening when the button is clicked. </p>
<p>In the real world, this would cause neither the open nor close commands to trigger any action, which would leave users unable to interact with the content and frustrated as a result. Let's troubleshoot and debug this issue in the Sources Panel.</p>
<p>In the code below, the openModal function is set to remove the indicated classes. However, this doesn't work because we deliberately misspelled <code>hidden</code>. </p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Introducing a bug: Incorrectly spelling 'hidden' as 'hiddn'</span>
<span class="hljs-keyword">const</span> openModal = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  modal.classList.remove(<span class="hljs-string">"hiddn"</span>); <span class="hljs-comment">// Intentional bug</span>
  overlay.classList.remove(<span class="hljs-string">"hidden"</span>);

  <span class="hljs-comment">// Fetch data from a real API and display in the modal</span>
};
</code></pre>
<h3 id="heading-step-1-set-up-breakpoints">✅ Step 1 - Set up breakpoints:</h3>
<p>Open Chrome DevTools and navigate to the Sources Panel. Here, find the JavaScript file that includes the modal functionality (in our example its pen.js). </p>
<p>The openModal function contains the logic for displaying the modal on the screen. This function will include a line where the modal element's class is manipulated to remove a "hidden" class. </p>
<p>Click on the number next to this code line in DevTools. A blue (or sometimes red, depending on the theme) icon appears next to the line number, indicating that a breakpoint has been set. This breakpoint will pause the execution of our JavaScript code as soon as it reaches this line.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/soscreenshot.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot of setting breakpoints the modal's JS in Sources panel</em></p>
<p>Breakpoints pause code execution at critical points, allowing you to inspect the current state of variables and understand the flow of execution. This step is crucial for identifying where the code deviates from expected behaviour.</p>
<h3 id="heading-step-2-examine-the-code-execution-flow">✅ Step 2 - Examine the code execution flow:</h3>
<p>With our breakpoint in place, try to open the modal by clicking on its button. Execution of our JavaScript code now pauses at our breakpoint, which enables us to step through the code line by line. </p>
<p>This is an opportunity to observe variables, function calls, look for anomalies such as misnamed functions, incorrect logic, or uncaught exceptions that could explain why the modal isn't working. </p>
<p>In our case it's because we intentionally misspelled the class name <code>hidden</code> as <code>hiddn</code>. Fix that in the code to get the modal working again.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/ChromeDevTools--2-.gif" alt="Image" width="600" height="400" loading="lazy">
<em>A GIF troubleshooting the modal bug in Chrome DevTools' Elements panel</em></p>
<h2 id="heading-optimize-performance-with-the-network-panel">Optimize Performance with the Network Panel</h2>
<p>Here I've added a fetch function that makes an API call to a live endpoint (<a target="_blank" href="https://jsonplaceholder.typicode.com/posts/1"><code>https://jsonplaceholder.typicode.com/posts/1</code></a>). This is an excellent opportunity to explore the Network Panel's capabilities in diagnosing and understanding network-related problems.</p>
<p>From the code below, you can see that the openModal function doesn't only open the modal but also makes an API call to the <code>jsonplaceholder</code> endpoint to fetch some data. </p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> openModal = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/posts/1'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
    .then(<span class="hljs-function"><span class="hljs-params">json</span> =&gt;</span> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'modal-content'</span>).innerText = json.title)
    .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error loading the content:'</span>, error));
};
</code></pre>
<h3 id="heading-step-1-initiate-the-api-call">✅ Step 1 - Initiate the API call:</h3>
<p> On the modal project UI, click on the 'Click me to learn a secret' button. Though the modal does not visibly activate, because of the fetch logic within the openModal function, an API call will be made.</p>
<h3 id="heading-step-2-network-panel-inspection">✅ Step 2 - Network Panel Inspection:</h3>
<p>Ideally, your Network Panel should be open before clicking the button, but you can also reverse the steps. Detailed insights on your API request such as the request's method, status code, response and the time it took to complete, will be available under headers, preview, response, initiator and timing tabs respectively. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/netscreenshot.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot overview of API request in Network panel</em></p>
<h3 id="heading-step-3-simulating-network-conditions">✅ Step 3 - Simulating Network Conditions:</h3>
<p>Use the Network Panel's throttling feature to mimic various network speeds like offline or slow 3G to see how the API request behaves under constrained conditions. </p>
<p>From this you can compare how different network speeds can affect application performance. This will teach you the importance of optimizing data loading strategies to enhance user experience, especially on slower connections.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/ChromeDevTools--1-.gif" alt="Image" width="600" height="400" loading="lazy">
<em>A GIF observing API requests and responses in Chrome DevTools' Network panel</em></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Bringing Chrome DevTools into your web development routine is not just about fixing bugs. It's about streamlining your workflow, making your sites more accessible, and boosting their performance. </p>
<p>Through our modal window mini-project, we've seen firsthand how DevTools can address a wide array of development challenges, but that’s merely scratching the surface of what it can do. </p>
<p>As you continue to explore its capabilities and familiarize yourself with its features, you'll find it's an invaluable companion on your web development journey – designed to make your development process not just faster, but also more rewarding.</p>
<ul>
<li><a target="_blank" href="https://developer.chrome.com/docs/devtools">The Official Chrome DevTools documentation</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/learn-how-to-use-the-chrome-devtools-to-troubleshoot-websites/">How to use the Chrome DevTools to troubleshoot websites</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Your Connection is Not Private Error – How to Fix in Chrome ]]>
                </title>
                <description>
                    <![CDATA[ If you log on to a website and your browser shows the “Your connection is not private” error, the browser is trying to warn you to stay off the website. In that case, the browser has run a check on the SSL (secure socket layer) certificate and found... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/your-connection-is-not-private-error-how-to-fix-in-chrome/</link>
                <guid isPermaLink="false">66adf297f452caf50fb1fe2b</guid>
                
                    <category>
                        <![CDATA[ browser ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ privacy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SSL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Thu, 07 Jul 2022 16:03:17 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/07/castle-1290860_1280.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you log on to a website and your browser shows the “Your connection is not private” error, the browser is trying to warn you to stay off the website.
<img src="https://www.freecodecamp.org/news/content/images/2022/07/Annotation-2022-07-06-111823-1.png" alt="Annotation-2022-07-06-111823-1" width="600" height="400" loading="lazy"></p>
<p>In that case, the browser has run a check on the SSL (secure socket layer) certificate and found a problem with it – the SSL could have expired or might not have been installed at all.</p>
<p>In some cases, the problem could be because of your browser and not the website. So, in this article, I will show you how to fix the “Your connection is not private” error on a Chrome browser.</p>
<h2 id="heading-what-well-cover">What We'll Cover</h2>
<ul>
<li><a class="post-section-overview" href="#heading-how-to-fix-the-your-connection-is-not-private-error-on-a-chrome-browser">How to Fix the “Your connection is not private” Error on a Chrome Browser</a><ul>
<li><a class="post-section-overview" href="#heading-reload-the-web-page">Reload the Web Page</a> </li>
<li><a class="post-section-overview" href="#heading-clear-chromes-cache">Clear Chrome’s Cache</a></li>
<li><a class="post-section-overview" href="#heading-make-sure-your-computers-date-and-time-are-correct">Make Sure your Computer’s Date and Time are Correct</a></li>
<li><a class="post-section-overview" href="#heading-disable-your-antivirus-and-vpn">Disable your Antivirus and VPN</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></li>
</ul>
<h2 id="heading-how-to-fix-the-your-connection-is-not-private-error-on-a-chrome-browser">How to Fix the “Your connection is not private” Error on a Chrome Browser</h2>
<h3 id="heading-reload-the-web-page">Reload the Web Page</h3>
<p>The first thing I would advise you do is to reload the page.</p>
<p>Reloading the web page is the old trick everyone tries if there is a problem with that web page. </p>
<p>In addition, there are chances that SSL-related work is going on with the website, so if you wait a while and reload the page, the issue could disappear.</p>
<p>If reloading doesn’t fix the issue for you, proceed to other solutions in this article.</p>
<h3 id="heading-clear-chromes-cache">Clear Chrome’s Cache</h3>
<p>The SSL data of the website in your Chrome browser cache might have expired. So if you clear the cache, the error may go away.</p>
<p>Follow the steps below to clear your Chrome browser cache:</p>
<p><strong>Step 1</strong>: Click the 3 vertical dots on the top-right corner and select Settings:
<img src="https://www.freecodecamp.org/news/content/images/2022/07/ss10-1.png" alt="ss10-1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Click the “Privacy and Security” tab on the left sidebar and select “Clear browsing data”:
<img src="https://www.freecodecamp.org/news/content/images/2022/07/ss11-1.png" alt="ss11-1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Select Cache and Cookies, then click “Clear data”:
<img src="https://www.freecodecamp.org/news/content/images/2022/07/ss12-1.png" alt="ss12-1" width="600" height="400" loading="lazy"></p>
<h3 id="heading-make-sure-your-computers-date-and-time-are-correct">Make Sure your Computer’s Date and Time are Correct</h3>
<p>If your computer clock is behind or ahead, your browser will show you a “Your connection is not secure” error.</p>
<p>These days, the error message has become more accurate in Chrome:
<img src="https://www.freecodecamp.org/news/content/images/2022/07/Annotation-2022-06-06-072807.png" alt="Annotation-2022-06-06-072807" width="600" height="400" loading="lazy"></p>
<p>In this case, you should set your date and time to the correct one and make it automatic, so nothing readjusts it again:
<img src="https://www.freecodecamp.org/news/content/images/2022/07/ss.png" alt="ss" width="600" height="400" loading="lazy"></p>
<h3 id="heading-disable-your-antivirus-and-vpn">Disable your Antivirus and VPN</h3>
<p>Some Antivirus programs with SSL scanning features can make your browser show the “Your connection is not private” error if they detect any irregularity with the SSL certificate of a website.</p>
<p>In the same vein, a VPN (virtual private network) conceals your IP address and other information. The problem is that the privacy a VPN gives you could have a negative effect on some sites' SSL.</p>
<p>Due to this, you should consider disabling your antivirus and VPN programs, at least temporarily, to see if that fixes the error.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Other fixes that might get rid of the “Your connection is not private” error for you include:</p>
<ul>
<li>Trying to access the web page in incognito mode. In Chrome, you can open an incognito tab by pressing <code>CTRL</code> + <code>SHIFT</code> + <code>N</code>.</li>
<li>Restarting your Router</li>
<li>Restarting your computer</li>
<li>Updating your OS</li>
</ul>
<p>If all the fixes fail to work, the problem could be from the website. This means there is a problem with the website’s SSL certificate. So, try to contact the site admin. </p>
<p>If you find no admin to contact, you should stay off that website and make sure you don’t share sensitive information with the site.</p>
<p>If you’re a site admin and your users complain about this error, I wrote <a target="_blank" href="https://www.freecodecamp.org/news/an-ssl-error-has-occurred-how-to-fix-certificate-verification-error/#howtofixsslerrorasasiteowner">an article on how you can restore your site’s SSL</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Fix Google Chrome Black Screen on Linux OS (Wayland) ]]>
                </title>
                <description>
                    <![CDATA[ If you are a Linux user, then you've likely used the GNOME Desktop Environment at least once. The latest GNOME DE (Desktop Environment) uses Wayland nowadays. And while it's possible to remove Wayland and select Xorg if you want, most users are start... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-fix-black-screen-on-google-chrome-on-linux-os/</link>
                <guid isPermaLink="false">66b902da5730a049b6bfea7a</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Problem Solving ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Md. Fahim Bin Amin ]]>
                </dc:creator>
                <pubDate>Wed, 27 Apr 2022 19:33:58 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/Fix---1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you are a Linux user, then you've likely used the GNOME Desktop Environment at least once.</p>
<p>The latest GNOME DE (Desktop Environment) uses Wayland nowadays. And while it's possible to remove Wayland and select Xorg if you want, most users are starting to use Wayland as their daily driver. </p>
<p>And if you still use the Chrome or Chromium browsers, you'll often face the black screen issue during screen sharing.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Untitled-design.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you typically use Wayland on your desktop, you'll have these issues while screen sharing, especially in Google Meet. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Untitled-design--1-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this article, I will show you a very simple trick that will help you solve the black screen issue during screen sharing on any kind of Chromium browser including the most popular browser, Google Chrome. </p>
<p>I have used Ubuntu for writing this article, but the same process is applicable to all other Linux OS distros running on Wayland.</p>
<h2 id="heading-the-black-screen-issue">The Black Screen Issue</h2>
<p>If you're having the black screen issue in Google Meet in a Chromium browser (Chrome, Brave, Vivaldi, etc), it probably looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-38-51.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Sometimes it might happen because screen sharing in Wayland gets broken or because of other issues. A lot of users also say it happens as developers are trying hard to increase the security level. </p>
<p>The issue of screen sharing might become common for many people in the usual way. A lot of users go back to X11 only for this issue or try the pipewire solution to fix this.</p>
<p>I will show you the solution using the pipewire technology so that you do not need to go back to X11 only for solving this issue. 😊</p>
<p>Go to <strong><code>chrome://flags/#enable-webrtc-pipewire-capturer</code></strong> using the address bar of your browser.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-39-23.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You will get a long list, but we are interested in <strong>WebRTC PipeWire Support</strong>. You will see that the option is on the Default mode right now.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-39-31.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We need to change it to <strong>Enabled</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-39-40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Simply click the drop down menu, and click <strong>Enabled</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-39-54.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now you will see a prompt on the below right side to relaunch the browser so this can take effect.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-39-54-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click on <strong>Relaunch</strong>. It will simply restart your browser.</p>
<p>The issue has been solved! ✌️</p>
<h2 id="heading-how-to-test-screen-sharing">How to Test Screen Sharing</h2>
<p>Now if you want to share your screen as usual, you can definitely do that. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-40-27.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Whenever you want to share the screen, a prompt will appear and will request you to select the monitor. You have to share the monitor. If you have only one monitor like I do, then you would get one monitor. Simply click on that and click <strong>Share</strong>. </p>
<p>You will also get to see the preview of the screen sharing.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-40-40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You may need to select the monitor again in the prompt. Simply select the monitor and click Share as earlier.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-40-47.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>From now on, you can share your screen on Google Meet from Wayland.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Screenshot-from-2022-04-25-19-41-07.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If this article helps you resolve the black screen issue for your Linux DE, then I've been successful. 😊</p>
<p>Thanks for reading the entire article. If it helps you then you can also check out other articles of mine at <a target="_blank" href="https://www.freecodecamp.org/news/author/fahimbinamin/">freeCodeCamp</a>.</p>
<p>If you want to get in touch with me, then you can do so using <a target="_blank" href="https://twitter.com/Fahim_FBA">Twitter</a>, <a target="_blank" href="https://www.linkedin.com/in/fahimfba/">LinkedIn</a>, and <a target="_blank" href="https://github.com/FahimFBA">GitHub</a>. </p>
<p>You can also <a target="_blank" href="https://www.youtube.com/@FahimAmin?sub_confirmation=1">SUBSCRIBE to my YouTube channel</a> (Code With FahimFBA) if you want to learn various kinds of programming languages with a lot of practical examples regularly.</p>
<p>If you want to check out my highlights, then you can do so at my <a target="_blank" href="https://www.polywork.com/fahimbinamin">Polywork timeline</a>.</p>
<p>You can also <a target="_blank" href="https://fahimbinamin.com/">visit my website</a> to learn more about me and what I'm working on.</p>
<p>Thanks a bunch!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ dns_probe_finished_nxdomain Error [Solved] ]]>
                </title>
                <description>
                    <![CDATA[ If you are a regular Google Chrome user, then you might have encountered the error “dns_probe_finished_nxdomain” before. It is usually accompanied by “This site can’t be reached”. This error is associated with the Domain Name System (DNS) server and... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/dns-probe-finished-nxdomain-error-solved/</link>
                <guid isPermaLink="false">66b85678da047dff8c7ad01c</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ error ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Wed, 20 Apr 2022 15:39:02 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/earth-931552_1920.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you are a regular Google Chrome user, then you might have encountered the error “dns_probe_finished_nxdomain” before. It is usually accompanied by “This site can’t be reached”.
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss1-5.png" alt="ss1-5" width="600" height="400" loading="lazy"></p>
<p>This error is associated with the Domain Name System (DNS) server and can occur due to misconfiguration in the DNS server, an unresponsive server, or a yet-to-be-propagated DNS of a website.  </p>
<p>The “nxdomain” in the error means you’re trying to access a “non-existent domain”.</p>
<p>In other browsers, the “dns_probe_finished_nxdomain” error could present itself in another way. On Microsoft Edge, it could appear as “Hmm…can’t reach this page”, and on Firefox, it usually appears as “Hmm. We’re having trouble finding that site”.</p>
<p>Fixing this error in order to restore your internet connectivity is not an uphill task. And so in this article, I’m going to show you 4 ways to fix it.</p>
<h2 id="heading-what-well-cover-here">What We'll Cover Here</h2>
<ul>
<li><a class="post-section-overview" href="#heading-fix-1-disable-your-antivirus-and-vpn">Disable your Antivirus and VPN</a></li>
<li><a class="post-section-overview" href="#heading-fix-2-flush-release-and-renew-your-dns-cache">Flush, Release,and Renew your DNS Cache</a><ul>
<li><a class="post-section-overview" href="#youshouldalsoconsiderflushingchromesdns">Flush your Google Chrome Browser Cache</a>) </li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-fix-3-restart-your-router-or-modem">Restart your Router or Modem</a></li>
<li><a class="post-section-overview" href="#heading-fix-4-manually-change-your-dns-server">Manually Change your DNS Server</a><ul>
<li><a class="post-section-overview" href="#heading-you-can-also-change-the-dns-server-of-the-google-chrome-browser-in-particular">Change your Google Chrome Browser DNS Server</a> </li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-fix-1-disable-your-antivirus-and-vpn">Fix 1 – Disable Your Antivirus and VPN</h2>
<p>Antivirus programs are notorious for interfering with apps and stopping them from working the right way.</p>
<p>VPNs, on the other hand, can block some websites, while some other websites don't work well with them.</p>
<p>If you are getting the “dns_probe_finished_nxdomain” error, consider disabling your Antivirus and turning off your VPN, then check to make sure you can access the internet again. </p>
<p>If you are able to access the internet after disabling your antivirus program and turning off your VPN, then this is the reason you’re getting the error. </p>
<p>If you are on Windows 10, you can disable Windows Security by following the steps below:
<strong>Step 1</strong>: Open the Task Manager by pressing <code>ALT</code> + <code>SHIFT</code> + <code>ESC</code> on your keyboard.</p>
<p><strong>Step 2</strong>: Click on the Startup tab.</p>
<p><strong>Step 3</strong>: Locate your Antivirus Program in the list, right-click on it, and select "Disable".
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss3-4.png" alt="ss3-4" width="600" height="400" loading="lazy"></p>
<p>Try to access the internet again to see if the error isn't shown anymore. If this fails to fix the error, keep reading and try other fixes in this article.</p>
<h2 id="heading-fix-2-flush-release-and-renew-your-dns-cache">Fix 2 – Flush, Release, and Renew your DNS Cache</h2>
<p>The DNS cache saves the IP addresses of websites you visited in order to speed up load time when you try to visit the same sites.</p>
<p>Flushing, releasing, and renewing the DNS cache can fix the “dns_probe_finished_nxdomain” error because the processes removes invalid IP configurations and outdated information in the DNS cache.</p>
<p>To flush, release, and renew your computer’s DNS on Windows, follow the steps highlighted below:
<strong>Step 1</strong>: Hit the <code>WIN</code> button on your keyboard and search for "cmd". Then select "Run as Administrator" on the right.</p>
<p><strong>Step 2</strong>: Enter and execute the following commands one after the other:</p>
<ul>
<li><code>ipconfig /flushdns</code></li>
<li><code>ipconfig /release</code></li>
<li>`ipconfig /renew
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss4-3.png" alt="ss4-3" width="600" height="400" loading="lazy"></li>
</ul>
<h3 id="heading-you-should-also-consider-flushing-chromes-dns">You should also consider flushing Chrome’s DNS.</h3>
<p>To flush Chrome's DNS, all you need to do is type <code>chrome://net-internals/#dns</code> in the address bar and hit <code>ENTER</code>. Then click “Clear host cache”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/flushChromeDNS-1.png" alt="flushChromeDNS-1" width="600" height="400" loading="lazy"></p>
<p>After flushing your computer's DNS along with Chrome's, restart your computer and check to see if you don’t get the error anymore.</p>
<h2 id="heading-fix-3-restart-your-router-or-modem">Fix 3 – Restart your Router or Modem</h2>
<p>If you access the internet through a router or modem, restarting it could help you get rid of the “dns_probe_finished_nxdomain” error. </p>
<p>This is because turning off and then turning on a router or modem clears the cache of IP addresses, which could fix the error in the long run.</p>
<p>To restart your router or modem, locate the power button and long-press to turn it off, then long-press again to turn it on.</p>
<h2 id="heading-fix-4-manually-change-your-dns-server">Fix 4 – Manually Change your DNS Server</h2>
<p>If any of the fixes above fail to work for you, you should consider changing your DNS server address as it is one of the most reliable ways to fix the “dns_probe_finished_nxdomain” error.</p>
<p>By default, a DNS server address is provided by your internet service provider, but using this default DNS is not always secure. And it could be the reason you are getting the “dns_probe_finished_nxdomain” error.</p>
<p>You can change your DNS server to one of the free ones provided by the likes of Google and Cloudflare.</p>
<p>The steps below show you how to change your DNS server to Google: 
<strong>Step 1</strong>: Right-click on Start and select “Network Connections”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss5-3.png" alt="ss5-3" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Scroll down and select “Change adapter options”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss6-3.png" alt="ss6-3" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: In the pop-up that appears, right-click on the network you are connected to and select “Properties”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss7-2.png" alt="ss7-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: In another pop-up that appears, double-click on “Internet Protocol Version 4 (TCP/IPv4)”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss8-2.png" alt="ss8-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 5</strong>: Another pop-up will appear. This time around, select the radio button that says “Use the following DNS server addresses”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss9-2.png" alt="ss9-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 6</strong>: Enter 8.8.8.8 for “Preferred DNS server” and 8.8.4.4 for “Alternate DNS server”. This is the free DNS server provided by Google.
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss10-2.png" alt="ss10-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 7</strong>: Click “Ok”, and “Ok” once again. </p>
<p><strong>N.B.</strong>: If your computer is configured to use IPv6 instead of IPv4, then in step 4, you should choose “Internet Protocol Version 6 (TCP/IPv6)” instead of “Internet Protocol Version 4 (TCP/IPv4)”. Then enter <code>2001:4860:4860::8888</code> for the preferred DNS server and <code>2001:4860:4860::8844</code> for the alternative DNS server.</p>
<h3 id="heading-you-can-also-change-the-dns-server-of-the-google-chrome-browser-in-particular">You can also change the DNS server of the Google Chrome browser in particular.</h3>
<p>To do this, head over to your chrome browser, type <code>chrome://settings/security</code> in the address bar and hit <code>ENTER</code>.
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss2-6.png" alt="ss2-6" width="600" height="400" loading="lazy"></p>
<p>On the page that appears, scroll down, click on “Custom”, and select “Google (Public DNS)”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss3-5.png" alt="ss3-5" width="600" height="400" loading="lazy"></p>
<p>After doing all this, check to see if your internet connection is restored.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As you can see in this article, resolving the “dns_probe_finished_nxdomain” error is not difficult because there are several ways you can fix it. This article discussed 4 of those ways.</p>
<p>If one of the fixes fails to resolve the error for you, then you should check out any of the rest. As for me, I experienced this error not long ago and what fixed it for me was changing my DNS server – <a class="post-section-overview" href="#heading-fix-4-manually-change-your-dns-server">Fix 4</a>.</p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Inspect an Element – Chrome Shortcut ]]>
                </title>
                <description>
                    <![CDATA[ The Inspect Element feature of the Google Chrome browser is a powerful yet easy-to-use tool. It’s an important part of Chrome Developer Tools that you can use to check the source code of any website. But it doesn’t end there. You can take things furt... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-inspect-an-element-chrome-shortcut/</link>
                <guid isPermaLink="false">66adf1313bf50764799b9cac</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Fri, 15 Apr 2022 01:40:32 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/inspect-element.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The Inspect Element feature of the Google Chrome browser is a powerful yet easy-to-use tool.</p>
<p>It’s an important part of Chrome Developer Tools that you can use to check the source code of any website.</p>
<p>But it doesn’t end there. You can take things further by changing the elements and styles that make up the website – that is, the HTML, CSS, and JavaScript code of the website. This is why a lot of developers use the Inspect tool for debugging purposes.</p>
<p>If you’re a beginner in web development, the Inspect tool is a useful feature you can take advantage of to learn about how websites are built, the fonts, icons, and plugins used, and even who made the website.</p>
<p>The good news is that you don’t need to be the developer of the website to use this powerful tool as it is available to users as well. You don’t even need to be a developer at all to use it.</p>
<p>In this article, you will learn how to open the Chrome Developer Tools so you can get access to the Inspect feature, and how to inspect specific elements on a website. I will also show you how you can manipulate the elements of a website by changing the texts and styles.</p>
<p>I'll be using freeCodeCamp.org to show you how things work with the Inspect tool.</p>
<h2 id="heading-how-to-open-the-chrome-developer-tools">How to Open the Chrome Developer Tools</h2>
<p>To open the Chrome Developer Tools, click the vertical dots at the top-right corner of your browser:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss1-3.png" alt="ss1-3" width="600" height="400" loading="lazy"></p>
<p>Then hover over “More tools” and select “Developer tools”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss2-5.png" alt="ss2-5" width="600" height="400" loading="lazy"></p>
<p>You will then get access to the tabs of the developer tools such as Elements (the HTML and CSS that makes up the website), Console with which you can run JavaScript, Sources, and many more.</p>
<p>You can drag these tabs around and place them wherever you want:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/drag.gif" alt="drag" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-do-i-open-inspect-element-in-chrome-with-the-keyboard">How Do I Open Inspect Element in Chrome with the Keyboard?</h2>
<p>You can open the Inspect element tool on Linux by pressing <code>CTRL</code> + <code>SHIFT</code> + <code>C</code> or <code>F12</code> on Windows.</p>
<p>If you are on Mac, press <code>Command</code> + <code>SHIFT</code> + <code>C</code>.</p>
<h2 id="heading-how-to-inspect-specific-elements-on-a-website">How to Inspect Specific Elements on a Website</h2>
<p>To inspect any element you see on a website, whether it's text, a button, a video, or an image, right-click on the element and click “Inspect”. </p>
<p>In this case, I will right-click on the “Learn to code – for free” text on the freeCodeCamp.org landing page.
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss3-3.png" alt="ss3-3" width="600" height="400" loading="lazy"></p>
<p>The source code will open and the element will be highlighted for you, like this:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss4-2.png" alt="ss4-2" width="600" height="400" loading="lazy"></p>
<p>You can see the text is an <code>h1</code> element.</p>
<h2 id="heading-how-to-manipulate-the-elements-of-a-website-with-the-inspect-tool">How to Manipulate the Elements of a Website with the Inspect Tool</h2>
<p>You can change the text contents of a website with the Inspect tool. </p>
<p>As an example, I’m going to change the “Build projects” text on the freeCodeCamp.org landing page to “Build real-world projects”.</p>
<p>To do this, right-click on the element you would like to change and click “Inspect”. In this case, it's the “Build projects” text:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss5-2.png" alt="ss5-2" width="600" height="400" loading="lazy"></p>
<p>Double-click on the “Build projects” text:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss6-2.png" alt="ss6-2" width="600" height="400" loading="lazy"></p>
<p>Type in “Build real-world projects” and hit <code>ENTER</code>:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss7-1.png" alt="ss7-1" width="600" height="400" loading="lazy"></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/ss8-1.png" alt="ss8-1" width="600" height="400" loading="lazy">
You can see the text has been changed to “Build real-world projects”.</p>
<h2 id="heading-how-to-change-styles-with-the-inspect-tool">How to Change Styles with the Inspect Tool</h2>
<p>Let’s change the background of the “Get started (it’s free)” button to my favorite color – #2ecc71.</p>
<p>Right-click on the button and select “Inspect”:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss9-1.png" alt="ss9-1" width="600" height="400" loading="lazy"></p>
<p>Double-click on the value of the “background-image” property on the right, that is <code>linear-gradient(#fecc4c,#ffac33)</code>.</p>
<p>Change the colors to <code>#2ecc71,#2ecc72</code> and hit <code>ENTER</code>:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss10-1.png" alt="ss10-1" width="600" height="400" loading="lazy">
You can see the background of the button has changed.</p>
<p>We have now made 2 changes on the freeCodeCamp.org landing page – we changed the “Build projects” text to “Build real-world projects” and we changed the background of the “Get Started (it’s free)” button:
<img src="https://www.freecodecamp.org/news/content/images/2022/04/ss11-1.png" alt="ss11-1" width="600" height="400" loading="lazy"></p>
<h2 id="heading-are-the-changes-you-make-with-the-inspect-tool-permanent">Are the changes you make with the Inspect tool permanent?</h2>
<p>No. Any change you make with the Inspect tool is not permanent. Once you reload the page, the changes are gone.</p>
<p>This is because the website has been deployed to a server. So when you make another request to that server by reloading the page, the content from the server is loaded by your browser.</p>
<p>So don't worry - you playing around in this way with the Inspect tool won't change a website permanently. It just helps you learn more about it and practice your coding :)</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article showed you how to get access to the Developer tools of Google Chrome, how to use its Inspect feature to view the source code of a website, and how to change the elements and styles of a website with it.</p>
<p>If you just started learning to code in HTML, CSS, and JavaScript, the Inspect feature of Chrome Dev tools is a powerful tool you can use to view the source code of any website so that you can learn about how they are built.  </p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use CSS Overview in Chrome Dev Tools ]]>
                </title>
                <description>
                    <![CDATA[ If you're a web developer, you probably appreciate a well-designed and attractive website.  And you might see a color scheme or font on a specific website that you want to incorporate into your blog or web app. But you'll need a browser extension to ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-css-overview-in-chrome-developer-tools/</link>
                <guid isPermaLink="false">66bb89f4add24ba4273250ed</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Cess ]]>
                </dc:creator>
                <pubDate>Mon, 07 Feb 2022 14:52:34 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/HTML-Best-Practices---How-to-Build-a-Better-HTML-Based-Website-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're a web developer, you probably appreciate a well-designed and attractive website. </p>
<p>And you might see a color scheme or font on a specific website that you want to incorporate into your blog or web app. But you'll need a browser extension to see the website's color scheme and other CSS features.</p>
<p>The <strong>CSS overview</strong> feature in Chrome Dev Tools lets you see these CSS properties.</p>
<p>In this post, we'll go over how to use the <strong>CSS overview</strong> feature in Chrome Developer Tools. We'll also learn how to use <strong>CSS overview</strong> to get the colors and other CSS properties you want to use in creating a web page.</p>
<p>Let's get started. 💃</p>
<h2 id="heading-what-is-the-css-overview-panel">What is the CSS overview panel?</h2>
<p>The <strong>CSS overview panel</strong> is one of the newest features of Chrome Developer Tools. It serves as a preview tool that allows you to see the different CSS properties used in creating a web page.</p>
<p>It displays CSS properties such as:</p>
<ul>
<li>The colors used on a web page.</li>
<li>The line height of each element used on a web page.</li>
<li>The font-size of each element used on a web page</li>
<li>The font-families of each element on a web page.</li>
<li>The font weights of each element used on a web page.</li>
</ul>
<h2 id="heading-what-are-chrome-developer-tools">What are Chrome Developer Tools?</h2>
<p><strong>Chrome Developer Tools</strong> is also known as Chrome Dev Tools.</p>
<p><strong>Chrome Dev Tools</strong> are a suite of web developer tools that come pre-installed in the Chrome browser. </p>
<p>Check out this article to know more about <a target="_blank" href="https://developer.chrome.com/docs/devtools/">Chrome developer tools.</a></p>
<p>Here are some of the advantages of using <strong>Chrome Dev Tools</strong>:</p>
<ul>
<li><p>It enables you to create better websites in a shorter amount of time.</p>
</li>
<li><p>It enables you to make changes to your code, test it, and inspect it.</p>
</li>
<li><p>Chrome Dev Tools give developers more control over their web applications and browsers. </p>
</li>
<li><p>It enables you to assess the general performance of a website. </p>
</li>
</ul>
<h2 id="heading-how-to-access-chrome-developer-tools-in-your-browser">How to Access Chrome Developer Tools in Your Browser</h2>
<p>You can access Chrome Developer tools in three different ways:</p>
<ol>
<li><p><strong>Chrome’s Menu</strong>:</p>
</li>
<li><p>Click on the three vertical dots located on the top right corner of your chrome browser. It will bring up a drop-down menu with <code>more tools</code> at the bottom of the screen. </p>
</li>
<li><p>Click on more tools.</p>
</li>
<li><p>Click on developer tools.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design.png" alt="Untitled-design" width="600" height="400" loading="lazy"></p>
<ol start="2">
<li><p><strong>Inspect:</strong> </p>
</li>
<li><p>Right-click on the chrome browser.</p>
</li>
<li><p>Click on inspect.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--1--2.png" alt="Untitled-design--1--2" width="600" height="400" loading="lazy"></p>
<ol start="3">
<li><p><strong>Shortcuts:</strong> </p>
</li>
<li><p>For Windows - <code>CTRL</code> + <code>Shift</code> + <code>I</code> OR <code>F12</code>. </p>
</li>
<li><p>For Mac - <code>CMD</code> + <code>Shift</code> + <code>I</code>. </p>
</li>
</ol>
<p>Once you click on the shortcut keys, the developer tools open.</p>
<p>When you press <code>CTRL</code> + <code>Shift</code> + <code>I</code>, it displays the last panel you opened by default. It shows the element, console, network, or performance panel, among other things.</p>
<p><code>CTRL</code> + <code>Shift</code> + <code>C</code> opens the <strong>element panel</strong> first by default.</p>
<h2 id="heading-how-to-use-css-overview-in-chrome-dev-tools">How to Use CSS Overview in Chrome Dev Tools</h2>
<p>The steps below will walk you through how to use the CSS overview feature to get the CSS properties used on a web page.</p>
<h2 id="heading-step-1-open-chrome-dev-tools">Step 1 - Open Chrome Dev tools</h2>
<p>We've already covered the various methods for accessing Chrome developer tools. You should be familiar with them now.</p>
<p>As a quick reminder, you can open Chrome dev tools by pressing <code>Ctrl</code> + <code>Shift</code> + <code>I</code> on Windows and Linux. Use <code>CMD</code> + <code>Option</code> + <code>I</code> on Mac.</p>
<h2 id="heading-step-2-click-on-more-tools">Step 2 - Click on More tools</h2>
<p>Click on the three vertical dots located on the top-right of Chrome dev tools.</p>
<p>Select "More Tools" from the drop-down menu. </p>
<p>You'll discover a variety of options when you click "More Tools." From the various options, select the <strong>CSS overview</strong> feature.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--1--3.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-3-click-on-capture-overview">Step 3 - Click on Capture Overview</h2>
<p>When you click on <strong>CSS Overview</strong>, you'll see a list of its functions.</p>
<p>Functions such as:</p>
<ul>
<li><p>Capture an overview of your page's CSS.</p>
</li>
<li><p>Identify potential CSS improvements.</p>
</li>
<li><p>Locate the affected elements in the element panel.</p>
</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--2-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click on the <strong>Capture Overview button</strong>.</p>
<p>A menu with five sections appears after clicking the Capture Overview button.</p>
<p>The five sections are:</p>
<ul>
<li>Overview summary</li>
<li>Colors</li>
<li>Font info</li>
<li>Unused declarations</li>
<li>Media queries</li>
</ul>
<p>Let's go over each of the five sections one by one to see how they work.</p>
<h2 id="heading-css-overview-summary">CSS Overview Summary</h2>
<p>The <strong>Overview summary</strong> contains a list of the CSS elements used in building the web page.</p>
<p>The Overview summary displays a summary of the CSS on your website, such as:</p>
<ul>
<li>The number of elements used on the web page.</li>
<li>The different types of selectors used in creating the web page.</li>
<li>The number of inline style elements used on the web page.</li>
<li>The number of external stylesheets used on the web page.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/overview-summary.png" alt="Image" width="600" height="400" loading="lazy">
<em>An Illustration of the overview summary.</em></p>
<p>The example above shows the various CSS elements used to build the web page.</p>
<h2 id="heading-colors">Colors</h2>
<p>The color panel displays all the colors used in creating the web page. It has a palette of colors for the background, text, fill, and borders. It also highlights low-contrast texts issues on the web page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/color-ya-boy.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the color panel.</em></p>
<p>The image above shows you the different colors used in creating the web page.</p>
<p>The beauty of the Color panel is that each color is clickable. If you click on a particular color in the Color panel, a list of elements that use that color appears. When you click on each element, it takes you to the element panel for inspection.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--6-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>I clicked on color <code>#49FCD4</code> in the image above, and it brought up a list of elements with that color.</p>
<p>You can also hover over an element in the lists of elements displayed. When you move your cursor over the element, it highlights the element on the web page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Untitled-design--7-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>When I hover my mouse over the <code>header</code> element in the image above, it highlights the header  on the web page.</p>
<p>Just a quick note: hovering refers to moving your cursor over anything. It means to place a cursor over text, an image, or other objects on the screen without clicking on them.</p>
<h2 id="heading-font-info">Font Info</h2>
<p>The font info panel displays the typefaces used in the development of the website. It shows you the <code>font-size</code>, <code>line-height</code>, <code>font-weight</code>, and <code>font families</code> used in creating the website. If you click on the <strong>occurrences</strong>, you will see a list of the affected elements.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/use-this-one-for-header.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the font info panel.</em></p>
<p>The above image shows you the different typefaces used in creating the web page. </p>
<h2 id="heading-unused-declarations">Unused Declarations</h2>
<p>You can find CSS styles that do not affect the web page by using <strong>unused declarations</strong>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/declare-my-boy.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the unused declaration panel.</em></p>
<p>The image above shows the number of unused declarations on the web page. The vertical alignment applied to the element that isn't inline or a table cell will not affect the page.</p>
<p>You can also click on the <strong>occurrences</strong> to see a list of elements affected, like the <strong>font info</strong> and <strong>color panel</strong>.</p>
<h2 id="heading-media-queries">Media Queries</h2>
<p>The media query panel displays a list of all the media queries used in creating the web page. You will be able to examine the various widths and screen resolutions used in creating the web page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/media-query-my-boy.png" alt="Image" width="600" height="400" loading="lazy">
<em>An illustration of the media query panel.</em></p>
<p>The above example displays the number of media queries used in creating the web page. It lists the screen resolutions used in order of occurrence, from highest to lowest. If you click on the <strong>occurrences</strong>, you will see a list of the affected elements.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When it comes to evaluating CSS attributes on a web page, the <strong>CSS overview</strong> tool comes in handy. It allows front-end developers and designers to inspect the CSS properties on a web page.</p>
<p>Thank you for reading 💙. If you would like to chat or have any questions, please feel free to contact me anytime on Twitter: <a target="_blank" href="https://twitter.com/Cessss_">@cessss_</a> and linkedIn: <a target="_blank" href="https://www.linkedin.com/in/success-eriamiantoe/">Success</a>.</p>
<p>Also, follow my blog to read some of my other stuff <a target="_blank" href="https://cesscode.hashnode.dev">@cesscode</a>.</p>
<p>Happy coding! 💙</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Turn On Dark Mode on Google – Chrome Black Theme Tutorial ]]>
                </title>
                <description>
                    <![CDATA[ These days, lots of developers like to use dark mode. And it's no surprise, as activating dark mode reduces eye strain and leads to better eye health. It also helps both machines and humans perform better overall. In this article, I will show you how... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-turn-on-dark-mode-on-google-chrome-black-theme-tutorial/</link>
                <guid isPermaLink="false">66adf149febac312b73075b6</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ dark mode ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Windows ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Fri, 08 Oct 2021 19:10:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/10/dark.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>These days, lots of developers like to use dark mode. And it's no surprise, as activating dark mode reduces eye strain and leads to better eye health. It also helps both machines and humans perform better overall.</p>
<p>In this article, I will show you how to turn on the dark theme on your Google Chrome app, on both Windows machines and Android phones. We'll also learn how to turn on dark theme for Google with some Chrome extensions. </p>
<p>It doesn’t end there – you will also be able to run your Windows apps in dark mode after reading this article.</p>
<h2 id="heading-how-to-turn-on-dark-mode-for-google-on-windows-10">How to Turn on Dark Mode for Google on Windows 10</h2>
<p><strong>Step 1</strong>: To turn on dark theme for Google on your Windows 10 PC, click on Start, or press the <code>WIN</code> (Windows) key.</p>
<p><strong>Step 2</strong>: Click on Settings
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-1.jpg" alt="ss-1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Click on Personalization
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-2.jpg" alt="ss-2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: Select Colors from the menu tab
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-3.jpg" alt="ss-3" width="600" height="400" loading="lazy"></p>
<p><strong>Step 5</strong>: And finally, under “Choose your default app mode”, choose dark.
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-4.jpg" alt="ss-4" width="600" height="400" loading="lazy"></p>
<p>The settings app itself will change to dark mode, meaning all the apps on your machine are now running on dark mode.
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-5-1.png" alt="ss-5-1" width="600" height="400" loading="lazy"></p>
<p>To confirm that Google is now running on dark mode, open up the Chrome app and search anything on Google:
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-6.png" alt="ss-6" width="600" height="400" loading="lazy"></p>
<p>Other parts of Chrome will run on dark theme, too:
<img src="https://www.freecodecamp.org/news/content/images/2021/10/other-parts-of-chrome.png" alt="other-parts-of-chrome" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-turn-on-dark-mode-for-google-with-a-chrome-extension">How to Turn on Dark Mode for Google With a Chrome Extension</h2>
<p>To turn on dark theme for Google with a Chrome extension, download and activate the Just Black chrome extension from the Chrome Web Store. </p>
<p>The extension was built by the Chrome team, so it's safe to install and use.
<img src="https://www.freecodecamp.org/news/content/images/2021/10/just-black.png" alt="just-black" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-turn-on-dark-mode-for-google-on-android-phones">How to Turn on Dark Mode for Google on Android Phones</h2>
<p><strong>Step 1</strong>: To turn on dark mode for Google on an Android phone, open your chrome app and click on the three dots in the top right corner.
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-a1.jpg" alt="ss-a1" width="600" height="400" loading="lazy"></p>
<p><strong>Step 2</strong>: Tap settings
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-a2.jpg" alt="ss-a2" width="600" height="400" loading="lazy"></p>
<p><strong>Step 3</strong>: Select theme
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-a3.jpg" alt="ss-a3" width="600" height="400" loading="lazy"></p>
<p><strong>Step 4</strong>: Finally, choose dark
<img src="https://www.freecodecamp.org/news/content/images/2021/10/ss-a4.jpg" alt="ss-a4" width="600" height="400" loading="lazy"></p>
<p>Your Chrome mobile app should be in dark mode, including the Google search page:
<img src="https://www.freecodecamp.org/news/content/images/2021/10/Screenshot_20211008-163435.png" alt="Screenshot_20211008-163435" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you learned how to turn on dark mode for Google on Windows machines and Android phones. </p>
<p>You also learned how to do the same with a chrome extension. </p>
<p>There are several other extensions that give you dark mode on Chrome, so feel free to check them out on the Chrome Web Store.</p>
<p>Thank you for reading, and have a nice time.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Clear Search History – Delete All Browsing Data in Chrome ]]>
                </title>
                <description>
                    <![CDATA[ Your browsing history can come in very handy when you want to look up  something that you have already viewed on the internet. Maybe you read an interesting article online and want to reference it again, but forgot to bookmark it. Well, you can have ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-clear-search-history-delete-all-browsing-data-in-chrome/</link>
                <guid isPermaLink="false">66b1e416a48ebbb08ba20892</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ privacy ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dionysia Lemonaki ]]>
                </dc:creator>
                <pubDate>Fri, 23 Jul 2021 21:42:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/07/pexels-caio-67112.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Your browsing history can come in very handy when you want to look up  something that you have already viewed on the internet.</p>
<p>Maybe you read an interesting article online and want to reference it again, but forgot to bookmark it. Well, you can have a look at your Chrome history and you'll find it there.</p>
<h2 id="heading-how-does-browser-history-work">How Does Browser History Work?</h2>
<p>While surfing the web, Chrome (like all other modern browsers) keeps track of your digital footsteps. This means it collects data on your preferences, searches, and history.</p>
<p>When using Google Search in Chrome, your search queries are saved. Next time you look for something similar, Google makes relavent suggestions.</p>
<p>It does this to enhance your browser experience by speeding up page load time, for intuitive web navigation, and to autofill forms (which again saves time).</p>
<p>Or Chrome can use all your information and the links you clicked on to target you with personalised Google Ads.</p>
<h3 id="heading-browser-history-privacy-concerns">Browser History Privacy Concerns</h3>
<p>This can raise privacy and security concerns. You may not want to leave traces of your previous searches, visited websites, and all web activity that includes sensitive data on your computer.</p>
<p>It's always a good idea to clean up of your search history and browsing data from time to time. This includes caches and cookies, too, for an optimal browsing experience and safer surfing.</p>
<p>This artcile shows you how to delete <em>all</em> of your Chrome search history and browsing data for whatever operating system you're using.</p>
<p>With just a few clicks and some easy steps you'll get rid of the internet history kept in your Chrome account.</p>
<p>It's worth mentioning that if you have set up your Chrome account to be in sync with all other devices you have (whether that's a laptop, phone, tablet etc) you will also have to delete your history on them.</p>
<h2 id="heading-howe-to-delete-your-chrome-history">Howe to Delete Your Chrome History</h2>
<p>In a few quick steps, you can delete everything all at once.</p>
<p>First things first, open your Chrome browser application.</p>
<p>On the top right hand corner there are three tiny dots next to your profile picture:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-11.43.07-AM.jpeg" alt="Screenshot-2021-07-23-at-11.43.07-AM" width="600" height="400" loading="lazy"></p>
<p>After clicking on them, a menu appears. </p>
<p>From that menu, select "History" and then "History" again.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-12.55.51-PM.png" alt="Screenshot-2021-07-23-at-12.55.51-PM" width="600" height="400" loading="lazy"></p>
<p>You can also use a keyboard shortcut:</p>
<ul>
<li><strong>For Mac users</strong> : <code>Command Y</code></li>
<li><strong>For Windows users</strong> : <code>Ctrl H</code></li>
</ul>
<p>This opens up a new tab with all your browsing history, with the most recent at the very top.</p>
<p>Next, to delete all of it, on the left hand side select "Clear browsing data".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-11.36.48-AM.jpeg" alt="Screenshot-2021-07-23-at-11.36.48-AM" width="600" height="400" loading="lazy"></p>
<p>You are then brought to another menu in the center of the screen, which looks something like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-11.38.20-AM.png" alt="Screenshot-2021-07-23-at-11.38.20-AM" width="600" height="400" loading="lazy"></p>
<p>There are two tabs " Basic" and "Advanced".</p>
<p>In the Basic tab, at the top of the pop up, you have the option to select a time and date frame. You can clear data from the last hour, 24 hours, 7 days, 4 weeks, or from all time.</p>
<p>To delete the entire browser data history, make sure all three checkboxes are checked – "Browsing History", "Cookies and other site data", and "Cached images and files".</p>
<p>Click on the time range dropdown menu and select "<strong>All time</strong>".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-5.06.37-PM.png" alt="Screenshot-2021-07-23-at-5.06.37-PM" width="600" height="400" loading="lazy"></p>
<p>Lastly, select "Clear data".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-6.17.08-PM.jpeg" alt="Screenshot-2021-07-23-at-6.17.08-PM" width="600" height="400" loading="lazy"></p>
<p>All history, cookies, and cache are now gone from your machine.</p>
<p>Chrome now won't show the pages you've previously visited, as all traces are gone. It will also not autocomplete URLs in the address bar or keywords during searches.</p>
<p>To go that extra step and get a bit more specific, you could select the "Advanced Tab" and then check/uncheck according to your needs. If you want a clean slate and to start afresh, make sure to check all boxes.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-12.31.34-PM.png" alt="Screenshot-2021-07-23-at-12.31.34-PM" width="600" height="400" loading="lazy"></p>
<p>Again, select the time frame you want. If you want to get rid of everything, choose "All time" and then "Clear Data".</p>
<p>Chrome now will not show files you downloaded (it won't delete the actual downloaded files – they live on your computer's hard drive). It will not  have the data to automatically fill in forms. It will not have saved passwords and login information. And it won't know what your language settings are or your location information or the different site settings. </p>
<h3 id="heading-what-are-cookies-and-caches">What are cookies and caches?</h3>
<p>Chrome stores cookies from visited websited and cached files.</p>
<p>Cookies recognize who you are. </p>
<p>For example, when you look up the temperature for today, cookies know to show the city where you live. Other cookies are responsible for the personalized ads that keep popping up. If you keep searching for shoes, you'll get ads from different online shoe stores.</p>
<p>They also hold onto login credentials, preferences, and sensitive auto-fill form information (like name, address, and phone number).</p>
<p>Caching stores images and graphics from websites you've visited again and again by downloading a local copy. This makes for faster access and the browser loads super fast.</p>
<p>Once the cache has been deleted, pages may take a bit longer to load because the browser has to pull images from the web instead of locally. But this can fix page loading errors and is the first step to fixing browser issues.</p>
<h2 id="heading-how-to-avoid-constantly-deleting-your-history">How to Avoid Constantly Deleting Your History</h2>
<p>You don't need to repeat the process mentioned above all the time – you can limit what data Google can collect from you. </p>
<p>You can disable your search and location history in Google settings. This is particularly useful when you're sharing a computer with others, if you're using a public computer, or if you're taking extra care of your privacy.</p>
<p>1) Click on your User account icon with your profile picture at the top right corner in Chrome.
2) Select "Manage your Google Account". This takes you to <a target="_blank" href="https://myaccount.google.com/">https://myaccount.google.com/</a></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-6.41.25-PM.png" alt="Screenshot-2021-07-23-at-6.41.25-PM" width="600" height="400" loading="lazy"></p>
<p>3) On the left side, choose "Data and personalisation". This page lets you know what information Google is gathering.
4) In the "Activity and controls" click "Web and App activity" which you can toggle off by clicking the blue slider button. You'll be prompted with a confirmation. In the "Activity and controls" panel you can also control if your location is saved.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-6.42.50-PM.jpeg" alt="Screenshot-2021-07-23-at-6.42.50-PM" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-use-incognito-mode-to-securely-browse-the-web">How to Use Incognito Mode to Securely Browse the Web</h2>
<p>Chrome has a safer and more private option for browsing the web. Incognito is a private mode offered by many modern browsers.</p>
<p>Using Incognito mode, Chrome will not store the sites you have visited in your browsing history and your activity is kept private.</p>
<p>When the window is closed, all browsing history activity, searches, cookies and caching from that session are automatically deleted and not saved. The browser won't know what you've been up to.</p>
<p>However, keep in mind that if you log into a site using login information, the site will recognise you and record your activity.</p>
<p>To open a new window in Incognito, go to the three little dots next to your profile picture in Chrome, click on them, and from the dropdown menu select "New Incognito Window":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-7.12.08-PM.png" alt="Screenshot-2021-07-23-at-7.12.08-PM" width="600" height="400" loading="lazy"></p>
<p>And ta-da! Chrome opens up a new window that does not track your browsing activity or load information from previous searches or forms you've filled.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-23-at-7.14.55-PM.png" alt="Screenshot-2021-07-23-at-7.14.55-PM" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we learned how to delete all of our brorwsing data from Chrome in a few simple steps. I also shared a couple suggestions to make your browsing activities a bit more secure. </p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is about:blank? Chrome Blank Homepage Guide ]]>
                </title>
                <description>
                    <![CDATA[ You might have heard of about:blank but don't understand what it is. Or you've seen it appear when you first load up a fresh new session in the Chrome browser – just an empty white page with about:blank showing in your address bar. You might even see... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-is-about-blank-chrome-blank-homepage-guide/</link>
                <guid isPermaLink="false">66b1e4dd88a49cff617991e9</guid>
                
                    <category>
                        <![CDATA[ Browsers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Dionysia Lemonaki ]]>
                </dc:creator>
                <pubDate>Wed, 21 Jul 2021 21:02:54 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/07/brandi-redd-aJTiW00qqtI-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>You might have heard of about:blank but don't understand what it is. Or you've seen it appear when you first load up a fresh new session in the Chrome browser – just an empty white page with about:blank showing in your address bar.</p>
<p>You might even see it when you click a link to download something from the web or when clicking on a link to take you to another webpage.</p>
<p>Have you wondered why about:blank appears and what is it exactly? Let's find out.</p>
<h2 id="heading-should-you-worry-about-aboutblank">Should you worry about about:blank?</h2>
<p>So is about:blank just a blank page as the name suggests, or is there more to it than meets the eye?</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-21-at-1.43.36-PM.png" alt="Screenshot-2021-07-21-at-1.43.36-PM" width="600" height="400" loading="lazy"></p>
<p>Does it have any benefits, or is it a useful feature? Should you keep it and use it, or should you get rid off it?</p>
<p>When about:blank shows up, it can be confusing for people and makes them worry. They see a blank page and think it's a virus – some dangerous malware – or that it means something has happened to their browser. They don't understand what it is and what is causing it.</p>
<p>This article answers the above questions. It covers why this page shows up in your browser URL field, why you may actually want to keep it, and if not, how to make it disappear and stop it from showing up.</p>
<h2 id="heading-why-does-your-browser-show-aboutblank">Why does your browser show about:blank?</h2>
<p>If you are seeing an empty page with about:blank as the address, you are indeed viewing an empty page that is built into Chrome. In fact, this is common in most modern web browsers like Mozilla Firefox, Apple Safari, Microsoft Edge, Internet Explorer and others.</p>
<p>This page is loaded by default when the browser has nothing else to display. </p>
<p>When a user hasn't specified a specific webpage to load when they first open up the browser, the browser itself does not know what to show So it chooses to show a special blank page that is built into it.</p>
<p>The browser always has to display something after all, even if that is blank.</p>
<h3 id="heading-is-aboutblank-a-virus">Is about:blank a virus?</h3>
<p>In and of itself about:blank is not malware. It is as innocent as it can be.</p>
<p>In general, you don't need to worry about it.</p>
<p>In some very rare cases about:blank can be a <em>symptom</em> of a virus.</p>
<p>It can indicate that your computer has detected something dangerous and is blocking viruses/malware when it comes across with a bad link/URL. </p>
<p>It can also appear after you have removed malware. After such a process, the browser won't know what action to perform.</p>
<p>In any case, if you are concerned that your computer has a virus, it is a good idea to investigate that and perform a scan with an antimalware program of your choice.</p>
<h2 id="heading-what-is-aboutblank">What is about:blank?</h2>
<p>As mentioned earlier, this is just a page built into your browser. </p>
<p>It's not a webpage, that is a page coming from the Internet. It's nothing more than a blank screen appearing in your browser.</p>
<p>Specifically, it's part of the "about" URI Scheme which is impelemented by a variety of different browsers. </p>
<p>The "about" URI Scheme is part of a naming convention that tells the browser to show its internal, built-in pages.</p>
<p>Besides about:blank, there is a long list of Chrome URLs, including some of the below:</p>
<ul>
<li>about:downloads to view Chrome's download list</li>
<li>about:bookmarks to view your saved bookmarks</li>
<li>about:accessibility shows accessibility information for each tab opened in the browser</li>
<li>about:apps to view the apps that come with the Chrome browser and those installed by the user</li>
</ul>
<p>If you type about:about you'll be able to go through them all:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-21-at-1.52.54-PM.png" alt="Screenshot-2021-07-21-at-1.52.54-PM" width="600" height="400" loading="lazy"></p>
<h2 id="heading-benefits-of-aboutblank">Benefits of about:blank</h2>
<p>This page is a favourite homepage choice for many users. It ensures their browser loads up with a white empty page when it opens for the first time.</p>
<p>It makes for a blazingly fast homepage as it starts the browser really quikcly. The browser doesn't lose time by making a request for a page across the Internet and waiting for the response.</p>
<p>So, this can help reduce your data usage if your internet connection is slow and you have limited badwidth. You are able to save computer and network resources.</p>
<p>A default blank page can also help you stay focused and not get distracted. You can just focus on searching for and visiting the page you want to view.</p>
<h2 id="heading-how-to-set-aboutblank-as-your-homepage">How to set about:blank as your homepage</h2>
<p>If you want to make about:blank your default start page, you'll need to configure your browser's settings.</p>
<p>Go to your settings menu and select "Settings":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-21-at-2.00.05-PM.png" alt="Screenshot-2021-07-21-at-2.00.05-PM" width="600" height="400" loading="lazy"></p>
<p>(you could have even typed about:settings!)</p>
<ul>
<li>In the search bar type "start-up".</li>
<li>Select "Open a specific page or set of pages". </li>
<li>Click "Add a new page" and type "about:blank"
<img src="https://www.freecodecamp.org/news/content/images/2021/07/Screenshot-2021-07-21-at-2.06.29-PM.png" alt="Screenshot-2021-07-21-at-2.06.29-PM" width="600" height="400" loading="lazy"></li>
<li>Finally,press "Add"</li>
</ul>
<p>When you open a new tab you'll see an empty page.</p>
<h2 id="heading-how-to-stop-aboutblank-from-showing">How to stop about:blank from showing</h2>
<p>This page is not something you just "get rid off" as it is a feature built into your browser and it'll always be there, under the hood.</p>
<p>If your browser always opens up with a blank page and you don't like it, you can stop that from happening. It's probably set as a default home page and you need to change your settings.</p>
<p>Following the steps mentioned in the previous section, delete "about:blank" from the "Open a specific page or set of pages" option and select your favorite web page. Or you can select "Open the New Tab page" instead.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Overall, about:blank is a safe feature and shouldn't raise any concerns.</p>
<p>And if you don't like seeing it when you open your browser, you can just change it in your settings. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What are Bookmarklets? How to Use JavaScript to Make a Bookmarklet in Chromium and Firefox ]]>
                </title>
                <description>
                    <![CDATA[ Bookmarklets are browser bookmarks that execute JavaScript instead of opening a webpage. They're also known as bookmark applets, favlets, or JavaScript bookmarks. Bookmarklets are natively available in all major browsers, including Mozilla Firefox an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-bookmarklets/</link>
                <guid isPermaLink="false">66d460fa47a8245f78752ac7</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firefox ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Seth Falco ]]>
                </dc:creator>
                <pubDate>Thu, 17 Jun 2021 00:53:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/06/cover-defectivefox-o-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="https://en.wikipedia.org/wiki/Bookmarklet">Bookmarklets</a> are browser bookmarks that execute JavaScript instead of opening a webpage. They're also known as bookmark applets, favlets, or JavaScript bookmarks.</p>
<p>Bookmarklets are natively available in all major browsers, including Mozilla Firefox and Chromium-based browsers like Chrome or Brave.</p>
<h2 id="heading-scripting-with-javascript">Scripting with JavaScript</h2>
<p>Learning how to write scripts provides many benefits, namely the huge time-savings from automating repetitive or tedious tasks.</p>
<p>If you aren't a developer, the idea of learning to code might be intimidating, however scripting doesn't require software engineering knowledge or design patterns. The goal isn't to make scalable software, but rather to automate specialized or trivial tasks.</p>
<p>Regardless of profession, even if you've never written code before, consider what you do in your browser. If you ever feel what you do is repetitive or robotic, consider the possibility of delegating the task to an actual robot.</p>
<h2 id="heading-use-cases-for-bookmarklets">Use Cases for Bookmarklets</h2>
<p>With bookmarklets, you can manipulate the current page as the function will have the context of the current tab. This means you can:</p>
<ul>
<li><p>Click buttons virtually</p>
</li>
<li><p>Modify the content</p>
</li>
<li><p>Use the content of the page to open a new page</p>
</li>
<li><p>Remove elements from the page</p>
</li>
</ul>
<p>You can also make bookmarks that don't utilize the context at all, such as conditionally opening a URL, or generating HTML for a new tab.</p>
<p>You'll find some bookmarklets I made for this article in <a class="post-section-overview" href="#heading-example-bookmarklets">Example Bookmarklets</a>. They're just for demonstration, but should make the capabilities and implementations apparent.</p>
<h2 id="heading-how-to-create-bookmarklets">How to Create Bookmarklets</h2>
<p>Creating a bookmarklet is almost identical to creating a regular bookmark. The only difference is that you'll write JavaScript in the URL field instead of an HTTP/HTTPS URL.</p>
<h3 id="heading-navigate-to-the-bookmark-menu">Navigate to the Bookmark Menu</h3>
<h4 id="heading-mozilla-firefox">Mozilla Firefox</h4>
<p>Either in your bookmarks bar, or in the Bookmarks sidebar (<code>CTRL</code> + <code>B</code>), you can right-click, then click "Add Bookmark...":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/firefox-1.png" alt="The &quot;Add bookmark&quot; modal when creating a new bookmark in Firefox." width="600" height="400" loading="lazy"></p>
<h4 id="heading-chromium">Chromium</h4>
<p>You can right-click your bookmarks bar, then click "Add page...". Alternatively, you can go to your Bookmarks manager, then right-click and click "Add new bookmark":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/chromium.png" alt="The &quot;Edit bookmark&quot; modal when creating a new bookmark in Chromium." width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-write-a-bookmarklet">How to Write a Bookmarklet</h2>
<p>In the URL field of the bookmark modal, write a JavaScript function in the following format.</p>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Your code here!</span>
})();
</code></pre>
<p><code>javascript:</code> is the URL's protocol. This indicates that the browser should execute the bookmark as JavaScript.</p>
<p><code>(() =&gt; { })</code> defines an anonymous function (lambda). You should write the code you want to execute between the curly braces.</p>
<p><code>();</code> will execute the anonymous function you just created.</p>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  alert(<span class="hljs-string">'Hello, World!'</span>);
})();
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-147-1.png" alt="A browser alert with the message: &quot;Hello, World!&quot;" width="600" height="400" loading="lazy"></p>
<p>You can also make it generate HTML and open it as an HTML document:</p>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-string">'&lt;h1 style="color: white; background-color: black;"&gt;Hello, World!&lt;/h1&gt;'</span>;
})();
</code></pre>
<h3 id="heading-spacing-for-bookmarklets">Spacing for Bookmarklets</h3>
<p>Most browsers don't allow a multiline input field in the bookmark URL, so you'll usually have to make strict use of curly braces (<code>{</code> and <code>}</code>) and semi-colons (<code>;</code>) when writing bookmarklets. This is especially important when scoping conditional constructs (<code>if</code>/<code>for</code>/<code>while</code>).</p>
<p>Other than this, spacing doesn't matter. Don't be afraid to have a lot of code in one line because that's all you've got:</p>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {   <span class="hljs-keyword">const</span> documentHTML = <span class="hljs-built_in">document</span>.documentElement.outerHTML;   <span class="hljs-keyword">const</span> matches = documentHTML.matchAll(<span class="hljs-regexp">/[\w.+=~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*/g</span>);   <span class="hljs-keyword">const</span> flatMatches = <span class="hljs-built_in">Array</span>.from(matches).map(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item[<span class="hljs-number">0</span>]);   <span class="hljs-keyword">const</span> uniqueMatches = <span class="hljs-built_in">Array</span>.from(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(flatMatches));      <span class="hljs-keyword">if</span> (uniqueMatches.length &gt; <span class="hljs-number">0</span>) {     <span class="hljs-keyword">const</span> result = uniqueMatches.join(<span class="hljs-string">'\n'</span>);     alert(result);   } <span class="hljs-keyword">else</span> {     alert(<span class="hljs-string">'No emails found!'</span>);   } })();
</code></pre>
<p>If your script is complex, it'll be easier to maintain your bookmarklet in a code editor like <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a>. You can copy and paste it over to your browser when it's ready.</p>
<h3 id="heading-how-to-interact-with-websites">How to Interact with Websites</h3>
<p>The most common thing you'd do with bookmarklets is manipulating or interacting with websites you have open.</p>
<h4 id="heading-the-global-document-object">The Global Document Object</h4>
<p>As the bookmarklet has the context of the page you're on, you have access to the <code>[document](https://developer.mozilla.org/en-US/docs/Web/API/Document)</code> object.</p>
<p>The ideal functions for selecting elements for our use case are:</p>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector"><code>querySelector</code></a> to select a single element by CSS selector.</p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll"><code>querySelectorAll</code></a> to select all matching elements by CSS selector.</p>
</li>
<li><p><code>[evaluate](https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate)</code> to select all matching elements by XPath.</p>
</li>
</ul>
<p>There are other functions like <code>[getElementById](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)</code> and <code>[getElementsByClassName](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName)</code>, but we want to avoid false-positives, so we'll always make a strict selection using multiple element attributes.</p>
<h4 id="heading-css-selectors-and-xpath">CSS Selectors and XPath</h4>
<p>If you're only selecting elements based on element names, IDs, classes, and other attributes, using a CSS selector will be simple and efficient.</p>
<p>CSS selectors are used to select elements in HTML documents to apply styles. If you're familiar with web development or CSS in general, then you already know how to use CSS selectors. (More Info: <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors">MDN</a>, <a target="_blank" href="https://www.freecodecamp.org/news/css-selectors-cheat-sheet/">freeCodeCamp</a>)</p>
<p>If you need to match the text content of an element as well, then you'll have to use XPath instead.</p>
<p>XPath is used to traverse XML documents, it provides all the capabilities of CSS selectors and more, including comparing the content of elements or using a regular expression to match it. (More Info: <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/XPath">MDN</a>, <a target="_blank" href="https://en.wikipedia.org/wiki/XPath">Wikipedia</a>)</p>
<h4 id="heading-how-to-select-elements-from-the-webpage">How to Select Elements from the Webpage</h4>
<p>One of the most common uses for bookmarklets is manipulating webpages. To interact with, manipulate, or remove elements from the page, you'll always have to select the elements first.</p>
<ol>
<li><p>First open the browser development tools by pressing <code>F12</code>, or <code>CTRL</code> + <code>SHIFT</code> + <code>I</code>.</p>
</li>
<li><p>Click the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector">Inspector</a>/<a target="_blank" href="https://developer.chrome.com/docs/devtools/dom/">Elements</a> tab, which displays the full HTML document of the page you have open.</p>
</li>
<li><p>Use the element selector tool (<code>CTRL</code> + <code>SHIFT</code> + <code>C</code>) and click on the element you want to interact with. The document viewer will scroll to the element you clicked in the HTML document. You'll see the element ID, classes, and attributes.</p>
</li>
<li><p>Check if you're on the correct element. Elements can be nested where it's easier to navigate to it manually in the HTML. For example, you may have clicked an <code>svg</code> element, but actually needed the <code>button</code> or <code>div</code> it was inside of.</p>
</li>
<li><p>Define a CSS selector or XPath that matches everything you want, you might want to make it more strict than necessary to avoid potential false-positives.</p>
</li>
</ol>
<p>For example, suppose I wanted to dismiss all topic suggestions on Twitter because they're annoying. Here is how a clickable element to dismiss a topic looks like.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/Screen-Shot-2021-06-16-at-04.19.17.png" alt="Twitter topic suggestions, with an X button to mark it as &quot;Not interested&quot;." width="600" height="400" loading="lazy"></p>
<p><em>Twitter topic suggestions, with an X button to mark it as "Not interested".</em></p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Dismiss"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">tabindex</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"..."</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- The parent div element has the click listener. --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"..."</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 24 24"</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"..."</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- The actual X icon. --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>An appropriate selector is <code>div[aria-label=Dismiss][role=button]</code>.</p>
<p>We need to use the <code>querySelectorAll</code> function from <a class="post-section-overview" href="#heading-the-global-document-object">The Global Document Object</a>, then call the <code>[click](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click)</code> method to simulate a click.</p>
<p>A bookmarklet can be implemented to select every dismiss button, and trigger a click event to all of them with a 250ms interval.</p>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> selector = <span class="hljs-string">'div[aria-label=Dismiss][role=button]'</span>;
  <span class="hljs-keyword">const</span> topics = <span class="hljs-built_in">document</span>.querySelectorAll(selector);

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; topics.length; i++) {
    <span class="hljs-keyword">let</span> topic = topics[i];
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> topic.click(), i * <span class="hljs-number">250</span>);
  }
})();
</code></pre>
<h2 id="heading-how-to-redistribute-bookmarklets">How to Redistribute Bookmarklets</h2>
<p>To "install" a bookmarklet, users create a bookmark on their browser and copy and paste the code to it.</p>
<p>This can be inconvenient, so it's common to link bookmarklets when sharing. This is as simple as putting it in the <code>href</code> attribute of your link anchor.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"javascript: (() =&gt; {   alert('Hello, World!'); })();"</span>&gt;</span>
  Hello, World!
<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</code></pre>
<p>Now users can right-click and "Bookmark Link", or drag it to the bookmarks bar for easy access.</p>
<p>Clicking the link on the web page will execute the script immediately. Ensure it's not going to obstruct what a user is trying to achieve on your site if they accidentally click it.</p>
<p>For example, the following link will display an alert with "Hello, World!".</p>
<h3 id="heading-user-content-and-content-security-policy-bypass">User Content and Content Security Policy Bypass</h3>
<p>If you run a service that allows user-generated content to contain custom HTML, it's important to sanitize link anchors (<code>a</code>).</p>
<p>The bookmarklet is executing like code in the developer tools console, and bypasses the configured <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Content Security Policy</a> (CSP).</p>
<p>The "Hello, World!" link can just as easily send data to another server, including the input of form fields, or cookies.</p>
<p>As a service provider, it's important to be wary that users can exploit this to share malicious code on your platform. If their link anchor is running on a page under your domain, it can access sensitive information on the page and <code>[document.cookies](https://developer.mozilla.org/en-US/docs/web/api/document/cookie)</code>.</p>
<p>You can try it yourself in a sandbox environment:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"javascript: (() =&gt; {   alert(document.cookie); })();"</span>&gt;</span>
  EvilScript
<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</code></pre>
<h3 id="heading-only-run-code-you-trust">Only Run Code You Trust</h3>
<p>As a user, it's important to note that any code can be malicious, only click or add bookmarklets where at least one of the following are true:</p>
<ul>
<li><p>It came from a reputable source.</p>
</li>
<li><p>You know JavaScript, and you reviewed what it does.</p>
</li>
<li><p>Someone you trust knows JavaScript, and they reviewed it for you.</p>
</li>
</ul>
<h2 id="heading-privacy-and-security">Privacy and Security</h2>
<p>Bookmarklets can be handy, but we also have <a target="_blank" href="https://en.wikipedia.org/wiki/Browser_extension">web extensions</a> and <a target="_blank" href="https://en.wikipedia.org/wiki/Userscript">user scripts</a> too. What makes them different?</p>
<p>Web extensions are incredibly user-friendly and flexible. Bookmarklets can't block network requests, update content as the page changes, or manage tabs.</p>
<p>However, there're some benefits to using bookmarklets over anything else, namely for privacy and security.</p>
<p>An extension that modifies the font on all pages must get permission to access all data on all web pages. On Firefox and Chrome, this includes all input and password fields. (More Info: <a target="_blank" href="https://support.mozilla.org/kb/permission-request-messages-firefox-extensions#w_access-your-data-for-all-websites">Mozilla</a>, <a target="_blank" href="https://developer.chrome.com/docs/extensions/mv3/permission_warnings/#permissions_with_warnings">Google</a>)</p>
<p>In contrast, a bookmarklet only has access to the page for the very moment it's executing, and only when manually triggered by the user.</p>
<p>This results in less risk of malware, a rogue employee can't push a malicious update, and data won't silently get sent to other servers.</p>
<p>The Chrome Web Store has previously had several malicious extensions which had to be taken down. Some of which had millions of installations before being removed. (<a target="_blank" href="https://en.wikipedia.org/wiki/Chrome_Web_Store#Malware">More Info</a>)</p>
<h2 id="heading-example-bookmarklets">Example Bookmarklets</h2>
<p>Here's a list of bookmarklet ideas, along with the code that implements it. You can copy and paste them into new bookmarks to try them out.</p>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> documentHTML = <span class="hljs-built_in">document</span>.documentElement.outerHTML;
  <span class="hljs-keyword">const</span> matches = documentHTML.matchAll(<span class="hljs-regexp">/[\w.+=~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*/g</span>);
  <span class="hljs-keyword">const</span> flatMatches = <span class="hljs-built_in">Array</span>.from(matches).map(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> item[<span class="hljs-number">0</span>]);
  <span class="hljs-keyword">const</span> uniqueMatches = <span class="hljs-built_in">Array</span>.from(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(flatMatches));

  <span class="hljs-keyword">if</span> (uniqueMatches.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">const</span> result = uniqueMatches.join(<span class="hljs-string">'\n'</span>);
    alert(result);
  } <span class="hljs-keyword">else</span> {
    alert(<span class="hljs-string">'No emails found!'</span>);
  }
})();
</code></pre>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> xpath = <span class="hljs-string">"//a [contains(., 'Jobs') or contains(., 'Careers') or contains(., 'Hiring')]"</span>;
  <span class="hljs-keyword">const</span> elements = <span class="hljs-built_in">document</span>.evaluate(xpath, <span class="hljs-built_in">document</span>);
  <span class="hljs-keyword">const</span> element = elements.iterateNext();

  <span class="hljs-keyword">if</span> (element) {
    element.click();
  } <span class="hljs-keyword">else</span> {
    alert(<span class="hljs-string">'No links for jobs found!'</span>);
  }
})();
</code></pre>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> allElements = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'*'</span>);

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> element <span class="hljs-keyword">of</span> allElements) {
    element.style.fontFamily = <span class="hljs-string">'Comic Sans MS'</span>;
  }
})();
</code></pre>
<pre><code class="lang-js">javascript: (<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> destination = <span class="hljs-string">"https://www.freecodecamp.org/"</span>;
  <span class="hljs-keyword">const</span> alternate = <span class="hljs-string">"https://tenor.com/Y6jj.gif"</span>;

  <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
  <span class="hljs-keyword">const</span> hours = date.getHours();

  <span class="hljs-keyword">if</span> (hours &lt; <span class="hljs-number">3</span> || hours &gt;= <span class="hljs-number">6</span>) {
    <span class="hljs-built_in">window</span>.open(destination);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">window</span>.open(alternate);
  }
})();
</code></pre>
<p>Thank you for reading! Now go forth and create your own bookmarklets.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn How to Use the Chrome DevTools to Troubleshoot Websites ]]>
                </title>
                <description>
                    <![CDATA[ Chrome DevTools is a set of web developer tools built directly into the Google Chrome browser. They can be super helpful for web developers. We just published a crash course that will teach you how to use Chrome DevTools to improve your productivity ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-how-to-use-the-chrome-devtools-to-troubleshoot-websites/</link>
                <guid isPermaLink="false">66b2048a903dc07a13516674</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 06 May 2021 16:26:08 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/05/chromdev.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Chrome DevTools is a set of web developer tools built directly into the Google Chrome browser. They can be super helpful for web developers.</p>
<p>We just published a crash course that will teach you how to use Chrome DevTools to improve your productivity as a web developer. You will learn how to do things like troubleshoot and live-edit web pages.</p>
<p>Nabheet Madan created this course. He is a very experienced developer and he does an excellent job teaching how to incorporate Chome DevTools into your development workflow.</p>
<p>Here are the sections covered in this course:</p>
<ul>
<li>Getting comfortable with the tool</li>
<li>Elements</li>
<li>Source</li>
<li>Console</li>
<li>Network</li>
<li>Performance</li>
<li>Application</li>
<li>Security</li>
<li>Memory</li>
<li>Lighthouse</li>
</ul>
<p>After getting a general overview of the developer tools, you will learn about the Elements tab. In this tab you can interact with web pages by modifying styles and HTML. </p>
<p>Next, you will learn to troubleshoot Javascript using the Sources tab. You will learn different types of breakpoints, source code modification options, overrides, and more. After that, you will learn about the console. The console is like a REPL in your browser where you can try out code.</p>
<p>You will learn to troubleshoot server side requests and local storage using the Network and Application tabs. Finally, you will learn how to improve performance and security using the Lighthouse, Performance, Memory, and Security tabs.</p>
<p>You can watch the full course below or <a target="_blank" href="https://www.youtube.com/watch?v=gTVpBbFWry8">on the freeCodeCamp.org YouTube channel</a> (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/gTVpBbFWry8" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Chrome Extension Tutorial: How to Pass Messages from a Page's Context ]]>
                </title>
                <description>
                    <![CDATA[ By Tarique Ejaz In the world of web development, Chrome extensions are a pretty handy set of tools to have around.  Whether you use them to add headers to simple requests or to scrape important data from the DOM, extensions help provide extra functio... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/chrome-extension-message-passing-essentials/</link>
                <guid isPermaLink="false">66d4614a246e57ac83a2c7cf</guid>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ chrome extension ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ messaging ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 24 Feb 2021 02:14:59 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/02/DDF5E684-AEF5-45E6-9421-E5D4360E9A85-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Tarique Ejaz</p>
<p>In the world of web development, Chrome extensions are a pretty handy set of tools to have around. </p>
<p>Whether you use them to add headers to simple requests or to scrape important data from the DOM, extensions help provide extra functionality that makes life easier.</p>
<p>I started playing around with developing a Chrome extension for a use-case I had in mind at work. It was then that I stumbled upon the way we pass around certain data from a web page to an extension. And the lack of a simplified guide made me write this article. </p>
<p>We do have the <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/">Chrome API documentation</a>, and it is indeed very thorough. But I consider myself to be more of a visual learner, and being able to visualize how we pass messages between the extension scripts helped simplify the overall development.</p>
<blockquote>
<p><strong>Note:</strong> This article makes use of Manifest V2 instead of V3. The major difference between the two is the usage of service workers in V3 instead of background pages and related actions. </p>
</blockquote>
<h2 id="heading-message-passing-interaction-between-scripts">Message Passing: Interaction Between Scripts</h2>
<p>An extension, as the name suggests, is like a layer on top of the existing webpage you're trying to access. The browser acts as the container.</p>
<p>It mainly comprises the following scripts:</p>
<ul>
<li><strong>Popup Script</strong> - Local JavaScript file for the extension DOM</li>
<li><strong>Background Script</strong> - Provides persistence and handles background events</li>
<li><strong>Content Script</strong> - Scripts that run in isolation in the context of the web page</li>
<li><strong>Injected Script</strong> - Scripts that are programmatically injected into the web page </li>
</ul>
<p>Normally, if you have to merely deal with the DOM content, then the way the extension is developed is relatively straightforward. </p>
<p>The raw HTML is already available to the content script and all you need to do is pass it to the popup script. </p>
<p>However, if you need to access the page's variables and functions, the process gets a little tricky.</p>
<p>The variables and functions available in the page context, say in the <code>window</code> object, are not accessible to the content scripts since they tend to run in a special JavaScript environment. They have access to only the DOM of the page but not the variables and functions. </p>
<p>To access a page's variables and functions, we inject scripts by appending them to the DOM. This makes the browser assume that it is run in the context of the web page. This in turn provides the injected script access to the local variables and functions.</p>
<p>Since Chrome extensions are event-driven because of their architecture, once the injected scripts have access to the page's variables and functions, they can pass it to the content script. </p>
<p>The content script then passes the these objects to the background page. </p>
<p>And finally, the popup script is able to call onto the background page using the Extension API and pass it to the Extension DOM. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/flowchart.png" alt="Image" width="600" height="400" loading="lazy">
<em>Message Passing Overview</em></p>
<p>Now, we will build a simple Performance Watcher extension that reads the performance data from the global window object of a page and maps the essential metrics for the user to see. Let's get into the code then.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/extn.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-enough-talk-show-me-the-code">Enough Talk, Show Me The Code</h2>
<p>You can find the complete code repository for the project <a target="_blank" href="https://github.com/tejazz/article-snippets/tree/master/chrome-extn-message-passing">here</a>. Let's quickly run through the primary files and the important functionalities they offer.</p>
<h3 id="heading-the-manifest-file">The Manifest File</h3>
<p>Every Chrome Extension needs a <code>manifest</code> file. It is basically a JSON-formatted file that provides a set of metadata so the browser can recognize the permissions that need to be granted and the likely operational reach of the extension. </p>
<p>Here is the manifest used for our application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/manifest.png" alt="Image" width="600" height="400" loading="lazy">
<em>manifest.json: metadata for your extension</em></p>
<p>Some of the important properties we need to focus on are the following:</p>
<ul>
<li><code>background</code> - Takes an array of scripts that would be run in the background page. </li>
<li><code>content-scripts</code> - Includes an array of content scripts we wish to run as part of the web page's context. </li>
<li><code>web_accessible_resources</code> - An array of packaged resources expected to be used in a web page's context. For example, an image we intend to embed in a page or a custom script we want to inject.</li>
<li><code>permissions</code> - Allows your extension to gain access to certain Chrome APIs like <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/tabs/#type-Tab">tabs</a> in this case. </li>
</ul>
<h3 id="heading-the-content-script">The Content Script</h3>
<p>Content Scripts have easy access to the DOM of the web page. We make use of the content script to append our custom script – <code>inject-script.js</code> – into the DOM.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/content-inject.png" alt="Image" width="600" height="400" loading="lazy">
<em>content-script.js: inject custom script into the DOM</em></p>
<p>The content script also simultaneously continues to listen for any message being sent upstream from the custom script. </p>
<p>As soon as we get a message from the injected script, we run a quick check on the data received and verify whether our extension is installed. Once done, we simply use Chrome's <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/runtime/">Runtime API</a> to send the data received forward to the background page. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/content-send.png" alt="Image" width="600" height="400" loading="lazy">
<em>content-script.js: send the required data to the background page</em></p>
<h3 id="heading-the-injected-script">The Injected Script</h3>
<p>The custom script can access global variables and functions like the <code>window</code> object. We map only the properties we require. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/inject-action.png" alt="Image" width="600" height="400" loading="lazy">
<em>inject-script.js: procure required object from the page's JS context</em></p>
<p>The message from the custom script is communicated safely to the content script using the <code>[window.postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)</code> function. In this case, a <code>setInterval</code> function is used to dynamically update the properties we are observing.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/inject-send.png" alt="Image" width="600" height="400" loading="lazy">
<em>inject-script.js: send the gathered data to the content-script</em></p>
<h3 id="heading-the-background-script">The Background Script</h3>
<p>The background script listens for any message transmitted by the content script using the Runtime API. The <code>window</code> object of the background page is then updated with the <code>tab.id</code> acting as the identifier. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/background.png" alt="Image" width="600" height="400" loading="lazy">
<em>background.js: listen for incoming message from the content-script</em></p>
<h3 id="heading-the-popup-script">The Popup Script</h3>
<p>The popup script is where we finally read the data we had procured from our custom script. It is also the place where we code any necessary JavaScript operations.</p>
<p>The background page is retrieved using the <code>getBackgroundPage</code> method of the Extension API. The active tab's id is queried using the <code>tabs.query</code> method of the Tabs API in order to correctly extract the relevant data.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/popup.png" alt="Image" width="600" height="400" loading="lazy">
<em>popup.js: reading the global object stored in the background page context</em></p>
<p>In this way, we are able to finally receive and map the data we need – <code>performance</code> in our case – efficiently in our extension.</p>
<p>The UI styling and other cosmetic code are available in the repository, for further reference.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Message passing is an essential concept when it comes to developing a Chrome extension. This is just one of the multiple ways in which you can communicate between scripts. </p>
<p>I spent a few hours in order to figure out how it would work for my use case. Hopefully, this simple walkthrough and the visual representation saves you some time. </p>
<p>I would suggest playing around with the code for a bit. If you have any questions, feel free to reach out to me on <code>[LinkedIn](https://www.linkedin.com/in/tarique-ejaz/)</code>.</p>
<p>In the meantime, keep coding.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Chrome Bookmarks – How To Delete or Recover a Bookmark ]]>
                </title>
                <description>
                    <![CDATA[ Being able to save a web page you visit often is very useful. Perhaps you check the Google Analytics of your blog posts every day, or visit a recipe app for cooking inspiration. Whatever the reason, bookmarks in Chrome can save you time and hassle. J... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/chrome-bookmarks-location-guide-how-to-delete-or-recover-a-bookmark/</link>
                <guid isPermaLink="false">66b1fa0b25ef0bb2c5a516cd</guid>
                
                    <category>
                        <![CDATA[ Browsers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abigail Rennemeyer ]]>
                </dc:creator>
                <pubDate>Tue, 23 Feb 2021 07:18:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/60270e0d0a2838549dcc4732.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Being able to save a web page you visit often is very useful. Perhaps you check the Google Analytics of your blog posts every day, or visit a recipe app for cooking inspiration.</p>
<p>Whatever the reason, bookmarks in Chrome can save you time and hassle. Just bookmark the page, and you can easily come back to it any time.</p>
<p>But what if you want to clean up your bookmarks? After all, you might no longer need that concert tickets website from several years ago. Well, that's easy enough to do, too.</p>
<p>In this article, you'll learn how to find your bookmarks in Chrome, and how to delete them, recover them, and otherwise manage them. Let's get started.</p>
<h2 id="heading-where-are-my-bookmarks-located-in-chrome">Where are my bookmarks located in Chrome?</h2>
<p>First, let's see where to find your bookmarks in the first place. There are a couple spots from which you can access them.</p>
<h3 id="heading-how-to-find-bookmarked-pages-under-the-bookmarks-tab-in-chrome">How to Find Bookmarked Pages under the Bookmarks Tab in Chrome</h3>
<p>First, there's the "Bookmarks" tab in your Chrome menu bar:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-3.30.48-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>The bookmark tab in Chrome is in the top menu bar.</em></p>
<p>If you click on that tab, it will bring up a dropdown menu which shows several options, like "Bookmark Manager" and "Bookmark this tab". It also lists any bookmarks or bookmark folders you currently have:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-3.32.12-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>For example, I have a "freeCodeCamp stuff" bookmark folder that contains many of my bookmarked pages and tabs.</em></p>
<p>If you click on a bookmark folder, it'll expand the list to show you all bookmarks in that folder, like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-3.39.30-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to display your current bookmarks in a bookmark folder</em></p>
<p>Any bookmarked pages that you don't have in a folder will simply be listed under the bookmarks tab in Chrome.</p>
<h3 id="heading-how-to-find-bookmarked-pages-under-settings-in-chrome">How to Find Bookmarked Pages under Settings in Chrome</h3>
<p>Your bookmarks are also located under your settings tab, the three vertical dots in the upper right corner of your browser:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.02.26-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to find your bookmarks in settings.</em></p>
<p>Just click those three dots and you'll see the right dropdown above. Hover over/click on "Bookmarks" and the left menu will pop out. There you'll see a similar bookmark menu with options like "Show Bookmarks Bar", "Bookmark Manager" and so on.</p>
<p>If you hover over, for example, a bookmark folder (the same folder we discussed above), you'll see all your bookmarks. Then you can just click on the one you want to visit.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.06.13-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Voilà - all your bookmarks.</em></p>
<h2 id="heading-how-to-add-a-bookmark-in-chrome">How to Add a Bookmark in Chrome</h2>
<p>There are a few ways to add bookmarks in Chrome. </p>
<ul>
<li>You can visit the bookmarks tab in the Chrome menu bar (that we discussed above) and select "Bookmark this Tab". A separate box will pop up with the name you want to give your bookmark (you can change this - just click into that highlighted box). Then click done.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.15.08-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to add a bookmark in Chrome.</em></p>
<ul>
<li>You can also visit the settings (three vertical dots), click on or hover over the "Bookmarks" tab, and then select "Bookmark this Tab". The same little box will pop up as above.</li>
<li>A third option is to simply click the little star icon in the address bar. That will bookmark the page you're currently on.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.31.42-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Click that star and it'll add a bookmark for you (after you click "Done").</em></p>
<p>But if you don't want to use your mouse and make that many clicks, there's another option.</p>
<h3 id="heading-how-to-add-or-manage-bookmarks-in-chrome-with-keyboard-shortcuts">How to Add or Manage Bookmarks in Chrome with Keyboard Shortcuts</h3>
<p>Sure enough, you can use keyboard shortcuts to add bookmarks and manage your bookmarks.</p>
<p>To bookmark a tab quickly, just key in Command+D / Ctrl+D (see below) - it'll bring up that same bookmark window as above. Then you can just hit enter if you don't want to update the bookmark's name or any other settings and it'll save it.</p>
<p>And to manage your bookmarks, just key in Option+Command+B / Ctrl+Shift+O (again, see below).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.10.43-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to add or manage bookmarks with keyboard shortcuts.</em></p>
<p>But we're getting a little ahead of ourselves. Now let's learn how to manage your bookmarks, and why you might want to do so.</p>
<h2 id="heading-how-to-manage-your-bookmarks-in-chrome">How to Manage your Bookmarks in Chrome</h2>
<p>Let's say you have a bunch of old bookmarks you don't need anymore. You may want to remove some of them so you aren't scrolling endlessly through a long (and partly irrelevant) list of bookmarks.</p>
<h3 id="heading-how-to-delete-a-bookmark-in-chrome">How to Delete a Bookmark in Chrome</h3>
<p>Well, it's easy to remove bookmarks you no longer need. Simply navigate to your bookmarks tab (through the Chrome menu bar or Settings - three dots - as we discussed above) and select "Manage Bookmarks". </p>
<p>Or you can use your handy keyboard shortcut (Option+Command+B / Ctrl+Shift+O) to bring up your bookmark manager. This is what you'll see (with your own bookmarks, of course):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.24.41-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to manage bookmarks in Chrome</em></p>
<p>If you have a bookmarks folder, like I do, just double-click to expand it and see all your bookmarks. It'll then list them all out like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.25.07-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You'll notice three vertical dots to the right of each bookmark. If you click on those, it'll bring up options to manage that bookmark:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.28.58-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>Options for managing your bookmarks in Chrome.</em></p>
<p>You can edit and delete bookmarks, copy the page URL, open the bookmark in a new tab, and more.</p>
<p>If you select "Delete", that bookmark will disappear from your list. Be careful - there's no "Are you sure you want to delete this?" message. </p>
<p>But wait...what if you didn't mean to delete that bookmark you just deleted?</p>
<h2 id="heading-how-to-recover-a-deleted-bookmark-in-chrome">How to Recover a Deleted Bookmark in Chrome</h2>
<p>If you've just deleted a bookmark in Chrome and didn't mean to do so, it's ok. Just hit Command+Z / Ctrl+Z (the "undo" function/keyboard shortcut) and it'll bring it back.</p>
<p>But what if you've since closed Chrome or done other things? Command+Z / Ctrl+Z won't work. Fortunately, there's another way – it just requires a few more steps.</p>
<h3 id="heading-how-to-recover-a-bookmark-in-chrome-that-you-deleted-a-while-ago-on-macos">How to Recover a Bookmark in Chrome that you Deleted a While Ago on macOS</h3>
<p>First, go to Finder. In the window that pops up, find "Macintosh HD" in the left menu bar:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/02/Screen-Shot-2021-02-12-at-4.48.14-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>How to recover deleted bookmarks later or the next day.</em></p>
<p>Double click on the "Users" folder (and then you might have to select the user you want to use). Then double click on the following folders in this order (a new window with an alphabetized list will pop up each time you double-click): </p>
<ul>
<li>Library -&gt; Application Support -&gt; Google -&gt; Chrome -&gt; Default</li>
</ul>
<p>There, you'll find two files, one called "Bookmarks" and the other called "Bookmarks.bak". The .bak file contains your backed-up bookmarks, which will have those bookmarks that you accidentally deleted.</p>
<p>You're just a couple steps away from getting your deleted bookmarks back:</p>
<ul>
<li>Make a copy of the Bookmarks file and save it somewhere else (another location on your computer). You'll also need to rename it.</li>
<li>Now, back in Default, delete that original Bookmarks file. You need to get it out of there so you can use the .bak file as your new Bookmarks file instead.</li>
<li>Then you need to rename the .bak file to just "Bookmarks" (get rid of the .bak extension) so that it's now your Bookmarks file. This should get back any bookmarks you deleted.</li>
<li>Now you can close finder and Chrome. When you reopen Chrome, your bookmarks should be back!</li>
</ul>
<h3 id="heading-how-to-recover-a-bookmark-in-chrome-that-you-deleted-a-while-ago-on-windows">How to Recover a Bookmark in Chrome that you Deleted a While Ago on Windows</h3>
<p>Open a file browser and go to the directory where Google Chrome stores user data. This is usually in <code>C:\Users\&lt;username&gt;\AppData\Local\Google\Chrome\User Data\Default</code>.</p>
<p>Or if you use multiple Chrome people / profiles, your data might be in another directory like Profile 1. I'm recovering a bookmark I deleted on my work profile in Chrome, so my "Bookmarks" and "Bookmarks.bak" files are in <code>C:\Users\&lt;username&gt;\AppData\Local\Google\Chrome\User Data\Profile 1</code>.</p>
<p>Also, the AppData directory is hidden by default, and you may need to make hidden files and folders visible first.</p>
<p>To do that, open Windows File Explorer, click on the "View" tab, then click the box for "Hidden items" and make sure that's checked:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/windows-file-explorer-hidden-files.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the Default or Profile x directory, you'll find two files, one called "Bookmarks" and the other called "Bookmarks.bak". The .bak file contains your backed-up bookmarks, which will have those bookmarks that you accidentally deleted.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/windows-10-bookmarks-bookmarks-bak.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>The "Bookmarks" and "Bookmarks.bak" files on Windows 10</em></p>
<p>You're just a couple steps away from getting your deleted bookmarks back:</p>
<ul>
<li>Make a copy of the Bookmarks file and save it somewhere else (another location on your computer). You'll also need to rename it.</li>
<li>Now, back in Default or Profile x, delete that original Bookmarks file. You need to get it out of there so you can use the .bak file as your new Bookmarks file instead.</li>
<li>Then you need to rename the .bak file to just "Bookmarks" (get rid of the .bak extension) so that it's now your Bookmarks file. This should get back any bookmarks you deleted.</li>
<li>Now you can close your file browser and Chrome. When you reopen Chrome, your bookmarks should be back!</li>
</ul>
<h3 id="heading-how-to-recover-a-bookmark-in-chrome-that-you-deleted-a-while-ago-on-linux">How to Recover a Bookmark in Chrome that you Deleted a While Ago on Linux</h3>
<p>Open a file browser and go to the directory where Google Chrome stores user data. This is usually in <code>/home/&lt;username&gt;/.config/google-chrome/Default/</code>.</p>
<p>Note that .config is a hidden directory, and you may need to make hidden files and folders visible first. Also, the <code>google-chrome</code> directory may be in another location depending on your flavor of Linux and how you installed the browser.</p>
<p>In the Default directory, you'll find two files, one called "Bookmarks" and the other called "Bookmarks.bak". The .bak file contains your backed-up bookmarks, which will have those bookmarks that you accidentally deleted.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>The "Bookmarks" and "Bookmarks.bak" files on Linux Mint</em></p>
<p>You're just a couple steps away from getting your deleted bookmarks back:</p>
<ul>
<li>Make a copy of the Bookmarks file and save it somewhere else (another location on your computer). You'll also need to rename it.</li>
<li>Now, back in Default, delete that original Bookmarks file. You need to get it out of there so you can use the .bak file as your new Bookmarks file instead.</li>
<li>Then you need to rename the .bak file to just "Bookmarks" (get rid of the .bak extension) so that it's now your Bookmarks file. This should get back any bookmarks you deleted.</li>
<li>Now you can close your file browser and Chrome. When you reopen Chrome, your bookmarks should be back!</li>
</ul>
<p>And that's it! Now you know how to manage your bookmarks in Chrome on macOS, Windows, and Linux.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Screenshot Capture API Using Terraform, AWS API Gateway, and AWS Lambda ]]>
                </title>
                <description>
                    <![CDATA[ By Aaron Katz Recently, I really wanted to find a way to build an API that would take in a URL and save a screenshot.  My initial use case was simple: if I was analyzing phishing emails, I wanted an easy way to get a screenshot of the URL ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-screenshot-capture-api-using-terraform-aws-api-gateway-and-aws-lambda/</link>
                <guid isPermaLink="false">66d45d593a8352b6c5a2a9e7</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ aws lambda ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Google Chrome ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Terraform ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 23 Nov 2020 15:49:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c95b1740569d1a4ca0e07.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Aaron Katz</p>
<p>Recently, I really wanted to find a way to build an API that would take in a URL and save a screenshot. </p>
<p>My initial use case was simple: if I was analyzing phishing emails, I wanted an easy way to get a screenshot of the URL that the email was trying to direct their targets to.</p>
<p>To build this, I used <a target="_blank" href="https://www.terraform.io/">Terraform</a> to create all of the infrastructure necessary to set it up in AWS, using Selenium, chromedriver, and headless Chrome to obtain the screenshots.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/10/Screenshot-Service.png" alt="High level diagram illustrating what will be built in AWS" width="600" height="400" loading="lazy">
<em>High level diagram illustrating what will be built in AWS by Terraform</em></p>
<p><em>Note: All code samples are from PowerShell, so please excuse the ".\" notation.</em></p>
<h3 id="heading-requirements">Requirements</h3>
<ul>
<li>An AWS account</li>
<li>Terraform binary</li>
<li>Existing S3 bucket to store Terraform state (<a target="_blank" href="https://www.terraform.io/docs/backends/types/s3.html">https://www.terraform.io/docs/backends/types/s3.html</a>)</li>
<li>AWS IAM user and access key created with appropriate permissions (programmatic access, administrative group) for Terraform usage</li>
</ul>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<p>Create your new directory and initialize Terraform like this:</p>
<pre><code class="lang-powershell">mkdir .\screenshot<span class="hljs-literal">-service</span>
<span class="hljs-built_in">cd</span> .\screenshot<span class="hljs-literal">-service</span>
.\terraform init
</code></pre>
<h2 id="heading-configure-the-aws-provider">Configure the AWS Provider</h2>
<p>Create a file called <code>provider.tf</code> in the root of your project directory. Then configure with appropriate values for the AWS access key and secret key, as well as the name of an existing S3 bucket that will be used to store the Terraform state file.</p>
<pre><code class="lang-terraform">provider "aws" {
  region = "us-east-1"

  access_key = "ACCESSKEY"
  secret_key = "SECRETKEY"
}

terraform {
  backend "s3" {
    bucket = "EXISTING_BUCKET"
    region = "us-east-1"
    key = "KEYFORSTATE"
    access_key = "ACCESSKEY"
    secret_key = "SECRETKEY"
    encrypt = "true"
  }
}
</code></pre>
<h2 id="heading-configure-the-s3-bucket">Configure the S3 Bucket</h2>
<p>We will be using an S3 bucket to store all of our screenshots. To configure the S3 service, create a new file in the root of your project called <code>s3.tf</code> and add the following:</p>
<pre><code class="lang-terraform">resource "aws_s3_bucket" "screenshot_bucket" {
  bucket        = "STORAGE_BUCKET_NAME"
  force_destroy = true
  acl = "public-read"

  versioning {
    enabled = false
  }
}
</code></pre>
<h2 id="heading-create-the-lambda-layer">Create the Lambda Layer</h2>
<p>Let's start by creating the lambda layer which will contain the necessary binaries. First, from the root of the project, create a folder called chromedriver_layer: <code>mkdir .\chromedriver_layer</code>.</p>
<p>Next, download the chromedriver and chromium binaries:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">cd</span> .\chromedriver_layer
<span class="hljs-built_in">wget</span> https://chromedriver.storage.googleapis.com/<span class="hljs-number">2.41</span>/chromedriver_linux64.zip <span class="hljs-literal">-OutFile</span> .\chromedriver.zip
<span class="hljs-built_in">wget</span> https://github.com/adieuadieu/serverless<span class="hljs-literal">-chrome</span>/releases/download/v1.<span class="hljs-number">0.0</span><span class="hljs-literal">-54</span>/stable<span class="hljs-literal">-headless</span><span class="hljs-literal">-chromium</span><span class="hljs-literal">-amazonlinux</span><span class="hljs-literal">-2017</span><span class="hljs-literal">-03</span>.zip <span class="hljs-literal">-OutFile</span> .\headless<span class="hljs-literal">-chromium</span>.zip
<span class="hljs-built_in">Expand-Archive</span> .\headless<span class="hljs-literal">-chromium</span>.zip
<span class="hljs-built_in">rm</span> *.zip
</code></pre>
<p>Last, we need to zip this up nicely for Terraform:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">cd</span> ..\
<span class="hljs-built_in">Compress-Archive</span> .\chromedriver_layer <span class="hljs-literal">-DestinationPath</span> \chromedriver_layer.zip
</code></pre>
<h2 id="heading-how-to-configure-lambda">How to Configure Lambda</h2>
<h3 id="heading-lambda-infrastructure">Lambda Infrastructure</h3>
<p>Create a file called <code>lambda.tf</code> in the root of your project directory. First, we will create the execution role required for our function:</p>
<pre><code class="lang-terraform">resource "aws_iam_role" "lambda_exec_role" {
  name        = "lambda_exec_role"
  description = "Execution role for Lambda functions"

  assume_role_policy = &lt;&lt;EOF
{
        "Version"  : "2012-10-17",
        "Statement": [
            {
                "Action"   : "sts:AssumeRole",
                "Principal": {  
                    "Service": "lambda.amazonaws.com"
                },
                "Effect": "Allow",
                "Sid"   : ""
            }
        ]
}
EOF
}
</code></pre>
<p>Next, we will add a few policies to the execution role we have created that will enable our function to access required services:</p>
<pre><code class="lang-terraform">resource "aws_iam_role_policy" "lambda_logging" {
  name = "lambda_logging"

  role = aws_iam_role.lambda_exec_role.id

  policy = &lt;&lt;EOF
{
    "Version"  : "2012-10-17",
    "Statement": [
        {
            "Effect"  : "Allow",
            "Resource": "*",
            "Action"  : [
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:CreateLogGroup"
            ]
        }
    ]
}
EOF
}

resource "aws_iam_role_policy" "lambda_s3_access" {
  name = "lambda_s3_access"

  role = aws_iam_role.lambda_exec_role.id

  # TODO: Change resource to be more restrictive
  policy = &lt;&lt;EOF
{
  "Version"  : "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBuckets",
        "s3:PutObject",
        "s3:PutObjectAcl",
        "s3:GetObjectAcl"
      ],
      "Resource": ["*"]
    }
  ]
}
EOF
}
</code></pre>
<p>There, now our function will be able to access S3 and log to CloudWatch. Let's define our function:</p>
<pre><code class="lang-terraform">resource "aws_lambda_function" "take_screenshot" {
  filename      = "./screenshot-service.zip"
  function_name = "take_screenshot"
  role          = aws_iam_role.lambda_exec_role.arn
  handler       = "screenshot-service.handler"
  runtime       = "python3.7"

  source_code_hash = filebase64sha256("./screenshot-service.zip")
  timeout          = 600
  memory_size      = 512 
  layers = ["${aws_lambda_layer_version.chromedriver_layer.arn}"]

  environment {
    variables = {
      s3_bucket = "${aws_s3_bucket.screenshot_bucket.bucket}"
    }
  }
}
</code></pre>
<p>The above code specifies that we are uploading a lambda function package using a Python 3.7 runtime, and that the function that will be called is named "handler".  </p>
<p>I have set the timeout to 600 seconds, but feel free to change that as you will. Also, feel free to play with the memory_size – for me, this has led to super quick screenshots.</p>
<p>We also set an environment variable called <strong>s3_bucket</strong> which will be passed to the function, containing the name of the bucket used to store the screenshot.</p>
<h3 id="heading-the-lambda-function-itself">The Lambda function itself</h3>
<p>Create a folder called <code>lambda</code> in the root of the project directory and create a file called <code>screenshot-service.py</code> in that folder.</p>
<p>Add the following imports and logging configuration to the file:</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-comment"># -*- coding utf-8 -*-</span>

<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">from</span> urllib.parse <span class="hljs-keyword">import</span> urlparse, unquote <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> Can I use urllib3?</span>
<span class="hljs-keyword">from</span> selenium <span class="hljs-keyword">import</span> webdriver
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> shutil <span class="hljs-keyword">import</span> copyfile
<span class="hljs-keyword">import</span> boto3
<span class="hljs-keyword">import</span> stat
<span class="hljs-keyword">import</span> urllib.request
<span class="hljs-keyword">import</span> tldextract

<span class="hljs-comment"># Configure logging</span>
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
</code></pre>
<p>Next, we will create a function that will copy the binaries from our lambda layer and make them executable:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">configure_binaries</span>():</span>
    <span class="hljs-string">"""Copy the binary files from the lambda layer to /tmp and make them executable"""</span>
    copyfile(<span class="hljs-string">"/opt/chromedriver"</span>, <span class="hljs-string">"/tmp/chromedriver"</span>)
    copyfile(<span class="hljs-string">"/opt/headless-chromium"</span>, <span class="hljs-string">"/tmp/headless-chromium"</span>)

    os.chmod(<span class="hljs-string">"/tmp/chromedriver"</span>, <span class="hljs-number">755</span>)
    os.chmod(<span class="hljs-string">"/tmp/headless-chromium"</span>, <span class="hljs-number">755</span>)
</code></pre>
<p>Next, we will create the function that will take the screenshot of the provided domain. We will be passing in the URL and the S3 bucket name.  </p>
<p>We will add an optional parameter allowing for the title of the image to be set by the user. The screenshot is taken by Selenium automating the headless Chrome browser we downloaded.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_screenshot</span>(<span class="hljs-params">url, s3_bucket, screenshot_title = None</span>):</span>     
    configure_binaries()

    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument(<span class="hljs-string">'--headless'</span>)
    chrome_options.add_argument(<span class="hljs-string">"disable-infobars"</span>)
    chrome_options.add_argument(<span class="hljs-string">"enable-automation"</span>)

    chrome_options.add_argument(<span class="hljs-string">'--no-sandbox'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--disable-gpu'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--window-size=1280x1696'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--user-data-dir=/tmp/user-data'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--hide-scrollbars'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--enable-logging'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--log-level=0'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--disable-dev-shm-usage'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--v=99'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--single-process'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--data-path=/tmp/data-path'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--ignore-certificate-errors'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--homedir=/tmp'</span>)
    chrome_options.add_argument(<span class="hljs-string">'--disk-cache-dir=/tmp/cache-dir'</span>)
    chrome_options.add_argument(
        <span class="hljs-string">'user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'</span>)
    chrome_options.binary_location = <span class="hljs-string">"/tmp/headless-chromium"</span>

    <span class="hljs-keyword">if</span> screenshot_title <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>: 
        ext = tldextract.extract(url)
        domain = <span class="hljs-string">f"<span class="hljs-subst">{<span class="hljs-string">''</span>.join(ext[:<span class="hljs-number">2</span>])}</span>:<span class="hljs-subst">{urlparse(url).port}</span>.<span class="hljs-subst">{ext[<span class="hljs-number">2</span>]}</span>"</span>
        screenshot_title = <span class="hljs-string">f"<span class="hljs-subst">{domain}</span>_<span class="hljs-subst">{datetime.utcnow().strftime(<span class="hljs-string">'%Y%m%d_%H%M%S'</span>)}</span>"</span>
    logger.debug(<span class="hljs-string">f"Screenshot title: <span class="hljs-subst">{screenshot_title}</span>"</span>)

    <span class="hljs-keyword">with</span> webdriver.Chrome(chrome_options=chrome_options, executable_path=<span class="hljs-string">"/tmp/chromedriver"</span>, service_log_path=<span class="hljs-string">"/tmp/selenium.log"</span>) <span class="hljs-keyword">as</span> driver: 
        driver.set_window_size(<span class="hljs-number">1024</span>, <span class="hljs-number">768</span>)

        logger.info(<span class="hljs-string">f"Obtaining screenshot for <span class="hljs-subst">{url}</span>"</span>)
        driver.get(url)     

        driver.save_screenshot(<span class="hljs-string">f"/tmp/<span class="hljs-subst">{screenshot_title}</span>.png"</span>) <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> Delete the screenshot after</span>
        logger.info(<span class="hljs-string">f"Uploading /tmp/<span class="hljs-subst">{screenshot_title}</span>.png to S3 bucket <span class="hljs-subst">{s3_bucket}</span>/<span class="hljs-subst">{screenshot_title}</span>.png"</span>)
        s3 = boto3.client(<span class="hljs-string">"s3"</span>)
        s3.upload_file(<span class="hljs-string">f"/tmp/<span class="hljs-subst">{screenshot_title}</span>.png"</span>, s3_bucket, <span class="hljs-string">f"<span class="hljs-subst">{screenshot_title}</span>.png"</span>, ExtraArgs={<span class="hljs-string">'ContentType'</span>: <span class="hljs-string">'image/png'</span>, <span class="hljs-string">'ACL'</span>: <span class="hljs-string">'public-read'</span>})
    <span class="hljs-keyword">return</span> <span class="hljs-string">f"https://<span class="hljs-subst">{s3_bucket}</span>.s3.amazonaws.com/<span class="hljs-subst">{screenshot_title}</span>.png"</span>
</code></pre>
<p>Last, let's create our handler, which will be invoked when the API Gateway receives a legitimate request:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handler</span>(<span class="hljs-params">event, context</span>):</span> 
    logger.debug(<span class="hljs-string">"## ENVIRONMENT VARIABLES ##"</span>)
    logger.debug(os.environ)
    logger.debug(<span class="hljs-string">"## EVENT ##"</span>)
    logger.debug(event)

    bucket_name = os.environ[<span class="hljs-string">"s3_bucket"</span>]
    logger.debug(<span class="hljs-string">f"bucket_name: <span class="hljs-subst">{bucket_name}</span>"</span>)

    logger.info(<span class="hljs-string">"Validating url"</span>)  

    <span class="hljs-keyword">if</span> event[<span class="hljs-string">"httpMethod"</span>] == <span class="hljs-string">"GET"</span>:
        <span class="hljs-keyword">if</span> event[<span class="hljs-string">"queryStringParameters"</span>]:
            <span class="hljs-keyword">try</span>:
                url = event[<span class="hljs-string">"queryStringParameters"</span>][<span class="hljs-string">"url"</span>]
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
                logger.error(e)
                <span class="hljs-keyword">raise</span> e
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">return</span> {
                <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">400</span>,
                <span class="hljs-string">"body"</span>: json.dumps(<span class="hljs-string">"No URL provided..."</span>)
            }
    <span class="hljs-keyword">elif</span> event[<span class="hljs-string">"httpMethod"</span>] == <span class="hljs-string">"POST"</span>:
        <span class="hljs-keyword">if</span> event[<span class="hljs-string">"body"</span>]:
            <span class="hljs-keyword">try</span>:
                body = json.loads(event[<span class="hljs-string">"body"</span>])
                url = body[<span class="hljs-string">"url"</span>]
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
                logger.error(e)
                <span class="hljs-keyword">raise</span> e
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">return</span> {
                <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">400</span>,
                <span class="hljs-string">"body"</span>: json.dumps(<span class="hljs-string">"No URL provided..."</span>)
            }
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> {
            <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">405</span>,
            <span class="hljs-string">"body"</span>: json.dumps(<span class="hljs-string">f"Invalid HTTP Method <span class="hljs-subst">{event[<span class="hljs-string">'httpMethod'</span>]}</span> supplied"</span>)
        }

    logger.info(<span class="hljs-string">f"Decoding <span class="hljs-subst">{url}</span>"</span>)
    url = unquote(url)

    logger.info(<span class="hljs-string">f"Parsing <span class="hljs-subst">{url}</span>"</span>)
    <span class="hljs-keyword">try</span>: 
        parsed_url = urlparse(url)
        <span class="hljs-keyword">if</span> parsed_url.scheme != <span class="hljs-string">"http"</span> <span class="hljs-keyword">and</span> parsed_url.scheme != <span class="hljs-string">"https"</span>:
            logger.info(<span class="hljs-string">"No valid scheme found, defaulting to http://"</span>)
            parsed_url = urlparse(<span class="hljs-string">f"http://<span class="hljs-subst">{url}</span>"</span>)
        <span class="hljs-keyword">if</span> parsed_url.port <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            <span class="hljs-keyword">if</span> parsed_url.scheme == <span class="hljs-string">"http"</span>:
                parsed_url = urlparse(<span class="hljs-string">f"<span class="hljs-subst">{parsed_url.geturl()}</span>:80"</span>)
            <span class="hljs-keyword">elif</span> parsed_url.scheme == <span class="hljs-string">"https"</span>:
                parsed_url = urlparse(<span class="hljs-string">f"<span class="hljs-subst">{parsed_url.geturl()}</span>:443"</span>)

    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e: 
        logger.error(e)
        <span class="hljs-keyword">raise</span> e

    logger.info(<span class="hljs-string">"Getting screenshot"</span>)
    <span class="hljs-keyword">try</span>: 
        screenshot_url = get_screenshot(parsed_url.geturl(), bucket_name) <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> Variable!</span>
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:  
        logger.error(e)
        <span class="hljs-keyword">raise</span> e

    response_body = {
        <span class="hljs-string">"message"</span>: <span class="hljs-string">f"Successfully captured screenshot of <span class="hljs-subst">{parsed_url.geturl()}</span>"</span>,
        <span class="hljs-string">"screenshot_url"</span>: screenshot_url
    }

    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">"body"</span>      : json.dumps(response_body)
    }
</code></pre>
<p>Next, we need to install all packages that the lambda function uses into the <code>lambda</code> directory since these packages are not installed by default in AWS. </p>
<p>Then we need to create the zip archive (once created, Terraform will continue to update it if you make changes to your code):</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">cd</span> .\lambda
pip install selenium tldextract <span class="hljs-literal">-t</span> .\
<span class="hljs-built_in">cd</span> ..\
<span class="hljs-built_in">Compress-Archive</span> .\lambda <span class="hljs-literal">-DestinationPath</span> .\screenshot<span class="hljs-literal">-service</span>.zip
</code></pre>
<h2 id="heading-how-to-configure-api-gateway">How to Configure API Gateway</h2>
<p>Create a file called <code>apigw.tf</code> in the root of your project directory. First, we will configure the REST API:</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_rest_api" "screenshot_api" {
  name        = "screenshot_api"
  description = "Lambda-powered screenshot API"
  depends_on = [
    aws_lambda_function.take_screenshot
  ]
}
</code></pre>
<p>This API will be used to direct all requests that are made for the screenshot service. We use the <code>depends_on</code> feature to ensure that the gateway and its related components are only created <em>after</em> the lambda function is created.  </p>
<p>Next, let's create the API Gateway resource for the lambda function:</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_resource" "screenshot_api_gateway" {
  path_part   = "screenshot"
  parent_id   = aws_api_gateway_rest_api.screenshot_api.root_resource_id
  rest_api_id = aws_api_gateway_rest_api.screenshot_api.id
}
</code></pre>
<p>We have now defined a resource that will respond at the <code>/screenshot</code> endpoint for the API service.</p>
<p>Next up we will create a stage for the API. A stage is a fancy way of naming our deployment of the API. You can configure caching, logging, request throttling, and more using a stage.</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_stage" "prod_stage" {
  stage_name = "prod"
  rest_api_id = aws_api_gateway_rest_api.screenshot_api.id
  deployment_id = aws_api_gateway_deployment.api_gateway_deployment_get.id
}
</code></pre>
<p>Next we will create an API key and usage plan tied to our stage, so that only users with knowledge of the key can use this service. (<em>Note:</em> If you want this publicly accessible, skip this step.)</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_usage_plan" "apigw_usage_plan" {
  name = "apigw_usage_plan"

  api_stages {
    api_id = aws_api_gateway_rest_api.screenshot_api.id
    stage = aws_api_gateway_stage.prod_stage.stage_name
  }
}

resource "aws_api_gateway_usage_plan_key" "apigw_usage_plan_key" {
  key_id = aws_api_gateway_api_key.apigw_prod_key.id
  key_type = "API_KEY"
  usage_plan_id = aws_api_gateway_usage_plan.apigw_usage_plan.id
}

resource "aws_api_gateway_api_key" "apigw_prod_key" {
  name = "prod_key"
}
</code></pre>
<p>Let's now configure the API to respond to either a <strong>GET</strong> or <strong>POST</strong> request if a valid API Gateway Key is provided (set the value to false if you want the method open to the public):</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_method" "take_screenshot_get" {
  rest_api_id   = aws_api_gateway_rest_api.screenshot_api.id
  resource_id   = aws_api_gateway_resource.screenshot_api_gateway.id
  http_method   = "GET"
  authorization = "NONE"
  api_key_required = true
}

resource "aws_api_gateway_method" "take_screenshot_post" {
  rest_api_id   = aws_api_gateway_rest_api.screenshot_api.id
  resource_id   = aws_api_gateway_resource.screenshot_api_gateway.id
  http_method   = "POST"
  authorization = "NONE"
  api_key_required = true
}
</code></pre>
<p>We now need to give the API Gateway permission to invoke the lambda function we created:</p>
<pre><code class="lang-terraform">resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.take_screenshot.arn
  principal     = "apigateway.amazonaws.com"

  source_arn = "${aws_api_gateway_rest_api.screenshot_api.execution_arn}/*/*/*"
}
</code></pre>
<p>Great, we now have the appropriate permissions. Let's set up our integration with the lambda function:</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_integration" "lambda_integration_get" {
  depends_on = [
    aws_lambda_permission.apigw
  ]
  rest_api_id = aws_api_gateway_rest_api.screenshot_api.id
  resource_id = aws_api_gateway_method.take_screenshot_get.resource_id
  http_method = aws_api_gateway_method.take_screenshot_get.http_method

  integration_http_method = "POST" # https://github.com/hashicorp/terraform/issues/9271 Lambda requires POST as the integration type
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.take_screenshot.invoke_arn
}

resource "aws_api_gateway_integration" "lambda_integration_post" {
  depends_on = [
    aws_lambda_permission.apigw
  ]
  rest_api_id = aws_api_gateway_rest_api.screenshot_api.id
  resource_id = aws_api_gateway_method.take_screenshot_post.resource_id
  http_method = aws_api_gateway_method.take_screenshot_post.http_method

  integration_http_method = "POST" # https://github.com/hashicorp/terraform/issues/9271 Lambda requires POST as the integration type
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.take_screenshot.invoke_arn
}
</code></pre>
<p>This integration tells the API Gateway what lambda function to invoke when it receives a request at the specified endpoint and HTTP method.</p>
<p>Almost done with the gateway, I promise. As a last step, let's make sure that our API can send logs to CloudWatch:</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_account" "apigw_account" {
  cloudwatch_role_arn = aws_iam_role.apigw_cloudwatch.arn
}

resource "aws_iam_role" "apigw_cloudwatch" {
  # https://gist.github.com/edonosotti/6e826a70c2712d024b730f61d8b8edfc
  name = "api_gateway_cloudwatch_global"

  assume_role_policy = &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "apigateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy" "apigw_cloudwatch" {
  name = "default"
  role = aws_iam_role.apigw_cloudwatch.id

  policy = &lt;&lt;EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams",
                "logs:PutLogEvents",
                "logs:GetLogEvents",
                "logs:FilterLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
EOF
}
</code></pre>
<p>We have now given API Gateway the requisite permissions in order to write logs to CloudWatch.  </p>
<p>Last but not least, we deploy our API. We use <code>depends_on</code> to ensure that the deployment occurs after all dependencies are created.</p>
<pre><code class="lang-terraform">resource "aws_api_gateway_deployment" "api_gateway_deployment_get" {
  depends_on = [aws_api_gateway_integration.lambda_integration_get,  aws_api_gateway_method.take_screenshot_get, aws_api_gateway_integration.lambda_integration_post, aws_api_gateway_method.take_screenshot_post]

  rest_api_id = aws_api_gateway_rest_api.screenshot_api.id
}
</code></pre>
<h2 id="heading-lambda-packaging">Lambda packaging</h2>
<p>In <code>main.tf</code>, add the following:</p>
<pre><code class="lang-terraform">data "archive_file" "screenshot_service_zip" {
  type        = "zip"
  source_dir  = "./lambda"
  output_path = "./screenshot-service.zip"
}

data "archive_file" "screenshot_service_layer_zip" {
  type = "zip"
  source_dir = "./chromedriver_layer"
  output_path = "./chromedriver_lambda_layer.zip"
}
</code></pre>
<h2 id="heading-outputs">Outputs</h2>
<p>Create a file called <code>output.tf</code> in the root of your project directory and add the following:</p>
<pre><code class="lang-terraform">output "api_gateway_url" {
  value = "${aws_api_gateway_stage.prod_stage.invoke_url}/${aws_api_gateway_resource.screenshot_api_gateway.path_part}"
}

output "api_key" {
  value = aws_api_gateway_api_key.apigw_prod_key.value
}
</code></pre>
<p>Now once you run <code>.\terraform apply</code> you will get output with the URL of the API and the associated API key.</p>
<p>Congrats! You now have a working screenshot service. To view the code I've used, feel free to check out my <a target="_blank" href="https://github.com/Caliburn-Security/screenshot-service">Github</a> repository.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
