Client-side migration: unified renderer window with self-contained sign-in#651
Open
Client-side migration: unified renderer window with self-contained sign-in#651
Conversation
Replace two server-side OneNote APIs with client-side implementations: 1. Article Extraction: Replace augmentation API with @mozilla/readability - Local Readability.js parsing instead of server POST - FullPage as default clip mode (no domain whitelist) 2. Full Page Screenshot: Replace DomEnhancer API with renderer window - Scroll-capture via captureVisibleTab in focused popup window - Canvas stitching with DPR-aware overlap detection - Binary MIME part upload (no base64 overhead) - URL rewriting for images, stylesheets, srcset, CSS url() - Fixed/sticky position neutralization - Mode-switch cancel/retry mechanism - Session storage cleanup Known limitations: - External CSS not inlined (next priority) - Renderer window must stay visible (captureVisibleTab) - Canvas height capped at 16384px, storage quota 10MB - Test infrastructure uses deprecated PhantomJS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CSS caching pipeline: content script extracts CSS from document.styleSheets
(CSSOM), passes via PageInfo.stylesheetCache through the communicator to the
clipper UI, which stores it in chrome.storage.session. The renderer reads
cached CSS and replaces <link> tags with <style> blocks. For cross-origin
sheets (SecurityError), the renderer fetches CSS directly via fetch().
Renderer iframe isolation: page content renders inside an iframe to prevent
CSS conflicts between renderer styles and captured page styles.
Shadow DOM handling: flattenShadowDomSlots() detects shadow hosts via
element.shadowRoot on the live document (browser consumes <template shadowroot>
during parsing), hides non-button [slot] content since shadow roots are lost
during cloneNode(true).
Additional fixes:
- removeUnsupportedHrefs: use getAttribute("href") instead of linkElement.href
(DOM property doesn't resolve on cloned documents, was stripping all
relative-URL <link> tags)
- inlineHiddenElements: preserve display:none state from live page
- Sticky sidebar height capping to prevent grid layout stretching
- Viewport-height min-height reset (use 0, not auto)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Block user interaction (keydown, mousedown, wheel, touchstart) on the iframe document to prevent accidental scrolling during capture. Removed experimental features that caused regressions: - Pixel-level blank space cropping (caused delay, row-by-row getImageData) - Pre-adjustment height reporting (caused repeated elements on all pages) - Sticky sidebar maxHeight cap (unnecessary with CSS grid-template-rows:auto) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pping Detect when scrollY stops changing during capture (caused by fixed→absolute conversion inflating scrollHeight) and stop capturing instead of looping to the 16384px safety cap. Measure content height before position conversions and use it to crop blank space from the stitched image. Also fix a race condition where cleanup() async-removed screenshot keys that were immediately re-set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Block click, pointerdown, contextmenu, and selectstart events in addition to existing keydown/mousedown/wheel/touchstart — prevents user interaction with the renderer window during capture. Also add stopPropagation for defense in depth. Refactor cleanup() to accept removeOutputKeys flag: failure/cancel paths remove screenshot output keys from session storage, while the success path preserves them for fullPageScreenshotHelper to read. Reorder success path to write output before cleanup to prevent async remove/set race. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use a fixed full-screen div (#interaction-shield) at max z-index to block all mouse, touch, pointer, hover, drag, and context menu interactions. Keyboard and wheel events still blocked via JS listeners since they don't respect z-index. Removes per-event blocking from both host page and iframe. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of accumulating all viewport captures in session storage (4-8MB) and stitching in fullPageScreenshotHelper, each capture is now sent back to the renderer via port for incremental drawing onto a hidden canvas. The renderer produces a single final JPEG and stores it in session storage. - Captures switched to PNG (lossless) — single JPEG encode at finalize - stitchImages() and ScrollData removed from fullPageScreenshotHelper - Session storage holds only HTML + one final JPEG (~1-2MB total) - Overlap/DPR calculation moved to renderer drawCapture handler - drawComplete ack prevents memory buildup from queued captures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reflect current architecture: incremental canvas stitching in renderer, PNG captures via port, scroll stall detection, content height cropping, interaction shield overlay, and simplified fullPageScreenshotHelper. Update data flow diagram, key decisions, and known issues sections. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dling Replace interaction shield div with pointer-events:none on iframe CSS — simpler, no compositing layer. Add imageSmoothingEnabled=false on stitch canvas for pixel-perfect drawing. Lock renderer window size via resize event listener. Handle port disconnect (user closes renderer) to prevent clipper from spinning indefinitely. Switch final output to JPEG 95%. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…us renderer - offscreenCommunicator: use chrome API directly instead of WebExtension.browser which is only initialized in service worker context (fixes Article mode crash) - renderer: strip <link rel="preload" as="script"> and modulepreload tags that trigger CSP violations on extension pages - worker: re-focus renderer window before each capture via windows.update to handle user clicking away mid-capture Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…LESS styles
Renderer window now includes a branded sidebar (322px) alongside the content
iframe. During capture, sidebar shows localized progress ("Capturing 3 of 5").
After capture, shows preview with Save/Close buttons.
- renderer.html: flexbox layout with content iframe + sidebar panel
- renderer.less: new LESS file using OneNote brand colors, compiled via gulp
- renderer.ts: sidebar progress updates, sidebar pixel cropping from captures,
preview phase with scrollable image, localized strings from session storage
- fullPageScreenshotHelper.ts: passes fullPageStrings map with i18n labels
- webExtensionWorker.ts: window width = content + sidebar, passes totalViewports
- strings.json: new keys for IncrementalProgress and Saving
- gulpfile.js: compiles renderer.less alongside clipper.less
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…save flow Complete Phase 2 implementation of the unified clipper window: - Mode buttons (Full Page, Article, Bookmark, Region) with sidebar controls - Article mode: Readability extraction directly from content-frame DOM - Bookmark mode: og:image/description metadata extraction from DOM - Section picker from cached localStorage notebooks - Title, note, source URL fields with i18n from localStorage - Save flow: worker builds multipart form, POSTs to OneNote API - Post-save: "View in OneNote" button with page URL from API response - Error display with expandable diagnostics (correlation ID, date, status) - Sidebar auto-hides for signed-in users (hideUi via inject communicator) - Window stays open after capture for mode switching - Duplicate window prevention (focus existing on re-click) - Tab navigation detection closes renderer window - UI lock during save (all inputs disabled during API round-trip) - Cancel + Clip side-by-side button row - Modal-like focus retention Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gn-in flow Sign-out: renderer → worker → uiCommunicator.showSignInPanel → clipper.tsx resets state + clipperInject.showUi → sign-in panel appears. User info footer pinned to sidebar bottom with email + sign-out link. Section refresh: auto-fetches fresh notebooks from OneNote API on renderer open (direct fetch with Bearer auth). Token expiration check uses relative accessTokenExpiration with lastUpdated offset. Region capture: standalone regionOverlay.ts injected into original tab via scripting.executeScript. Crosshair overlay with canvas hole-punch selection. Coords sent as JSON string (required by offscreen.ts message handler). Worker captures tab as JPEG 95%, sends via port. Renderer crops with DPR handling. Multi-region: thumbnails with × remove buttons, + Add another region button. Regions cached across mode switches. Each region stored as separate session storage key to avoid size limits. Post-sign-in: clipper.tsx detects SignInAttempt updateReason, hides sidebar, starts capture → opens renderer. Non-signed-in guard prevents renderer opening without auth. Full-page preview restored from session storage when switching back from region mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… polish - Remove fullPageStrings legacy i18n passthrough from session storage - Resolve fullPageScreenshotHelper promise on finalizeComplete instead of leaving it pending indefinitely - Move save URL from session storage to port message (message.url) so all save parameters flow through the port; only fullPageFinalImage and regionImage_N remain in session storage (too large for port messages) - Cache fullPageDataUrl in page variable for instant mode switching (no async session storage read needed) - Session storage cleanup: worker cleanup() removes all fullPage*/regionImage* keys on window close; helper cleanup removes source data after finalize - Readability: dynamic import pattern added (browserify still bundles inline; ready for bundler upgrade) - Region overlay: hide scrollbar via CSS style injection instead of overflow:hidden (preserves scroll during selection); remove-all-regions stays in region mode with add button instead of snapping to fullpage - Update plan doc with resolved tech debt items Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Worker opens renderer window directly on button click (bypasses clipperInject.ts injection entirely). Renderer shows MSA/OrgId sign-in overlay when not signed in; transitions to capture mode after OAuth. Key changes: - New contentCaptureInject.ts: standalone content script reads page DOM + stylesheets, sends to worker via chrome.runtime.sendMessage - Worker: openRendererWindow() checks isUserLoggedIn, handles signIn/ signOut port messages, content capture listener - Renderer: sign-in overlay, signInResult/signOutComplete handlers, custom section picker (ul/li with scrollable dropdown), UI lock during capture, Close button, anti-maximize via chrome.windows API - Region overlay: selection border drawn on canvas (no separate div), overflow:hidden on root, no scrollbar manipulation - Sign-out keeps renderer open (shows sign-in overlay), full state reset including notebook cache and stale capture data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Renderer telemetry: imports Funnel/LogMethods/Session enums, sends LogDataPackage via port to worker's logger (Invoke, AuthAttempted, AuthSignInCompleted/Failed, ClipAttempted, SignOut, session lifecycle) - Worker: telemetry port handler routes to parseAndLogDataPackage; save handler refreshes token via auth.updateUserInfoData before API call - docs/unified-window-plan.md: V3 flow diagram, V2/V3 checklist, telemetry docs (#14-16), consistent versioning - docs/client-side-migration.md: V3 data flow, updated CSS fidelity refs, JPEG 95%, V3 evolution section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracted DOM cleaning functions from DomUtils into contentCaptureInject as self-contained inline functions (zero external imports). Replaces the previous raw outerHTML approach that truncated at 2MB and missed content. Pipeline matches old clipperInject.ts → DomUtils.getCleanDomOfCurrentPage: clone → inline hidden elements → flatten shadow DOM → canvas→image → base tag → image sizes → remove unwanted items (scripts, noscript, clipper elements, non-web links, binary styles) → remove srcset → serialize. Plus lazy image resolution (data-src → src) for sites using loading="lazy". Output: ~10KB standalone script, no dependency on DomUtils/Constants/ ObjectUtils modules. Ready for dead code cleanup phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
contentCaptureInject.ts: Align with master DomUtils.getCleanDomOfCurrentPage
pipeline (isLocalReferenceUrl, split removeUnwantedItems into 5 sub-functions,
full DOCTYPE with publicId/systemId, iframe local-ref filtering). Layer
enhancements on top: neutralizePositioning converts sticky→relative and
fixed→absolute with !important to prevent repetition in stitched captures.
renderer.ts: Remove fullPageStylesheets session storage consumption (renderer
fetches CSS directly via <link> tags). Add safeSend() wrapper for all
port.postMessage calls to handle disconnected port errors. Inject
[hidden]{display:none!important} CSS override. Position neutralization
upgraded to use !important.
webExtensionWorker.ts: Remove fullPageStylesheets storage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
unified-window-plan.md: Mark sticky element duplication as resolved (!important fix). Update sign-out flow to V3 (stays in renderer). Remove stale stylesheet reference from flow diagram. Add video/streaming embed as known limitation. Add safeSend, [hidden] CSS, position neutralization to verification checklist. client-side-migration.md: Remove resolved "bottom void" from remaining issues. Add video/streaming embed limitation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
renderer.ts: Add cleanArticleHtml() to strip ONML-unsupported elements and style/class attributes from Readability output before caching — ensures preview matches what gets saved to OneNote (old toOnml pipeline parity). Fix article/bookmark preview styling to match OneNote page layout: Segoe UI 11pt, 624px max-width (@OneNotePageWidth), left-aligned margin instead of auto-centered. docs: Add CSR/shadow DOM sites as known limitation (pre-existing, shared with server-side Puppeteer). Document article mode ONML cleanup in CSS fidelity section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
contentCaptureInject.ts: Inline the original page's computed body
font-size onto the cloned body element. CSS reset stylesheets (e.g.,
body{font-size:75%}) may resolve differently in the renderer iframe
due to stylesheet loading order, causing text to render 25% smaller.
Captures the actual computed value and applies it with !important,
matching the pattern used for inlineHiddenElements and
neutralizePositioning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Feedback link (OrgId users only, matching old sidebar): - renderer.html: Smiley icon + i18n label in footer, left-aligned. Footer restructured: feedback left, email + sign-out stacked right. - renderer.ts: Click handler sends openFeedback port message with page URL. Hidden for MSA users. i18n via WebClipper.Action.Feedback. - renderer.less: Footer layout with flex-end alignment, stacked user-info-right, feedback-icon and separator styling. - webExtensionWorker.ts: openFeedback handler builds feedback URL with all 6 params (LogCategory, originalUrl, clipperId, usid, version, type). Opens 1000x700 popup via chrome.windows.create. Session USID and API correlation: - Per-session USID generated in launchRenderer (cccccccc- prefix + v4 UUID tail, matching old logger pattern). Sent as X-UserSessionId header in save API calls for server-side log correlation. Same USID used in feedback URL for support ticket correlation. - Refactored GUID generation into static newGuid() helper, used for both sessionUsid and per-request X-CorrelationId (was non-standard timestamp-based, now proper v4 UUID matching StringUtils.generateGuid). Error diagnostics: - Copy button (clipboard emoji) next to "More information" in error display. Uses navigator.clipboard.writeText (no extra permissions). Shows green checkmark on success. stopPropagation prevents toggling details open/close. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- i18n: wire sign-in panel, field labels, error messages to loc() using existing server-translated keys (zero new strings.json keys needed) - Contrast: error color #ff6b6b→#ff9999 (WCAG AA), region btn border #bbb→#999 (SC 1.4.11), focus outlines 2px solid #f8f8f8 - ARIA: radiogroup/radio for mode buttons, combobox for section picker, role="dialog" on sign-in overlay, role="alert" on error, role="main" on sidebar, aria-live announcements, progressbar role - Keyboard: arrow keys + Home/End on mode buttons and section picker, Escape to close dropdown, clean tab order (iframes tabindex=-1, Clip before Cancel), focus management on capture/sign-in transitions - Tab order: mode buttons→title→note→section→Clip→Cancel→feedback→signout - Focus outlines: 2px #f8f8f8 on purple, high-contrast Highlight mode - Dynamic html lang from localStorage.locale (BCP 47) - Removed blur re-focus handler (fought Alt+Tab, screen readers, popups) - Keydown handler allows arrows/modifier combos for screen reader compat - Screen reader testing: ARIA tree verified correct via edge://accessibility but Edge disables a11y API flags for extension popup windows (untested) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fresh fix Region overlay: Shadow DOM instruction bar with i18n text + "Back (Esc)" button, CSS-isolated from page styles. Escape/Back stays in region mode instead of switching to full page. Worker injects i18n strings via window.__regionStrings before overlay script. Renderer: add mode button tooltips using existing i18n keys. Remove token expiry pre-check from notebook fetch so newly created sections appear. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
|
@microsoft-github-policy-service agree company="Microsoft" |
…t and image width - Version bump to 3.11.0 across package.json and Chrome/Edge manifests - Upgrade gulp-uglify v2→v3 (UglifyJS v3) to support ES6+ minification (Readability) - Update preserveComments→output.comments for gulp-uglify v3 API, preserve license headers - Remove "Clipping Page" status heading during capture (reserve "clipping" for save phase) - Hide progress bar until first viewport capture arrives - Fix full-page screenshot width in OneNote ONML: pass actual CSS width from renderer (contentPixelWidth / DPR) instead of pre-calculated window width, fixing aspect ratio Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix minified × character showing as "×" by declaring UTF-8 charset - Update a11y docs: NVDA + Edge verified working after devbox reboot Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Mode panel: role="radiogroup" → role="toolbar", buttons use aria-pressed - More natural UX: NVDA announces "toggle button, pressed" instead of "radio button" - Arrow key navigation retained (standard toolbar pattern) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… timeout
- Add purple toolbar above article preview with three control groups:
- Highlighter toggle: TextHighlighter injected into iframe, yellow (#fefe56)
highlights with red circle × delete buttons (top-left corner)
- Font family: Sans-serif (Verdana) / Serif (Georgia) toggle buttons
- Font size: +/- buttons, 2px increments, 8px–72px range
- Font family and size applied to saved OneNote content via wrapping div style
- Highlight state preserved across mode switches (articleWorkingHtml snapshot)
- Save serializes highlights from cloned DOM (preserves live preview)
- Add 30-second save timeout via Promise.race (matches old OneNoteApi default)
- Consistent × button positioning: region thumbnails moved to top-left to match
- Header bar visible only in article mode, hidden in all other modes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set highlight_cursor.cur on iframe body when highlighter enabled - Restore default cursor when disabled - Cursor persists correctly after mode switch back to article Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clip button always stays as "Clip" for re-clipping - Success banner below buttons shows "✓ Clip Successful!" + purple "View in OneNote" button (opens page, closes clipper window) - Banner clears on mode switch or re-clip - Fix display:none CSS default override for View button Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, bug fixes - Section picker: notebook/section-group headings with icons, indented sections, keyboard nav skips headings, icons inverted white for dark background contrast - Success banner: separate "Clip Successful!" + "View in OneNote" button below Clip/Cancel. Clip button stays as Clip for re-clipping. Banner clears on mode switch, re-clip, or section change. - Save timeout moved to renderer (service worker setTimeout unreliable — SW suspends after ~30s). 30s client-side timeout with error display + retry. - Fix: section selection no longer resets button state (was enabling Clip during capture). Only clears success banner + saveDone flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lint fixes across new files: - renderer.ts: null→undefined, var→let, else placement, shadowed variable - regionOverlay.ts: single→double quotes - webExtensionWorker.ts: var→let, tslint-disable for Chrome API null param - domUtils.ts: single→double quotes Service worker keepalive: renderer pings every 25s via port to prevent MV3 service worker suspension while popup is open. Inactivity auto-close: renderer closes after 5 minutes without mouse, keyboard, scroll, or focus activity. Timer resets on any interaction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
renderer.html(content iframe + sidebar) opened by the service workerclipperInject.tsinjection in the primary flowcontentCaptureInject.tsperforms full DOM cleaning (master-compatible DomUtils baseline + enhancements: hidden element preservation, position neutralization, shadow DOM flattening, lazy image resolution)captureVisibleTabwith sidebar cropping, JPEG 95% stitching in rendererregionOverlay.tswith Shadow DOM instruction bar, crosshair overlay, multi-region thumbnails, i18n stringsrole="option",role="combobox",role="progressbar",role="alert",role="dialog"), keyboard navigation,aria-liveannouncements,<html lang>attributesafeSend()wrapper prevents disconnected port errorsKey new/modified files
src/renderer.htmlsrc/scripts/renderer.tssrc/styles/renderer.lesssrc/scripts/extensions/contentCaptureInject.tssrc/scripts/extensions/regionOverlay.tssrc/scripts/extensions/webExtensionBase/webExtensionWorker.tsgulpfile.jsdocs/Test plan
🤖 Generated with Claude Code